From 7fc21113e3e89651b5b61b25b9a2b7698347ca01 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:09:17 +1100 Subject: [PATCH] Extend Truthtrance revival promises through earlier turn phases --- components/revival-promises.tsx | 50 +++-- components/truthtrance.tsx | 5 +- docs/CURRENT_STATUS.md | 36 ++++ docs/IMPLEMENTATION_STATUS.md | 35 ++++ docs/RULE_DECISIONS.md | 16 ++ docs/TRUTHTRANCE_REVIVAL_PROMISES.md | 66 +++++++ game/engine.ts | 110 ++++++++--- game/reference.ts | 14 +- game/truthtrance.ts | 8 +- tests/fixture-truth-revival-early.ts | 124 ++++++++++++ tests/fixture-truth-revival.ts | 7 +- tests/truth-revival-controls.test.tsx | 4 +- tests/truth-revival-early-controls.test.tsx | 178 +++++++++++++++++ tests/truth-revival-early-runtime.test.ts | 209 ++++++++++++++++++++ 14 files changed, 810 insertions(+), 52 deletions(-) create mode 100644 tests/fixture-truth-revival-early.ts create mode 100644 tests/truth-revival-early-controls.test.tsx create mode 100644 tests/truth-revival-early-runtime.test.ts diff --git a/components/revival-promises.tsx b/components/revival-promises.tsx index b012469b..6a9a1193 100644 --- a/components/revival-promises.tsx +++ b/components/revival-promises.tsx @@ -6,29 +6,49 @@ import { Button } from './ui/button'; type RevivalPromiseProps = { game: GameView; busy: boolean; send: (action: Action) => void }; -/** Preserve the server's exact native return/Ready quote. The client checks - * only the live personalized event and current owned action opportunity. */ +/** Preserve the server's exact preparation or native return/Ready quote. The + * client checks only its live event and current owned action opportunity. */ export function RevivalPromiseGuideButton(props: RevivalPromiseProps) { const game = props.game, guide = game.revivalPromiseGuide; if (!guide) return null; - const actor = game.me, turn = game.turn, order = game.revivalOrder; + const actor = game.me, turn = game.turn, phase = guide.phase, order = game.revivalOrder; const action = guide.action, event = action.revivalPromiseEvent, signature = JSON.stringify(action); const canAct = () => { const current = props.game, me = current.players.find(player => player.id === current.me); + const payment = phase === 3 && action.type === 'decision' && + current.decision?.kind === 'auctionPayment' && current.decision.player === actor; + const native = current.richeseAuction; + const nativePass = phase === 3 && action.type === 'richeseBid' && native && + native.event === action.event && !native.outcome && native.eligible.includes(actor) && + (native.method === 'silent' ? !native.submitted.includes(actor) && action.amount === 0 + : native.active === actor && action.amount === null) && action.allyPayment === 0; + const opportunity = phase === 2 + ? !current.ready.includes(actor) && (action.type === 'charity' || action.type === 'ready') + : phase === 3 + ? payment || nativePass || action.type === 'passBid' && !native && current.auction?.active === actor + : phase === 4 && !current.ready.includes(actor) && (action.type === 'revive' || action.type === 'ready') && + current.revivalOrder === order && (!current.revivalOrder || current.revivalOrder.turn === turn && + current.revivalOrder.remaining[0] === actor && current.active === actor); return !props.busy && current === game && revivalPromiseModeSupported(current) && - current.status === 'playing' && current.phase === 4 && current.me === actor && current.turn === turn && - !current.ready.includes(actor) && current.revivalOrder === order && - (!current.revivalOrder || current.revivalOrder.turn === turn && current.revivalOrder.remaining[0] === actor && current.active === actor) && - current.revivalPromiseGuide === guide && current.revivalPromiseGuide.action === action && JSON.stringify(action) === signature && - typeof event === 'string' && (action.type === 'revive' || action.type === 'ready') && + current.status === 'playing' && current.phase === phase && current.me === actor && current.turn === turn && opportunity && + current.revivalPromiseGuide === guide && current.revivalPromiseGuide.phase === phase && + current.revivalPromiseGuide.action === action && JSON.stringify(action) === signature && typeof event === 'string' && liveRevivalPromises(current.revivalPromises, actor, turn).some(promise => promise.event === event) && !!me && !me.autopilot && - !current.truthtrance && !current.response && !current.decision && !current.phaseOpening && + !current.truthtrance && !current.response && (!current.decision || payment) && !current.phaseOpening && !current.automaticContinuationPending && !current.roomControl?.paused && !current.roomControl?.closed; }; return ; } @@ -57,7 +77,13 @@ export function RevivalPromises(props: RevivalPromiseProps) { Original revival costs, limits and turn order still apply. Optional modules remain outside this commitment. {' '}Revival promise rules.

- {waiting &&

Your Revival opportunity is still ahead; this promise remains visible while the current actor finishes.

} + {props.game.phase < 4 &&

+ A current preparation may claim Charity, pass an auction bid, or complete an already committed auction payment. + These steps preserve your later Revival; they do not count as returned forces. +

} + {waiting &&

Your Revival opportunity is still ahead; {props.game.phase < 4 + ? 'continue the current phase and keep the required spice and forces available.' + : 'this promise remains visible while the current actor finishes.'}

} ; } diff --git a/components/truthtrance.tsx b/components/truthtrance.tsx index 1a66f0d1..f75085b8 100644 --- a/components/truthtrance.tsx +++ b/components/truthtrance.tsx @@ -342,7 +342,7 @@ export function Truthtrance({ Verified current facts - +