Support Truthtrance in ordered Charity
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
31c6e79c80
commit
68c7434a20
11 files changed
+425
-19
No files matched your search
+21
-8
@@ -17725,7 +17725,7 @@ function phaseBidWitnesses(g: Game, p: Player): PhaseBidOpportunity[] {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/** The first structured Charity question covers the live concurrent Basic phase.
|
||||
/** A structured Charity answer retains the recipient's native opportunity.
|
||||
* A detached native action quotes the real entitlement and payer; it never
|
||||
* commits a payment or opens a response at the table. */
|
||||
function charityPromiseRule<T>(run: () => T): T {
|
||||
@@ -17735,17 +17735,25 @@ function charityPromiseRule<T>(run: () => T): T {
|
||||
}
|
||||
}
|
||||
function charityQuestionWindow(g: Game): boolean {
|
||||
return g.status === 'playing' && !g.advanced && g.phase >= 0 && g.phase <= 2 && !g.charityOrder &&
|
||||
const timing = !g.advanced && g.phase >= 0 && g.phase <= 2 && !g.charityOrder ||
|
||||
g.advanced && g.phase === 2 && !!g.charityOrder;
|
||||
return g.status === 'playing' && timing &&
|
||||
!g.response && !g.decision && !g.phaseOpening && !g.pendingKarama && !g.pendingTreacheryDiscard &&
|
||||
(g.phase < 2 || !byFaction(g, 'choam') || g.choamCharity?.turn === g.turn);
|
||||
}
|
||||
function charityQuestionTarget(g: Game, player: Player): boolean {
|
||||
return player.charityTurn !== g.turn && (!g.charityOrder || g.charityOrder.remaining.includes(player.id));
|
||||
}
|
||||
function charityPromiseCompletion(g: Game, player: string): Action | null {
|
||||
if (!charityQuestionWindow(g) || getPlayer(g,player).charityTurn === g.turn) return null;
|
||||
if (!charityQuestionWindow(g) || !charityQuestionTarget(g,getPlayer(g,player))) return null;
|
||||
// In early phases the action is only a detached witness for the later
|
||||
// Charity opportunity. Never project it as a currently executable guide.
|
||||
const trial = g.phase < 2 ? charityForecastContext(g) : structuredClone(g);
|
||||
trial.truthtrance = null;
|
||||
trial.ready = trial.ready.filter(id => id !== player);
|
||||
// Quote this seat's eventual native claim without changing the live order.
|
||||
// The caller has already checked that its opportunity remains in the queue.
|
||||
if (trial.charityOrder) delete trial.charityOrder;
|
||||
// Either CHOAM opening outcome pays another faction's ordinary claim; use
|
||||
// the existing bank-fallback branch only on this detached quote. Its own
|
||||
// future opening income is uncertain and is handled separately below.
|
||||
@@ -17778,7 +17786,7 @@ function charityPromiseCanClaim(g: Game, player: string): boolean {
|
||||
return !!charityPromiseCompletion(g,player);
|
||||
}
|
||||
function charityTruthAnswers(g: Game, p: Player, turn: number): TruthAnswer[] {
|
||||
if (!charityQuestionWindow(g) || turn !== g.turn || p.charityTurn === turn) return [];
|
||||
if (!charityQuestionWindow(g) || turn !== g.turn || !charityQuestionTarget(g,p)) return [];
|
||||
if (richeseBidOtherPromises(g,p)) return ['unknown'];
|
||||
// CHOAM's own unsettled opening changes its purse and depends on another
|
||||
// player's cancellation. No remains an owned choice; do not invent Yes.
|
||||
@@ -17792,7 +17800,7 @@ function bindCharityTruth(g: Game,p: Player,turn: number,answer: boolean,asker:
|
||||
g.charityPromises = charityPromiseRule(() => bindCharityPromise(g.charityPromises ?? [],p.id,asker,turn,answer,charityPromiseCanClaim(g,p.id)));
|
||||
// Only the actual concurrent Charity phase reopens Ready; Storm/Spice
|
||||
// readiness belongs to the original phase and must remain intact.
|
||||
if (g.phase === 2) g.ready = g.ready.filter(id => id !== p.id);
|
||||
if (g.phase === 2 && !g.charityOrder) g.ready = g.ready.filter(id => id !== p.id);
|
||||
}
|
||||
function reconcileCharityTruth(g: Game,voluntary?: {actor:string;action:Action}): void {
|
||||
if (!g.charityPromises?.length || g.truthtrance || g.response || g.decision || g.phaseOpening ||
|
||||
@@ -17803,7 +17811,9 @@ function reconcileCharityTruth(g: Game,voluntary?: {actor:string;action:Action})
|
||||
for (const player of people) {
|
||||
const rows: CharityPromise[] = promises.filter(row => row.player === player && !row.fulfilled && !row.released);
|
||||
for (const turn of new Set(rows.map(row => row.turn))) {
|
||||
const open = g.status === 'playing' && !g.advanced && g.phase >= 0 && g.phase <= 2 && g.turn === turn;
|
||||
const open = g.status === 'playing' && g.turn === turn &&
|
||||
(!g.advanced && g.phase >= 0 && g.phase <= 2 ||
|
||||
g.advanced && g.phase === 2 && !!g.charityOrder?.remaining.includes(player));
|
||||
const before: CharityPromise[] = promises;
|
||||
promises = charityPromiseRule(() => reconcileCharityPromises(before,player,turn,
|
||||
{phaseOpen:open,canClaim:open && charityPromiseCanClaim(g,player),
|
||||
@@ -17815,7 +17825,8 @@ function reconcileCharityTruth(g: Game,voluntary?: {actor:string;action:Action})
|
||||
g.charityPromises = promises;
|
||||
}
|
||||
function projectedCharityTruthGuide(g: Game,player: string): {action:Action} | null {
|
||||
if (g.phase !== 2 || g.truthtrance || !charityQuestionWindow(g) || g.ready.includes(player)) return null;
|
||||
if (g.phase !== 2 || g.truthtrance || !charityQuestionWindow(g) || g.ready.includes(player) ||
|
||||
g.charityOrder && g.charityOrder.remaining[0] !== player) return null;
|
||||
const promise = liveCharityPromises(g.charityPromises ?? [],player,g.turn).find(row => row.answer);
|
||||
const action = promise && charityPromiseCompletion(g,player);
|
||||
return action && promise ? {action:{...action,charityPromiseEvent:promise.event,charityPromiseTurn:promise.turn}} : null;
|
||||
@@ -30385,11 +30396,13 @@ function guardCharityOrderFinish(g: Game, player: string, frame: CharityOrder, a
|
||||
closed.ready.push(player);
|
||||
closed.active = frame.remaining[0] ?? null;
|
||||
const voluntary = { actor: player, action };
|
||||
reconcileCharityTruth(closed, voluntary);
|
||||
reconcileTravelPromises(closed, player, action, g.phase, g);
|
||||
reconcileRevivalPromises(closed, voluntary);
|
||||
reconcileBribePromises(closed, voluntary);
|
||||
reconcileShipmentPromises(closed, voluntary);
|
||||
reconcileGroundMovementPromises(closed, player, action, g.phase);
|
||||
if (closed.charityPromises) g.charityPromises = closed.charityPromises;
|
||||
}
|
||||
function revivalOrderRule<T>(calculate: () => T): T {
|
||||
try { return calculate(); }
|
||||
@@ -48138,7 +48151,7 @@ export function viewGame(state: Game, id: string, now?: number) {
|
||||
}).map(player => player.id) : [],
|
||||
truthBidAnswers: g.truthtrance?.stage === 'answer' && g.truthtrance.question?.target === id &&
|
||||
g.truthtrance.question.kind === 'bid' ? bidTruthAnswers(g, me, g.truthtrance.question) : null,
|
||||
truthCharityTargets: charityQuestionWindow(g) ? g.players.filter(player => player.charityTurn !== g.turn).map(player => player.id) : [],
|
||||
truthCharityTargets: charityQuestionWindow(g) ? g.players.filter(player => charityQuestionTarget(g,player)).map(player => player.id) : [],
|
||||
truthCharityAnswers: g.truthtrance?.stage === 'answer' && g.truthtrance.question?.target === id &&
|
||||
g.truthtrance.question.kind === 'charity' ? charityTruthAnswers(g,me,g.truthtrance.question.turn) : null,
|
||||
truthCharityPromises: (g.charityPromises ?? []).filter(row => row.player === id).map(row => ({...row})),
|
||||
|
||||
Reference in new issue
Block a user