Allow bribes during classic pending decisions
Verify and publish checkpoint container / container (push) Successful in 10m47s

This commit is contained in:
Quintine committed 2026-10-29 08:26:34 +11:00
1 parent 1e81ce8ad8
commit 6ecd26c8d1
8 files changed
+444 -5

No files matched your search

+34 -2
View File
@@ -17342,10 +17342,10 @@ function bribePaymentPreservesPromises(g: Game, p: Player, recipient: string, am
* advancing their pending state. Existing whole-promise planners retain their
* original timing boundary until those compositions are implemented. */
function basicBribeInterrupt(g: Game, p: Player): boolean {
if (g.status !== 'playing' || g.advanced || g.expansions.length !== 1 ||
if (g.status !== 'playing' || g.advanced || g.expansions.length > 1 ||
g.inflation?.side === 'double' || g.techTokens || g.strongholdCards || g.leaderSkills ||
g.homeworlds || g.nexusCards || g.discoveryEnabled || g.sandtrout || g.coalitionState ||
g.ecazTreachery || g.semutaPreview || g.semutaRecruitsPreview || g.truthtrance || g.response ||
g.ecazTreachery || g.semutaPreview || g.semutaRecruitsPreview || g.truthtrance ||
g.pendingKarama || g.pendingKull || g.pendingTreacheryDiscard || g.pendingNullentropy ||
g.bureaucratPayments?.pending || g.battle || g.pendingRicheseGift || g.pendingShipment ||
g.pendingGuildTransport || g.pendingArrivalOverlap || g.pendingAmbassador || g.pendingEmperorAuctionGift ||
@@ -17356,9 +17356,41 @@ function basicBribeInterrupt(g: Game, p: Player): boolean {
liveRevivalExpressionPromises(g.revivalExpressionPromises ?? [], p.id, g.turn).length ||
liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length ||
livePhaseBidPromises(g.phaseBidPromises ?? [], p.id, g.turn).length ||
(g.bidPromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.earlyRicheseBidPromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.richeseBidPromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.charityPromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.alliancePromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.allianceExpressionPromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.pledgePromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
(g.deals ?? []).some(row => row.status === 'active' && row.payment?.payer === p.id && row.paymentStatus === 'pending')) return false;
const classic = ['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'];
if (!g.expansions.length) {
if (g.phaseOpening || !g.players.every(seat => classic.includes(seat.faction)) ||
Object.entries(g).some(([key, value]) => key.startsWith('pending') &&
(Array.isArray(value) ? value.length > 0 : value !== null && value !== undefined && value !== false))) return false;
if (g.decision?.kind === 'auctionPayment' && !g.response)
return g.phase === 3 && !!g.auction && !g.richeseAuction && !g.currentAuctionSale &&
g.auction.bidder === g.decision.player && !!g.auction.cards[g.auction.index];
const response = g.response;
if (!response || g.decision || !Array.isArray(response.passed) ||
new Set(response.passed).size !== response.passed.length ||
response.passed.some(id => !g.players.some(seat => seat.id === id))) return false;
const owner = g.players.find(seat => seat.id === response.owner);
const keys = Object.keys(response).sort().join(',');
if (response.kind === 'atreidesAuction') return keys === 'kind,owner,passed' && g.phase === 3 &&
owner?.faction === 'atreides' && !!g.auction && !g.richeseAuction && !g.currentAuctionSale &&
!!g.auction.cards[g.auction.index];
if (response.kind === 'fremenRevivalGrant') {
if (keys !== 'fremenGrant,kind,owner,passed,recipient' || g.phase !== 4 || owner?.faction !== 'fremen' ||
!response.fremenGrant || response.fremenGrant.owner !== response.owner ||
response.fremenGrant.recipient !== response.recipient) return false;
try { validateFremenRevivalGrant(g, response.fremenGrant); return true; }
catch (error) { if (error instanceof FremenRevivalGrantError) return false; throw error; }
}
return false;
}
if (g.response) return false;
if (g.expansions[0] === 'ix') return !!g.phaseOpening && !!g.amalWindowOpen && !g.decision &&
g.players.every(seat => [...classic, 'ixians', 'tleilaxu'].includes(seat.faction));
const extortion = g.moritaniExtortion, decision = g.decision;
+3 -3
View File
@@ -296,7 +296,7 @@ export const RULE_TOPICS: RuleTopic[] = [
steps: [
'Open Bribes beneath the table. Choose a non-allied faction and a positive whole amount, then use the named Pay button. The amount is public and leaves your supply immediately. A message or promise alone does not pay spice.',
'The recipient keeps paid spice separately until the next Mentat opening. A payment after the current opening waits for next turn’s opening. Your incoming bribes are shown separately from your spendable supply; they cannot fund bids, shipments or another bribe yet.',
'You may pay out of turn when no decision or response is pending, including during Mentat after opening collection. In supported Basic Ix-deck games, you can also pay during the Amal opening; in supported Basic Moritani games, you can pay during an Extortion choice. The original opening or choice remains pending. You cannot bribe yourself or your ally, or while Inflation shows Double. Emperor gifts to allies use their own power controls.',
'You may pay out of turn when no decision or response is pending, including during Mentat after opening collection. In supported Basic classic games, direct bribes can also interrupt a normal auction-payment decision, Atreides auction inspection or Fremen allied-revival grant. Supported Basic Ix and Moritani games also allow payments during the Amal opening or an Extortion choice. The original parent and its passes remain pending. You cannot bribe yourself or your ally, or while Inflation shows Double. Emperor gifts to allies use their own power controls.',
'Your payment limit excludes reserved spending and preserves binding Truthtrance answers. If an alliance, pending decision or available supply changes, recheck the recipient and amount. A rejected payment does not change the game.',
'In the Basic group preview, group allies are excluded from ordinary bribes and nonallied deals. Existing agreements remain recorded and can be released; a deferred bribe to a newly allied recipient stays pending while it is unavailable, subject to its original deadline.',
'A qualifying Bureaucrat may divert two spice to the Bank without reducing the payer’s cost. The remainder stays deferred. Unsupported combinations remain unavailable and do not imply an official rule prohibition.',
@@ -310,9 +310,9 @@ export const RULE_TOPICS: RuleTopic[] = [
related: ['table-discussion', 'alliance-funding', 'card-truthtrance', 'privacy'],
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'Public two-to-six-human offer, unanimous consent and mutual release are recorded. Immediate and deferred deal payments, including later deadlines, use the native bribe action; prose compliance remains player managed. Funding, escrow and Bureaucrat continuation remain native. Complete negotiation and all module combinations remain open.', evidence: ['game/public-deals.ts', 'game/public-deal-obligations.ts', 'game/bribe-options.ts', 'game/engine.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Public deal proposals, responses and mutual release, named bribe recipient, explicit payment, and a structured current-turn shipment or movement term with public status and an owner-only native-action guide.', evidence: ['components/public-deals.tsx', 'components/travel-promises.tsx', 'components/bribes.tsx'] },
{ area: 'Player controls', status: 'Partial', detail: 'Public deal proposals, responses and mutual release, named bribe recipient, explicit payment, and structured current- or later-turn travel with public status and an owner-only native-action guide.', evidence: ['components/public-deals.tsx', 'components/travel-promises.tsx', 'components/bribes.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Passive receipt/collection, Bureaucrat responses and legal autopilot continuation of a previously accepted travel term work. Strategic bribe initiation and negotiation wait for feature completion.' },
{ area: 'Documentation', status: 'Partial', detail: 'Public deal consent, structured travel and payment, player-managed terms, escrow, restrictions and remaining boundaries are recorded.', evidence: ['docs/PUBLIC_DEALS.md', 'docs/BRIBE_CONTROLS.md', 'docs/MENTAT_BRIBES.md', 'tests/public-deals-travel-runtime.test.ts', 'tests/public-deals-travel-controls.test.tsx'] },
{ area: 'Documentation', status: 'Partial', detail: 'Public deal consent, structured travel and payment, player-managed terms, escrow, restrictions and remaining boundaries are recorded.', evidence: ['docs/PUBLIC_DEALS.md', 'docs/BRIBE_CONTROLS.md', 'docs/MENTAT_BRIBES.md', 'tests/bribe-classic-auction-runtime.test.ts', 'tests/bribe-classic-response-runtime.test.ts', 'tests/bribe-classic-pending-controls.test.tsx', 'tests/public-deals-travel-runtime.test.ts', 'tests/public-deals-travel-controls.test.tsx'] },
{ area: 'Verification', status: 'Partial', detail: 'Supported Basic pending opening and Extortion payments retain their original parent and deferred collection; the actual opening and next-turn collection have focused runtime and rendered-control coverage. Public deal consent, release, immediate and deferred native payments, authenticated visibility and legal bot continuation have focused coverage, including staged group and legacy records. Group travel has actual multi-seat consent, physical travel and release coverage. Original late Mentat/next-turn receipt journeys supplement existing bribe projection, budgets and Bureaucrat coverage. Full integrated acceptance remains open.', evidence: ['tests/bribe-pending-basic.test.ts', 'tests/bribe-pending-basic-controls.test.tsx', 'tests/coalition-bribe-deals.test.ts', 'tests/coalition-bribe-truth.test.ts', 'tests/public-deals.test.ts', 'tests/public-deals-engine.test.ts', 'tests/public-deals-multiparty-engine.test.ts', 'tests/public-deals-group-travel-runtime.test.ts', 'tests/public-deals-payment-engine.test.ts', 'tests/public-deals-deferred-runtime.test.ts', 'tests/public-deals-deferred-truthtrance.test.ts', 'tests/public-deals-deferred-charity.test.ts', 'tests/public-deals-deferred-income.test.ts', 'tests/public-deals-deferred-http.test.ts', 'tests/public-deals-controls.test.tsx', 'tests/public-deals-http.test.ts', 'tests/public-deals-multiparty-http.test.ts', 'tests/public-deals-payment-http.test.ts', 'tests/mentat-bribes-runtime.test.ts', 'tests/bribe-controls.test.tsx', 'tests/bribe-controls-http.test.ts'] },
],
},