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 &&
Travel term

This term concerns one physical action during the selected turn. The sole recipient commits to travel in a two-party deal. In a group deal, choose one recipient to commit; they accept last after all other recipients. Written terms remain player-managed.

-

A two-party travel term may include an immediate payment in either direction. The payment recipient collects their share at the next Mentat opening and can use it after collection. Group travel terms cannot include a payment.

- {!combinedPaymentValid &&

For two-party travel, choose an immediate payment between the two participants. Group travel cannot include payment.

} +

A travel term may include an immediate payment between two participants. The payment recipient collects their share at the next Mentat opening and can use it after collection. All invited parties accept before the traveler gives final consent.

+ {!combinedPaymentValid &&

For travel, choose an immediate payment between two participants.

} {selected.length > 1 &&