Files
Dune/tests/public-deals-cross-turn-runtime.test.ts
T

128 lines
7.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Action, type Game } from '../game/engine';
import {
actMentatBribe as act, advanceMentatBribe as advance,
createMentatBribeFixture as fixture, finishMentatBribe as finish,
mentatBribeClean as clean, mentatBribeInventory as inventory,
mentatBribePlayer as player,
} from './fixture-mentat-bribes';
function createDeal(game: Game, proposer: string, payer: string, recipient: string,
amount: number, dueTurn: number) {
const offered = act(game, proposer, { type: 'dealOffer', target: payer,
terms: 'Pay the agreed spice by the stated turn.',
payment: { kind: 'deferredBribe', payer, recipient, amount, dueTurn } });
const deal = offered.deals?.at(-1); assert.ok(deal);
const accepted = act(offered, payer, { type: 'dealRespond', deal: deal.id, accept: true });
assert.equal(accepted.deals?.find(row => row.id === deal.id)?.paymentStatus, 'pending');
return { game: accepted, id: deal.id };
}
function status(game: Game, id: string) {
const deal = game.deals?.find(row => row.id === id); assert.ok(deal);
return deal.paymentStatus;
}
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => act(game, actor, action));
assert.deepEqual(game, before, 'A rejected payment leaves the original game intact.');
}
/** Each route begins at the original H/A/E Basic turn-one Mentat, earned by
* native setup, bidding and collection. The shared fixture selects only
* unopened physical card/Spice positions; no hand or wallet is installed. */
void test('an accepted next-turn deal survives the real turn boundary and may be paid at its due turn', () => {
const f = fixture(), payer = f.target, recipient = f.asker;
const initialSpice = player(f.game, payer).spice;
const { game: accepted, id } = createDeal(f.game, recipient, payer, recipient, 3, f.game.turn + 1);
assert.equal(player(accepted,payer).spice,initialSpice);
const next = finish(accepted);
assert.equal(next.turn,f.game.turn + 1); assert.equal(status(next,id),'pending');
assert.equal(player(next,payer).spice,initialSpice);
assert.equal(viewGame(next,payer).deferredDealPaymentOptions[id]?.blocked,null);
const paid = act(next,payer,{type:'dealPay',deal:id});
assert.equal(status(paid,id),'paid');
assert.equal(player(paid,payer).spice,initialSpice - 3);
assert.equal(player(paid,recipient).bribes,3);
reject(paid,payer,{type:'dealPay',deal:id});
const beforeOpening = advance(paid, game => game.turn === next.turn && game.phase === 7 && clean(game));
const recipientBefore = player(beforeOpening,recipient).spice;
const opening = advance(beforeOpening, game => game.turn === next.turn && game.phase === 8 && clean(game));
assert.equal(player(opening,recipient).spice,recipientBefore + 3);
assert.equal(player(opening,recipient).bribes,0);
assert.equal(status(opening,id),'paid');
assert.deepEqual(inventory(opening),f.originalCards);
});
void test('a future-due deal can be paid early, and its actual late Mentat receipt waits until the next opening', () => {
const f = fixture(), payer = f.target, recipient = f.asker;
const { game: accepted, id } = createDeal(f.game,recipient,payer,recipient,3,f.game.turn + 1);
const recipientSpice = player(accepted,recipient).spice;
const paid = act(accepted,payer,{type:'dealPay',deal:id});
assert.equal(status(paid,id),'paid');
assert.equal(player(paid,recipient).bribes,3);
assert.equal(player(paid,recipient).spice,recipientSpice);
const beforeOpening = advance(paid, game => game.turn === f.game.turn + 1 && game.phase === 7 && clean(game));
const priorSpice = player(beforeOpening,recipient).spice;
const opening = advance(beforeOpening, game => game.turn === f.game.turn + 1 && game.phase === 8 && clean(game));
assert.equal(player(opening,recipient).spice,priorSpice + 3);
assert.equal(player(opening,recipient).bribes,0);
assert.equal(status(opening,id),'paid');
assert.deepEqual(inventory(opening),f.originalCards);
});
void test('before the due turn an accepted debt neither reserves current spice nor blocks ending the turn', () => {
const f = fixture(), payer = f.target, recipient = f.asker;
const { game: accepted, id } = createDeal(f.game,recipient,payer,recipient,6,f.game.turn + 1);
const spent = act(accepted,payer,{type:'bribe',target:f.opponent,amount:8});
assert.equal(player(spent,payer).spice,2);
assert.equal(status(spent,id),'pending');
const next = finish(spent);
assert.equal(next.turn,f.game.turn + 1);
assert.equal(status(next,id),'pending', 'Future funding is unknown, not automatically impossible.');
assert.deepEqual(inventory(next),f.originalCards);
});
void test('a payable debt blocks its payer from ending the actual due-turn Mentat', () => {
const f = fixture(), payer = f.target, recipient = f.asker;
const { game: accepted, id } = createDeal(f.game,recipient,payer,recipient,3,f.game.turn + 1);
const due = advance(accepted, game => game.turn === f.game.turn + 1 && game.phase === 8 && clean(game));
assert.equal(status(due,id),'pending');
reject(due,payer,{type:'ready'});
const paid = act(due,payer,{type:'dealPay',deal:id});
const ready = act(paid,payer,{type:'ready'});
const next = finish(ready);
assert.equal(next.turn,f.game.turn + 2);
assert.equal(status(next,id),'paid');
assert.deepEqual(inventory(next),f.originalCards);
});
void test('unanimous early release cancels only the future unpaid term', () => {
const f = fixture(), payer = f.target, recipient = f.asker;
const { game: accepted, id } = createDeal(f.game,recipient,payer,recipient,3,f.game.turn + 1);
const requested = act(accepted,recipient,{type:'dealRelease',deal:id,agree:true});
assert.equal(status(requested,id),'pending');
const released = act(requested,payer,{type:'dealRelease',deal:id,agree:true});
assert.equal(status(released,id),'released');
reject(released,payer,{type:'dealPay',deal:id});
const next = finish(released);
assert.equal(status(next,id),'released');
assert.equal(player(next,payer).spice,player(f.game,payer).spice);
assert.deepEqual(inventory(next),f.originalCards);
});
void test('different due turns remain separate and may be settled in either order', () => {
const f = fixture(), payer = f.target, recipient = f.asker, turn = f.game.turn;
const first = createDeal(f.game,recipient,payer,recipient,3,turn + 1);
const second = createDeal(first.game,recipient,payer,recipient,4,turn + 2);
const next = finish(second.game);
assert.equal(status(next,first.id),'pending'); assert.equal(status(next,second.id),'pending');
const paidSecond = act(next,payer,{type:'dealPay',deal:second.id});
assert.equal(status(paidSecond,first.id),'pending'); assert.equal(status(paidSecond,second.id),'paid');
assert.equal(player(paidSecond,recipient).bribes,4);
const paidFirst = act(paidSecond,payer,{type:'dealPay',deal:first.id});
assert.equal(status(paidFirst,first.id),'paid');
assert.equal(player(paidFirst,recipient).bribes,7);
assert.deepEqual(inventory(paidFirst),f.originalCards);
});