Preserve Hajr after another seat accepts a paid deal

This commit is contained in:
Quintine committed 2026-10-28 00:57:50 +11:00
1 parent 6da6ff7f83
commit 3edebecc89
7 files changed
+115 -5

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { applyAction, viewGame, type Game } from '../game/engine';
import {
completeHajrReplay, createHajrReplayFixture, hajrReplayPlayer,
} from './fixture-hajr-replay';
/** This reaches the native Advanced immediate-after-movement Hajr window via
* setup, auction, shipment, actual Hajr play, Box recovery and movement. The
* genuine fixture has spent its spice on those actions; only the payer's spice
* is explicitly staged so this independent deal payment can be exercised. */
function windowGame() {
const fixture = createHajrReplayFixture({ advanced: true });
const game = completeHajrReplay(fixture).beforeSecondHajr;
const payer = game.players.find(player => player.id === fixture.owner)!;
const recipient = game.players.find(player => player.id !== payer.id && !player.bot &&
player.id !== payer.ally && player.ally !== payer.id);
assert.ok(recipient);
assert.equal(payer.spice, 0, 'The original purchases, shipment and Box consumed the fixture spice.');
payer.spice = 4; // Controlled funding only; the native movement/window is not staged.
assert.ok(payer.hajrWindow);
assert.equal(available(game, payer.id), true);
return { fixture, game, payer: payer.id, recipient: recipient.id };
}
function available(game: Game, id: string) {
return viewGame(game, id).players.find(player => player.id === id)!.hajrAvailable;
}
function offer(game: Game, from: string, to: string, payer: string, recipient: string): Game {
return applyAction(game, from, {
type: 'dealOffer', target: to, terms: 'An immediate one-spice bribe.',
payment: { kind: 'immediateBribe', payer, recipient, amount: 1 },
});
}
void test('another seat accepting a prior-consented payment preserves the payer’s native Hajr opportunity', () => {
const { fixture, game: initial, payer, recipient } = windowGame();
const window = structuredClone(hajrReplayPlayer(initial).hajrWindow);
const beforeSpice = hajrReplayPlayer(initial).spice;
const beforeBribes = initial.players.find(player => player.id === recipient)!.bribes;
const offered = offer(initial, payer, recipient, payer, recipient);
assert.deepEqual(hajrReplayPlayer(offered).hajrWindow, window);
const deal = offered.deals!.at(-1)!;
const paid = applyAction(offered, recipient, { type: 'dealRespond', deal: deal.id, accept: true });
assert.equal(paid.deals!.at(-1)!.status, 'active');
assert.equal(hajrReplayPlayer(paid).spice, beforeSpice - 1);
assert.equal(paid.players.find(player => player.id === recipient)!.bribes, beforeBribes + 1);
assert.deepEqual(hajrReplayPlayer(paid).hajrWindow, window);
assert.equal(available(paid, payer), true);
const played = applyAction(paid, payer, { type: 'card', card: fixture.hajr });
assert.equal(hajrReplayPlayer(played).hajrPlays, 2);
assert.equal(hajrReplayPlayer(played).spice, beforeSpice - 1);
assert.equal(played.players.find(player => player.id === recipient)!.bribes, beforeBribes + 1);
});
void test('the payer’s own voluntary bribe or final acceptance closes the immediate Hajr window', () => {
const { game: initial, payer, recipient } = windowGame();
const direct = applyAction(initial, payer, { type: 'bribe', target: recipient, amount: 1 });
assert.equal(hajrReplayPlayer(direct).hajrWindow, undefined);
assert.equal(available(direct, payer), false);
const offered = offer(initial, recipient, payer, payer, recipient);
assert.equal(available(offered, payer), true);
const accepted = applyAction(offered, payer, { type: 'dealRespond', deal: offered.deals!.at(-1)!.id, accept: true });
assert.equal(hajrReplayPlayer(accepted).hajrWindow, undefined);
assert.equal(available(accepted, payer), false);
assert.equal(hajrReplayPlayer(accepted).spice, 3);
});