From 6c1530382dfd8ea1b1b56384f88cc2d4697fa757 Mon Sep 17 00:00:00 2001
From: Quintine <15965533+Quintine@users.noreply.github.com>
Date: Thu, 29 Oct 2026 17:41:58 +1100
Subject: [PATCH] Support immediate payments in group travel deals
---
components/public-deals.tsx | 10 +-
docs/CURRENT_STATUS.md | 9 ++
docs/PUBLIC_DEALS.md | 33 +++++--
game/engine.ts | 2 +-
game/public-deals.ts | 7 +-
game/reference.ts | 2 +-
...ic-deals-group-paid-travel-runtime.test.ts | 99 +++++++++++++++++++
.../public-deals-group-travel-runtime.test.ts | 2 +-
tests/public-deals-travel-controls.test.tsx | 21 +++-
tests/public-deals-travel-schema.test.ts | 46 ++++++++-
10 files changed, 207 insertions(+), 24 deletions(-)
create mode 100644 tests/public-deals-group-paid-travel-runtime.test.ts
diff --git a/components/public-deals.tsx b/components/public-deals.tsx
index 9b8e00ce..a1fe0613 100644
--- a/components/public-deals.tsx
+++ b/components/public-deals.tsx
@@ -69,7 +69,7 @@ export function publicDealOfferAction(targets: readonly string[], terms: string,
const travelKeys = travel ? Object.keys(travel).filter(key => key !== 'dueTurn').sort().join(',') : '';
const travelDeadline = travel && Object.hasOwn(travel, 'dueTurn') ? travel.dueTurn : undefined;
if (travel && (!proposer || targets.includes(proposer) ||
- (groupTravel ? !!payment || travelKeys !== 'actor,answer,kind,minimum,territory' ||
+ (groupTravel ? !!payment && payment.kind !== 'immediateBribe' || travelKeys !== 'actor,answer,kind,minimum,territory' ||
typeof travel.actor !== 'string' || !targets.includes(travel.actor) :
travelKeys !== 'answer,kind,minimum,territory' ||
!!payment && payment.kind !== 'immediateBribe') ||
@@ -121,7 +121,7 @@ export function PublicDealList({ game, act, disabled }: {
const finalAcceptance = recipient && !accepted.includes(game.me) && accepted.length === participants.length - 1;
const travelActorWaiting = !!travel && participants.length > 2 && game.me === travelActor &&
participants.some(participant => participant !== travelActor && !accepted.includes(participant));
- const paymentBlocked = payment?.kind === 'immediateBribe' && finalAcceptance ? game.bribeOptions?.blocked : null;
+ const paymentBlocked = payment?.kind === 'immediateBribe' && finalAcceptance && payment.payer === game.me ? game.bribeOptions?.blocked : null;
const paymentExpired = payment?.kind === 'deferredBribe' && game.turn > payment.dueTurn;
const deferredOption = (game as DeferredPaymentProjection).deferredDealPaymentOptions?.[deal.id];
const mayPay = payment?.kind === 'deferredBribe' && deal.status === 'active' && paymentStatus === 'pending' &&
@@ -227,7 +227,7 @@ export function PublicDeals({ game, act, busy }: {
...(selected.length > 1 ? {actor:travelActor} : {}),
...(travelTurn > game.turn ? {dueTurn:travelTurn} : {})} : undefined;
const combinedPaymentValid = !includeTravel || !payment || payment.kind === 'immediateBribe' &&
- selected.length === 1 && paymentPeopleAndAmountValid;
+ paymentPeopleAndAmountValid;
const offer = legalSelection && paymentValid && (!includeTravel || travelDueValid)
? publicDealOfferAction(selected, terms, payment, me?.id, game.turn, travel) : null;
const disabled = busy || !!game.roomControl?.paused || !!game.roomControl?.closed || !!me?.autopilot;
@@ -308,8 +308,8 @@ export function PublicDeals({ game, act, busy }: {
{includeTravel &&