Support Truthtrance bribes in Basic Kull preview

This commit is contained in:
Quintine committed 2026-10-27 19:37:38 +11:00
1 parent 92c68ec4f6
commit 21f95bdd5b
15 files changed
+532 -15

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ export interface BribeOptions {
export function bribeTimingBlock(g: Game): string | null {
if (g.status !== 'playing') return 'Bribes require a game in progress.';
if (g.inflation?.side === 'double') return 'Bribes are prohibited while Inflation shows Double.';
if (g.pendingTreacheryDiscard || g.pendingNullentropy || g.bureaucratPayments?.pending ||
if (g.pendingKull || g.pendingTreacheryDiscard || g.pendingNullentropy || g.bureaucratPayments?.pending ||
['name', 'reveal'].includes(g.battle?.mentatQuestion?.stage ?? '') ||
g.nexusCards?.phase?.stage === 'drawing' || g.truthtrance ||
g.nexusTraitorExchanges?.some(exchange => exchange.stage === 'return') ||
+21 -5
View File
@@ -1,4 +1,5 @@
import { nativePromiseScopeSupported, type NativePromiseScope } from './native-promise-scope';
import { FACTIONS } from './catalog';
export type BribeClaim = { turn: number; recipient: string; minimum: number };
export type BribePromise = BribeClaim & {
@@ -35,14 +36,29 @@ export function parseBribeClaim(value: unknown): BribeClaim {
return { turn: input.turn, recipient: input.recipient, minimum: input.minimum };
}
/** Permission for the existing bare native context, not a game-start gate.
* The engine retains nonally targets, timing and ordinary payment authority. */
/** The ordinary, grouped and mixed bribe families share this admission. The
* printed Kull extension is deliberately limited to the existing fresh Basic
* CHOAM/classic roster; other previews keep their own composition boundaries.
* This quote grants no start permission or future unowned funds. */
export function bribePromiseModeSupported(game: NativePromiseScope & {
semutaPreview?: unknown; kullPreview?: unknown; nexusKullPreview?: unknown;
richeseBetrayalPreview?: unknown;
richeseBetrayalPreview?: unknown; guildBetrayalPreview?: unknown;
nexusIxianReplacementPreview?: unknown; nexusIxianBetrayalPreview?: unknown;
nexusHarkonnenBetrayalPreview?: unknown; moritaniAssassinatePreview?: unknown;
mobileStronghold?: unknown;
}): boolean {
return nativePromiseScopeSupported(game) && !game.semutaPreview && !game.kullPreview &&
!game.nexusKullPreview && !game.richeseBetrayalPreview;
if (!nativePromiseScopeSupported(game) || game.semutaPreview || game.nexusKullPreview ||
game.richeseBetrayalPreview) return false;
if (!game.kullPreview) return true;
return game.kullPreview === true && game.advanced === false && game.expansions.length === 2 &&
game.expansions.includes('choam') && game.expansions.includes('ix') &&
game.players.some(player => player.faction === 'choam') &&
new Set(game.players.map(player => player.faction)).size === game.players.length &&
game.players.every(player => player.faction === 'choam' ||
FACTIONS.some(faction => faction.id === player.faction && faction.expansion === 'base')) &&
!game.guildBetrayalPreview && !game.nexusIxianReplacementPreview &&
!game.nexusIxianBetrayalPreview && !game.nexusHarkonnenBetrayalPreview &&
!game.moritaniAssassinatePreview && !game.mobileStronghold;
}
export function bribeClaimText(claim: BribeClaim, recipientName: (id: string) => string = id => id): string {
+18 -3
View File
@@ -1542,6 +1542,8 @@ export type Game = {
form: 'printed' | 'substitution';
/** New Advanced attempts restrict this effect; absent preserves original phase scope. */
purpose?: KullPurpose;
/** New Basic Kull attempts may bind an existing structured bribe ledger. */
bribeReceipt?: true;
stage: 'offer' | 'counter';
selection?: { source: 'printed' | 'nexus'; card: string; nexusEvent?: string };
intent: { kind: 'ordinary'; use: KaramaUse } | { kind: 'special'; use: SpecialKaramaIntent };
@@ -15658,10 +15660,11 @@ function forecastOrdinaryCharity(source: Game, owner: string): Game | null {
const player = getPlayer(g, owner);
if (charityQuote(g, player).total <= 0) return null;
try {
if (source.phase < 2 && byFaction(g, 'choam') && g.choamCharity?.turn !== g.turn) {
if (source.phase <= 2 && byFaction(g, 'choam') && g.choamCharity?.turn !== g.turn) {
if (player.faction === 'choam') return null;
// Another faction's ordinary entitlement survives either CHOAM opening
// outcome. Quote the beneficiary only, without choosing that response.
// outcome, including the phase-two opening before its response is
// settled. Quote the beneficiary only, without choosing that response.
player.spice += charityQuote(g, player).total; player.charityTurn = g.turn;
return g;
}
@@ -15849,7 +15852,11 @@ function reconcileBribePromises(g: Game, voluntary?: { actor: string; action: Ac
const pending = g.response || g.decision || g.phaseOpening || g.pendingTreacheryDiscard ||
g.pendingKarama || g.pendingKull || g.pendingRicheseBetrayal;
if (pending && voluntary) {
const future = structuredClone(g); future.truthtrance = null;
// Quote after the original Kull response on a detached source. Leaving
// its frame installed would dispatch the simulated Charity as a Kull
// decision instead of evaluating the payer's real future entitlement.
const future = structuredClone(g.pendingKull ? kullSource(g, g.pendingKull) : g);
future.truthtrance = null;
if (future.phaseOpening) projectTurnActionOpeningEnd(future);
future.response = null; future.decision = null; future.pendingKarama = null; future.pendingTreacheryDiscard = null;
let affordable = false;
@@ -18076,6 +18083,9 @@ function kullSignature(g: Game, frame: KullFrame): string {
karamaShipping: source.karamaShipping, spiceWindow: source.spiceWindow,
spiceResolution: source.spiceResolution, spiceSequence: source.spiceSequence,
stormResolution: source.stormResolution, worthless: frame.worthless,
...(frame.bribeReceipt ? {bribeReceipt:frame.bribeReceipt,bribePromises:source.bribePromises,
bribeExpressionPromises:source.bribeExpressionPromises,bribePromiseSequence:source.bribePromiseSequence,
mixedBribeQuestions:source.mixedBribeQuestions} : {}),
});
}
/** Only the verified pre-source printed profile may bind its old held cost. */
@@ -18120,6 +18130,9 @@ function kullIntegrity(g: Game): void {
return;
}
requireRule(g.kullPreview === true, 'This table has not opted into the Kull preview.');
requireRule(frame.bribeReceipt === undefined || frame.bribeReceipt === true && !g.advanced &&
bribePromiseModeSupported(g) && !!(g.bribePromises?.length || g.bribeExpressionPromises?.length || g.mixedBribeQuestions?.length),
'The Kull attempt has lost its original Basic bribe-answer ledger.');
validateKullAttemptStamp(g, frame, physicalTreacheryCards(g));
requireRule(Number.isSafeInteger(g.kullSequence) && g.kullSequence! > 0 &&
frame.event === JSON.stringify(['kull', g.turn, g.phase, g.kullSequence, frame.player, frame.owner]),
@@ -18200,6 +18213,8 @@ function beginKullAttempt(g: Game, p: Player, card: Card, intent: KullFrame['int
turn: g.turn, phase: g.phase, form: card.kind === 'worthless' ? 'substitution' : 'printed',
stage: 'offer', intent: structuredClone(intent), resume: structuredClone(resume),
...(g.advanced ? { purpose: kullIntentPurpose(g, intent) } : {}),
...(!g.advanced && (g.bribePromises?.length || g.bribeExpressionPromises?.length || g.mixedBribeQuestions?.length)
? { bribeReceipt: true as const } : {}),
worthless: structuredClone(g.pendingChoamWorthless ?? null), signature: '',
};
// Payment dispatch removed this control before entering its native branch.
+3 -2
View File
@@ -4779,6 +4779,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'React before a Karama is played; a distinct counter can stop Kull before its phase restriction begins.',
steps: [
'In the lobby, the host can opt into this Basic development preview with CHOAM and classic factions, the CHOAM and Ix Treachery decks, and no other optional modules. It uses printed ordinary Karama. The preview withholds some structured Truthtrance question types and defers unfunded non-CHOAM Karama-dependent bids; leave it off to retain the ordinary current game. A separate Advanced development profile supports Bene Gesserit substitutions and legal special powers. Existing games are not retrofitted, and the warning is not rules-completion approval.',
'In the supported Basic printed Kull preview, ordinary, grouped and mixed Truthtrance bribe questions remain available. A pending Kull response temporarily holds bribe controls; its real resolution resumes the existing commitment. Other structured question types remain incomplete in this preview.',
'Another player’s legal Karama cancellation, shipment-rate use, immediate auction purchase, winning-bid payment or supported special activation pauses before its card and other costs. CHOAM sees the attempted player and purpose and may decline or choose a legal held Kull Wahad. Each Use names its printed source explicitly. The separate Nexus Kull guide covers additional Cunning costs in its new profile only. The same public opportunity appears without Kull in hand, so waiting does not disclose hidden ownership. Invalid attempts never open this window; CHOAM does not receive a self-targeted Kull offer.',
'Decline resumes the original attempt once. Declaring Kull opens the ordinary CHOAM-effect response. Kull is still pending: its target may counter with a different eligible physical Karama before any phase ban applies. The interrupted card stays reserved and cannot serve as its own counter. Other eligible responders use their existing response controls.',
'If everyone allows Kull, CHOAM discards the exact Kull card. The original attempted card remains in its owner’s hand; its effect and additional costs do not occur, and an attempted special once-per-game use remains unspent. Basic prevents that player’s Karama activations for this phase. A new Advanced attempt instead prevents only the specific effect: shipment pricing, free Treachery purchase, that faction’s special, or cancellation of the particular advantage. Other effects remain available; printed and substituted Karama share the same restriction. An allied shipment recipient is not automatically the restricted player.',
@@ -4792,11 +4793,11 @@ export const RULE_TOPICS: RuleTopic[] = [
example: 'Bene Gesserit attempts to use Baliset as Karama. CHOAM declares Kull. Baliset cannot also counter Kull; a different eligible Karama may. If Kull succeeds, Baliset stays held; in a new Advanced attempt BG cannot repeat that specific Karama effect this phase but may use another legal effect. If a distinct counter prevents Kull, the original Baliset attempt proceeds to its normal conversion response.',
related: ['choam-worthless', 'nexus-choam-kull', 'faction-choam', 'card-karama', 'advanced-beneGesserit', 'special-karama', 'bidding', 'choam-karama', 'nexus-cards', 'implementation-checklist'],
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'Opt-in Kull binds typed intents, distinct physical counters and Basic/legacy all-purpose or new Advanced semantic-effect restrictions. Different effects remain playable; phase expiry is provisional in Advanced. Deferred overbid recovery and combined-module support remain excluded.', evidence: ['game/choam-kull.ts', 'game/kull-purpose.ts', 'game/engine.ts'] },
{ area: 'Implementation', status: 'Partial', detail: 'Opt-in Kull binds typed intents, distinct physical counters and Basic/legacy all-purpose or new Advanced semantic-effect restrictions. Different effects remain playable; phase expiry is provisional in Advanced. Basic printed Kull also retains structured bribe promises through its response; deferred overbid recovery and broader module support remain excluded.', evidence: ['game/choam-kull.ts', 'game/kull-purpose.ts', 'game/bribe-promises.ts', 'game/engine.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Owner-only legal held costs and decline use the Kull event; counters reuse ordinary response controls. Public viewers receive purpose/actors, not private intent payloads or hidden choices.', evidence: ['components/choam-kull.tsx', 'components/game-table.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Legal preview participation uses projected reactor choices and ordinary distinct counter controls. Full strategy and difficulty calibration remain deferred.', evidence: ['game/bot-choam-kull.ts', 'game/bots.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'Pending versus active restrictions, distinct custody, decline/prevention/success, BG-before-conversion and deferred overbid limits have internal explanations. Publisher-source and full-faction acceptance remain separate.', evidence: ['docs/CHOAM_KULL_DESIGN.md', 'docs/CHOAM_KULL_SOURCE_UPDATE.md', 'docs/NEXUS_CHOAM_RULES.md'] },
{ area: 'Verification', status: 'Partial', detail: '18 October Advanced scope:12-file111/111, types/lint/build and two genuine-action/rendered-table programmes pass. Selected unopened deal/auction/Spice positions yield real Baliset purchase, paid shipments, repeated-effect rejection and a different-effect cancellation; Basic retains broad scope. Four policies legally pass. Controlled intent tests cover shared auction/special variants and unstamped frames. Earlier SQLite/phone evidence concerns the old scope, not current recovery/browser certification. Original Nexus/alias programmes, wider combinations, final assurance and deployed acceptance remain open.', evidence: ['tests/kull-purpose.test.ts', 'tests/kull-purpose-intents.test.ts', 'tests/kull-purpose-engine.test.ts', 'tests/fixture-kull-purpose.ts', 'tests/choam-kull-engine.test.ts', 'tests/choam-kull-recovery.test.ts', 'tests/choam-kull-controls.test.tsx', 'tests/bot-choam-kull.test.ts', 'tests/choam-kull.test.ts', 'tests/prototype-room.test.ts'] },
{ area: 'Verification', status: 'Partial', detail: '18 October Advanced scope:12-file111/111, types/lint/build and two genuine-action/rendered-table programmes pass. Selected unopened deal/auction/Spice positions yield real Baliset purchase, paid shipments, repeated-effect rejection and a different-effect cancellation; Basic retains broad scope. Four policies legally pass. Controlled intent tests cover shared auction/special variants and unstamped frames. Earlier SQLite/phone evidence concerns the old scope, not current recovery/browser certification. Original Nexus/alias programmes, wider combinations, final assurance and deployed acceptance remain open.', evidence: ['tests/kull-purpose.test.ts', 'tests/kull-purpose-intents.test.ts', 'tests/kull-purpose-engine.test.ts', 'tests/fixture-kull-purpose.ts', 'tests/choam-kull-engine.test.ts', 'tests/choam-kull-recovery.test.ts', 'tests/choam-kull-controls.test.tsx', 'tests/bot-choam-kull.test.ts', 'tests/choam-kull.test.ts', 'tests/kull-bribe-runtime.test.ts', 'tests/kull-bribe-controls.test.tsx', 'tests/prototype-room.test.ts'] },
],
},
{