import assert from 'node:assert/strict'; import test from 'node:test'; import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance'; import { applyAction, createGame, newPlayer, type Action, type Game } from '../game/engine'; const act = (game: Game, actor: string, action: Action) => applyAction(game, actor, action); /** A player's retained partner and unrelated bribe remain jointly feasible. * The empty joint ledger is significant: it delegates accepted-order * reconciliation to the scalar alliance planner. */ function fixture(): Game { let game = createGame('ADMISSIONJOINT', newPlayer('a', 'Atreides', 'atreides'), false); game.players.push(newPlayer('b', 'Emperor', 'emperor'), newPlayer('c', 'Fremen', 'fremen'), newPlayer('d', 'Harkonnen', 'harkonnen'), newPlayer('e', 'Guild', 'guild')); Object.assign(game, {status:'playing', phase:1, turn:2, nexus:true, order:['a','b','c','d','e'], ready:[]}); game.coalitionState = emptyCoalitionState('unlimited'); game.players.find(player => player.id === 'a')!.spice = 8; game = act(game, 'a', {type:'alliance', target:'b'}); game = act(game, 'b', {type:'alliance', target:'a'}); const source = JSON.stringify(['ordinaryNexus', game.turn, game.spiceDiscard.map(pile => pile.length), game.spiceSequence?.pile ?? null, game.spiceSequence?.skipped?.length ?? 0]); game.alliancePromises = [{event:'alliance', source, player:'a', asker:'e', turn:game.turn, partner:'b', answer:true}]; game.bribePromises = [{event:'bribe', player:'a', asker:'e', turn:game.turn, recipient:'e', minimum:1, answer:true, progress:0}]; game.turnActionPromises = []; return game; } function proposal(game: Game) { const group = coalitionOf(game.players, game.coalitionState, 'a'); assert.ok(group); return {type:'coalitionPropose', groupId:group.id, entrant:'c', expectedRevision:game.coalitionState!.revision}; } function answer(game: Game, accept: boolean) { const offer = game.coalitionState!.proposal; assert.ok(offer); return {type:'coalitionAnswer', event:offer.event, nexus:offer.nexus, turn:offer.turn, accept, expectedRevision:game.coalitionState!.revision}; } function commitmentsLive(game: Game) { assert.equal(game.bribePromises![0].released ?? false, false); assert.equal(game.alliancePromises![0].released ?? false, false); } void test('joint accepted-order promises remain binding after group admission', () => { let game = fixture(); game = act(game, 'a', proposal(game)); commitmentsLive(game); game = act(game, 'b', answer(game, true)); commitmentsLive(game); game = act(game, 'c', answer(game, true)); commitmentsLive(game); assert.equal(game.coalitionState!.proposal, null); assert.deepEqual(coalitionOf(game.players, game.coalitionState, 'a')!.members, ['a','b','c']); game = act(game, 'a', {type:'bribe', target:'e', amount:1}); assert.equal(game.bribePromises![0].fulfilled, true); assert.equal(game.alliancePromises![0].released ?? false, false); });