Connect printed flight and poison plays to Truthtrance
This commit is contained in:
1 parent
b02839ce2a
commit
6a0fba860d
17 files changed
+963
-41
No files matched your search
+54
-14
@@ -18375,6 +18375,8 @@ function ornithopterBlock(
|
||||
return 'Use Ornithopter during your own Shipment and Movement turn.';
|
||||
if (g.truthtrance || g.response || g.decision || g.phaseOpening)
|
||||
return 'Resolve the current interaction before declaring a movement.';
|
||||
const promised = printedRolePlayBlock(g, p.id, 'ornithopter');
|
||||
if (promised) return promised;
|
||||
if (g.ornithopter)
|
||||
return 'Finish the already played Ornithopter movement first.';
|
||||
if (p.moved >= movesAllowed(g,p))
|
||||
@@ -19064,9 +19066,21 @@ function turnActionCardsMatch(g: Game, actor: string, requirements: readonly Tur
|
||||
}
|
||||
function* nativePrintedCardActions(g: Game, actor: string, effect: PrintedCardEffect): Generator<Action> {
|
||||
const player = getPlayer(g, actor);
|
||||
// Semuta belongs only to its exact fresh-discard window, projected before
|
||||
// ordinary payment/card preparation rather than as a generic card action.
|
||||
if (effect === 'semutaDrug') return;
|
||||
// Semuta owns a fresh-discard window; Ornithopter uses real movement
|
||||
// declarations. Neither is a generic card-button preparation.
|
||||
if (effect === 'semutaDrug' || effect === 'ornithopter') return;
|
||||
if (effect === 'residualPoison') {
|
||||
const battle = g.battle;
|
||||
if (!battle?.event || ![battle.attacker, battle.defender].includes(actor)) return;
|
||||
const target = battle.attacker === actor ? battle.defender : battle.attacker;
|
||||
for (const card of player.hand.filter(card => card.effect === effect)) {
|
||||
try {
|
||||
validateResidualPoison(g, player, card.id, target, battle.event);
|
||||
yield { type: 'card', card: card.id, target, event: battle.event };
|
||||
} catch (error) { if (!(error instanceof RuleError)) throw error; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (effect === 'distrans') {
|
||||
const quote = distransView(g, player);
|
||||
if (quote && !quote.blocked) for (const choice of quote.choices) if (!choice.blocked)
|
||||
@@ -19129,17 +19143,19 @@ function guardTurnActionBoxCost(source: Game, owner: Player, box: string) {
|
||||
for (const seed of turnActionBoxSeeds(source, owner.id, box)) if (turnActionCompletion(seed.game, owner.id)) return;
|
||||
throw new RuleError('The two-spice Box payment must preserve your accepted whole Truthtrance answers.');
|
||||
}
|
||||
/** Native transfer/order effects retain their physical cost and public queue.
|
||||
* No recipient consent, unseen acquisition or income is assumed. */
|
||||
/** Native transfer/order and pre-leader costs retain their physical cards and
|
||||
* queues. No unseen acquisition, random casualty or income is assumed. */
|
||||
function* turnActionTransferOrderSeeds(source: Game, actor: string, requirements: readonly TurnActionCardRequirement[]):
|
||||
Generator<{ game: Game; actions: Action[] }> {
|
||||
const search = function* (game: Game, actions: Action[]): Generator<{ game: Game; actions: Action[] }> {
|
||||
const pending = [...new Set(requirements.filter(required => required.answer &&
|
||||
(required.effect === 'distrans' || required.effect === 'juiceOfSapho') &&
|
||||
(required.effect === 'distrans' || required.effect === 'juiceOfSapho' || required.effect === 'residualPoison') &&
|
||||
nativePrintedCardCount(game, actor, required.effect) <= required.baseline).map(required => required.effect))];
|
||||
for (const effect of pending) for (const action of nativePrintedCardActions(game, actor, effect)) {
|
||||
try {
|
||||
const played = applyActionInner(game, actor, action, 'movementPreparation');
|
||||
const played = effect === 'residualPoison'
|
||||
? projectResidualPoisonPrintedCost(game, actor, String(action.card))
|
||||
: applyActionInner(game, actor, action, 'movementPreparation');
|
||||
const active = played.active, extortion = played.decision?.kind === 'moritaniExtortion' ? played.decision : null;
|
||||
const projected = groundMovementProbe(played, actor);
|
||||
projected.active = active;
|
||||
@@ -19198,7 +19214,7 @@ function* turnActionOwnedCardSeeds(source: Game, actor: string, requirements: re
|
||||
function* turnActionCardSeeds(source: Game, actor: string, requirements: readonly TurnActionCardRequirement[]):
|
||||
Generator<{ game: Game; actions: Action[] }> {
|
||||
if (!requirements.some(required => required.answer &&
|
||||
(required.effect === 'distrans' || required.effect === 'juiceOfSapho') &&
|
||||
(required.effect === 'distrans' || required.effect === 'juiceOfSapho' || required.effect === 'residualPoison') &&
|
||||
nativePrintedCardCount(source, actor, required.effect) <= required.baseline)) {
|
||||
yield* turnActionOwnedCardSeeds(source, actor, requirements);
|
||||
return;
|
||||
@@ -20155,7 +20171,7 @@ function turnActionPromise(g: Game, p: Player, claim: TurnActionClaim, answer: b
|
||||
...(printedCards.size ? { cardPlays: { hajr: printedCards.has('hajr') ? { played: false, closed: !open } : null,
|
||||
ghola: printedCards.has('ghola') ? { played: false, closed: false } : null,
|
||||
...Object.fromEntries([...printedCards].filter(effect => effect !== 'hajr' && effect !== 'ghola')
|
||||
.map(effect => [effect, { played: false, closed: false }])) } } : {}),
|
||||
.map(effect => [effect, { played: false, closed: effect === 'ornithopter' && !open }])) } } : {}),
|
||||
...(combat ? { combat } : {}),
|
||||
...(preparation ? { preparation } : {}),
|
||||
...(nexus ? { alliance: { source: nexus.source, partner: null, closed: false } } : {}),
|
||||
@@ -20398,14 +20414,16 @@ function projectedTurnActionGuide(g: Game, player: Player): { event: string; act
|
||||
if (!action) return null;
|
||||
// The joint quotation can prepare a later answer while an earlier unrelated
|
||||
// answer remains live. Window controls must bind the row naming that source.
|
||||
const held = action.type === 'card' ? player.hand.find(card => card.id === action.card) : undefined;
|
||||
const held = action.type === 'card' ? player.hand.find(card => card.id === action.card)
|
||||
: action.type === 'move' ? player.hand.find(card => card.id === action.movementCard) : undefined;
|
||||
const printedWindow = held?.kind === 'special' && held.effect === 'karama' && action.mode === 'purchase'
|
||||
? 'karamaAuction' : held?.kind === 'special' && held.effect === 'karama' && action.mode === 'shipment'
|
||||
? 'karamaShipment' : isWindowCardEffect(held?.effect) ? held.effect : null;
|
||||
const windows: WindowCardEffect[] = printedWindow ? [printedWindow]
|
||||
: action.type === 'decision' && action.karama === true ? ['karamaAuction']
|
||||
: action.type === 'ready' || held?.effect === 'nullentropyBox' ? [...WINDOW_CARD_EFFECTS] : [];
|
||||
const sideRole = held?.effect === 'distrans' || held?.effect === 'juiceOfSapho' ? held.effect : null;
|
||||
const sideRole = held?.effect === 'distrans' || held?.effect === 'juiceOfSapho' ||
|
||||
held?.effect === 'residualPoison' || held?.effect === 'ornithopter' ? held.effect : null;
|
||||
const guidePromise = sideRole ? liveTurnActionPromises(g.turnActionPromises ?? [], player.id, g.turn).find(row =>
|
||||
row.cardPlays?.[sideRole] && !row.cardPlays[sideRole]!.played && !row.cardPlays[sideRole]!.closed) ?? promise
|
||||
: windows.length ? liveTurnActionPromises(g.turnActionPromises ?? [], player.id, g.turn).find(row =>
|
||||
@@ -20423,7 +20441,7 @@ function projectedTurnActionGuide(g: Game, player: Player): { event: string; act
|
||||
function turnActionCurrentPrintedPreparation(g: Game, player: Player, action: Action): boolean {
|
||||
if (action.type !== 'card') return true;
|
||||
const card = player.hand.find(card => card.id === action.card);
|
||||
if (card?.effect !== 'distrans' && card?.effect !== 'juiceOfSapho') return true;
|
||||
if (card?.effect !== 'distrans' && card?.effect !== 'juiceOfSapho' && card?.effect !== 'residualPoison') return true;
|
||||
return [...nativePrintedCardActions(g, player.id, card.effect)].some(choice =>
|
||||
['type', 'card', 'target', 'give', 'scope', 'event', 'mode'].every(key => choice[key] === action[key]));
|
||||
}
|
||||
@@ -20685,8 +20703,10 @@ function groundMovementCompletionAtMovement(game: Game, actor: string, promises:
|
||||
.every(promise => matchesShipment(promise, null) === promise.answer);
|
||||
},
|
||||
needsProgress(state, owner) {
|
||||
return state.advanced && !hajrAvailable(state, getPlayer(state, owner)) && cardPlays.some(required =>
|
||||
required.effect === 'hajr' && required.answer && nativePrintedCardCount(state, owner, 'hajr') <= required.baseline);
|
||||
return cardPlays.some(required => required.answer &&
|
||||
nativePrintedCardCount(state, owner, required.effect) <= required.baseline &&
|
||||
(required.effect === 'ornithopter' || required.effect === 'hajr' && state.advanced &&
|
||||
!hajrAvailable(state, getPlayer(state, owner))));
|
||||
},
|
||||
progressKey(state, owner) {
|
||||
return cardPlays.map(required => [required.effect, nativePrintedCardCount(state, owner, required.effect) > required.baseline]);
|
||||
@@ -21182,6 +21202,7 @@ function declareGroundMovement(g: Game, p: Player, action: Action, quoteOnly = f
|
||||
: `That destination is blocked or more than ${speed} territories away.`,
|
||||
);
|
||||
validateFlightSelection(g, p, move);
|
||||
if (action.movementCard !== undefined) recordNativePrintedCardPlay(g, id, 'ornithopter');
|
||||
const matching = quoteMatchingMovement(g, move);
|
||||
const gatherMatching=quoteGatherMatching(g,move);
|
||||
if (quoteOnly) return move;
|
||||
@@ -27886,6 +27907,8 @@ function validateResidualPoison(
|
||||
);
|
||||
const reserved = transferCardBlock(g, p, card);
|
||||
requireRule(!reserved, reserved ?? 'The card is committed.');
|
||||
const promised = printedRolePlayBlock(g, p.id, 'residualPoison');
|
||||
requireRule(!promised, promised ?? 'Retain your whole Truthtrance answer.');
|
||||
const target = getPlayer(g, targetId),
|
||||
controlled = controlledLeaders(g, target);
|
||||
const candidates = noFieldRule(() =>
|
||||
@@ -27904,6 +27927,15 @@ function validateResidualPoison(
|
||||
});
|
||||
return { b, card, target, candidates };
|
||||
}
|
||||
/** A private completion pays the printed cost without drawing a random victim
|
||||
* or borrowing a hypothetical casualty for later Ghola/revival preparation. */
|
||||
function projectResidualPoisonPrintedCost(source: Game, actor: string, card: string): Game {
|
||||
const game = structuredClone(source), player = getPlayer(game, actor);
|
||||
const used = discard(game, player, card);
|
||||
recordNativePrintedCardPlay(game, actor, 'residualPoison');
|
||||
rememberMovementDiscards(game, [{ card: used, discardedBy: actor, publicFace: true }]);
|
||||
return game;
|
||||
}
|
||||
function playResidualPoison(
|
||||
g: Game,
|
||||
p: Player,
|
||||
@@ -27918,6 +27950,12 @@ function playResidualPoison(
|
||||
targetId,
|
||||
event,
|
||||
);
|
||||
// Check deliberate physical expenditure before random selection. The
|
||||
// native victim itself remains automatic and is never chosen by a witness.
|
||||
if (liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length) {
|
||||
const quoted = projectResidualPoisonPrintedCost(g, p.id, card.id);
|
||||
guardTurnActionPrintedCardCost(quoted, getPlayer(quoted, p.id), card);
|
||||
}
|
||||
const selected = shuffle(candidates)[0];
|
||||
const victim = controlledLeaders(g, target).find(
|
||||
(l) => l.id === selected.id,
|
||||
@@ -27936,6 +27974,8 @@ function playResidualPoison(
|
||||
const harkonnen = byFaction(g, 'harkonnen');
|
||||
if (harkonnen) returnCaptives(g, harkonnen);
|
||||
const used = discard(g, p, card.id);
|
||||
recordNativePrintedCardPlay(g, p.id, 'residualPoison');
|
||||
guardTurnActionPrintedCardCost(g, p, used);
|
||||
log(
|
||||
g,
|
||||
`${p.name} played Residual Poison against ${target.name}. ${victim.name} was randomly selected from the available leaders and sent to the Tanks. No spice is awarded, no forces are lost, and battle preparation continues.`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const PRINTED_CARD_EFFECTS = ['hajr', 'ghola', 'nullentropyBox', 'distrans', 'juiceOfSapho', 'semutaDrug'] as const;
|
||||
export const PRINTED_CARD_EFFECTS = ['hajr', 'ghola', 'nullentropyBox', 'distrans', 'juiceOfSapho', 'semutaDrug',
|
||||
'ornithopter', 'residualPoison'] as const;
|
||||
export type PrintedCardEffect = typeof PRINTED_CARD_EFFECTS[number];
|
||||
export type PrintedCardPlayCounts = {
|
||||
turn: number;
|
||||
@@ -56,7 +57,8 @@ export function printedCardPlayCount(counts: PrintedCardPlayCounts | undefined,
|
||||
|
||||
/** Call once for each actually accepted printed-role play, never for an effect,
|
||||
* sale, discard, recovery or role conversion. Semuta requires its physical
|
||||
* exchange, not commitment or candidate inspection. Genuine reacquisition may replay. */
|
||||
* exchange, not commitment or candidate inspection. Ornithopter counts placement
|
||||
* into its native flight, not each group or final disposal. Genuine reacquisition may replay. */
|
||||
export function recordPrintedCardPlay(counts: PrintedCardPlayCounts | undefined, player: string,
|
||||
turn: number, cardEffect: PrintedCardEffect): PrintedCardPlayCounts {
|
||||
validateRequest(player, turn, cardEffect);
|
||||
|
||||
+3
-2
@@ -3232,6 +3232,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'A printed Nullentropy Box clause counts its accepted two-spice search activation after answering this turn, before private selection. It does not name the recovered card; earlier use or recovery alone does not count. No lasts through Mentat. The actual fee shares the purse with payments and physical actions; an unpaid unknown recovered card cannot supply another effect. Whole native Basic Richese answers retain their real Semuta discard continuation. Current guides use native availability and paid selection keeps its own panel.',
|
||||
'Printed Distrans and Juice of Sapho clauses count actual native use after answering this turn. They do not select a transfer recipient/card or an order scope/mode/outcome. Native costs, capacity, reservations, first/last, no-op and unsplit-current-turn rules remain. Earlier Sapho answers may wait for original Movement without new income; only current exact choices become guides. These No intervals last through Mentat. Wider future ordering and action calendars remain partial.',
|
||||
'A printed Semuta Drug clause counts the completed exchange and disposal after answering, not a multi-card inspection commitment. The selected card is not promised. Its No interval lasts through Mentat. Current fresh windows and the already-declared foreign Truth cost retain native event, reservations and physical exchange; no later foreign discard is invented. Immediate No and completion checks precede inspection, and a required Yes cannot pass its sole modelled trigger. OR can preserve a later alternative. Unknown acquisitions supply no specific effect, but can supply Distrans’s generic other-card transfer with native reservations.',
|
||||
'A printed Ornithopter clause counts its first accepted native flight declaration after answering, not every group or disposal. It names no mode or route; an already active flight supplies no new credit. Its interval closes at your actual Movement end. A printed Residual Poison clause counts actual native random death/disposal without naming a victim, through turn end. Private completion pays its card without sampling a victim or inventing casualties for revival. Native movement and pre-leader legality, current events, costs and other commitments remain.',
|
||||
'Weather Control and Family Atomics conditions concern a future printed play in this original open Storm window, including after dial reveal and before movement starts. Harvester concerns this original fresh blow. A later blow has a different identity even in the same phase. Earlier plays, disposal, other roles and recovery alone do not count.',
|
||||
'These original-window conditions describe playing the card, with the normal chosen Weather distance, Atomics qualifying forces/casualties and Harvester arithmetic. They do not describe a future selected distance or amount. Accepting the window must preserve the whole answer; a later payment alternative can remain. No caps last through actual source closure, rather than ending at your own Ready.',
|
||||
'A current-lot Karama condition counts a future physical printed Karama cost for this original ordinary auction card, either immediate purchase or its winning invoice. Bidding alone, another Karama role, a converted Worthless card, an earlier play, recovery or a later lot does not count. A bid-and-Karama answer must retain the real bid before the purchase; free acquisition still uses the original native controls and hand limit. A whole No shares its payment and bid funding without assuming a forbidden free invoice. Unseen future auction cards cannot fund another promised card play; actual owned knowledge remains usable.',
|
||||
@@ -3247,11 +3248,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
],
|
||||
related: ['card-truthtrance', 'truthtrance-alliance', 'truthtrance-travel', 'truthtrance-bid', 'truthtrance-revival', 'truthtrance-bribe-expression', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Whole signed alternatives reuse phase-opening Amal with native payment order, printed Thumper with known-encounter quotation, Karama shipment pricing and current-lot costs, original-window plays, native power declarations, printed Hajr/Ghola/Box/Distrans/Sapho and completed Semuta exchanges, battle, Traitor, final-alliance, bid, Revival, payment and transport constraints with separate actual intervals, resources and deadlines.', evidence: ['game/window-card-play.ts', 'game/battle-power-question.ts', 'game/turn-action-promises.ts', 'game/printed-card-play.ts', 'game/movement-promise-search.ts', 'game/mixed-action-question.ts', 'game/engine.ts', 'game/truthtrance.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Whole signed alternatives reuse phase-opening Amal with native payment order, printed Thumper with known-encounter quotation, Karama shipment pricing and current-lot costs, original-window plays, native power declarations, printed Hajr/Ghola/Box/Distrans/Sapho, accepted Ornithopter flights, native Residual Poison and completed Semuta exchanges, battle, Traitor, final-alliance, bid, Revival, payment and transport constraints with separate actual intervals, resources and deadlines.', evidence: ['game/window-card-play.ts', 'game/battle-power-question.ts', 'game/turn-action-promises.ts', 'game/printed-card-play.ts', 'game/movement-promise-search.ts', 'game/mixed-action-question.ts', 'game/engine.ts', 'game/truthtrance.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Full editor, raw question, private answer, persistent own history and exact current-phase native guide.', evidence: ['components/turn-action-fields.tsx', 'components/turn-action-promises.tsx', 'components/truthtrance.tsx', 'components/game-table.tsx', 'tests/truth-turn-action-controls.test.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Four minimal profiles answer and consume native guides, including printed roles, exact fresh Semuta controls and original power declarations; full strategy and calibration remain deferred.', evidence: ['game/bots.ts', 'tests/truth-turn-action-runtime.test.ts', 'tests/truth-turn-preparation-runtime.test.ts', 'tests/fixture-truth-turn-alliance.ts', 'tests/truth-turn-alliance-runtime.test.ts', 'tests/truth-turn-combat-runtime.test.ts', 'tests/truth-turn-card-play-runtime.test.ts', 'tests/truth-turn-power-runtime.test.ts', 'tests/truth-turn-window-card-runtime.test.ts', 'tests/truth-turn-karama-runtime.test.ts', 'tests/truth-turn-karama-shipment-runtime.test.ts', 'tests/truth-turn-thumper-runtime.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Implemented', detail: 'Current funding, physical event meaning, private alternatives, separate deadlines and provisional scope are explicit.', evidence: ['docs/TRUTHTRANCE_TURN_ACTIONS.md', 'docs/RULE_DECISIONS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Focused helper, control and original native sources, including physical printed plays, original Storm/blow sources, shared Revival resources and native power declarations; broader composition and final acceptance remain.', evidence: ['tests/fixture-truth-semuta-play.ts', 'tests/truth-semuta-play-runtime.test.ts', 'tests/semuta-reaction-controls.test.tsx', 'tests/fixture-truth-transfer-order-play.ts', 'tests/truth-transfer-order-play-runtime.test.ts', 'tests/fixture-distrans-prebid.ts', 'tests/fixture-sapho-started-movement.ts', 'tests/fixture-truth-box-play.ts', 'tests/truth-box-play-runtime.test.ts', 'tests/truth-opaque-card-plan.test.ts', 'tests/fixture-truth-choam-income.ts', 'tests/truth-choam-income-runtime.test.ts', 'tests/window-card-play.test.ts', 'tests/battle-power-question.test.ts', 'tests/printed-card-play.test.ts', 'tests/turn-action-promises.test.ts', 'tests/truth-turn-action-controls.test.tsx', 'tests/fixture-truth-turn-action.ts', 'tests/truth-turn-action-runtime.test.ts', 'tests/truth-turn-action-funding.test.ts', 'tests/fixture-truth-turn-preparation.ts', 'tests/truth-turn-preparation-runtime.test.ts', 'tests/fixture-truth-turn-alliance.ts', 'tests/truth-turn-alliance-runtime.test.ts', 'tests/fixture-truth-turn-combat.ts', 'tests/truth-turn-combat-runtime.test.ts', 'tests/fixture-truth-turn-card-play.ts', 'tests/truth-turn-card-play-runtime.test.ts', 'tests/fixture-truth-turn-power.ts', 'tests/truth-turn-power-runtime.test.ts', 'tests/fixture-truth-turn-window-card.ts', 'tests/truth-turn-window-card-runtime.test.ts', 'tests/fixture-truth-turn-karama.ts', 'tests/truth-turn-karama-runtime.test.ts', 'tests/truth-turn-karama-forecast.test.ts', 'tests/fixture-truth-turn-karama-shipment.ts', 'tests/truth-turn-karama-shipment-runtime.test.ts', 'tests/truth-turn-karama-shipment-funding.test.ts', 'tests/fixture-truth-turn-thumper.ts', 'tests/truth-turn-thumper-runtime.test.ts', 'tests/fixture-truth-turn-amal.ts', 'tests/truth-turn-amal-runtime.test.ts', 'game/ordinary-payment-plan.ts', 'tests/ordinary-payment-plan.test.ts', 'tests/fixture-truth-charity-payments.ts', 'tests/truth-charity-payments-runtime.test.ts'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Focused helper, control and original native sources, including physical printed plays, original Storm/blow sources, shared Revival resources and native power declarations; broader composition and final acceptance remain.', evidence: ['tests/fixture-truth-flight-poison.ts', 'tests/truth-flight-poison-runtime.test.ts', 'tests/fixture-truth-semuta-play.ts', 'tests/truth-semuta-play-runtime.test.ts', 'tests/semuta-reaction-controls.test.tsx', 'tests/fixture-truth-transfer-order-play.ts', 'tests/truth-transfer-order-play-runtime.test.ts', 'tests/fixture-distrans-prebid.ts', 'tests/fixture-sapho-started-movement.ts', 'tests/fixture-truth-box-play.ts', 'tests/truth-box-play-runtime.test.ts', 'tests/truth-opaque-card-plan.test.ts', 'tests/fixture-truth-choam-income.ts', 'tests/truth-choam-income-runtime.test.ts', 'tests/window-card-play.test.ts', 'tests/battle-power-question.test.ts', 'tests/printed-card-play.test.ts', 'tests/turn-action-promises.test.ts', 'tests/truth-turn-action-controls.test.tsx', 'tests/fixture-truth-turn-action.ts', 'tests/truth-turn-action-runtime.test.ts', 'tests/truth-turn-action-funding.test.ts', 'tests/fixture-truth-turn-preparation.ts', 'tests/truth-turn-preparation-runtime.test.ts', 'tests/fixture-truth-turn-alliance.ts', 'tests/truth-turn-alliance-runtime.test.ts', 'tests/fixture-truth-turn-combat.ts', 'tests/truth-turn-combat-runtime.test.ts', 'tests/fixture-truth-turn-card-play.ts', 'tests/truth-turn-card-play-runtime.test.ts', 'tests/fixture-truth-turn-power.ts', 'tests/truth-turn-power-runtime.test.ts', 'tests/fixture-truth-turn-window-card.ts', 'tests/truth-turn-window-card-runtime.test.ts', 'tests/fixture-truth-turn-karama.ts', 'tests/truth-turn-karama-runtime.test.ts', 'tests/truth-turn-karama-forecast.test.ts', 'tests/fixture-truth-turn-karama-shipment.ts', 'tests/truth-turn-karama-shipment-runtime.test.ts', 'tests/truth-turn-karama-shipment-funding.test.ts', 'tests/fixture-truth-turn-thumper.ts', 'tests/truth-turn-thumper-runtime.test.ts', 'tests/fixture-truth-turn-amal.ts', 'tests/truth-turn-amal-runtime.test.ts', 'game/ordinary-payment-plan.ts', 'tests/ordinary-payment-plan.test.ts', 'tests/fixture-truth-charity-payments.ts', 'tests/truth-charity-payments-runtime.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -757,6 +757,8 @@ export function truthQuestionText(
|
||||
return 'you activate Nullentropy Box in its printed role and pay its 2-spice search cost after this answer this turn';
|
||||
if (action.effect === 'distrans') return 'you complete a printed Distrans transfer and discard Distrans after this answer this turn';
|
||||
if (action.effect === 'semutaDrug') return 'you complete a printed Semuta Drug exchange and discard Semuta Drug after this answer this turn';
|
||||
if (action.effect === 'ornithopter') return 'you play Ornithopter into a native flight after this answer before your Shipment and Movement turn ends';
|
||||
if (action.effect === 'residualPoison') return 'you play Residual Poison in its printed role after this answer this turn';
|
||||
return `you play ${action.effect === 'hajr' ? 'Hajr' : action.effect === 'ghola' ? 'Tleilaxu Ghola' :
|
||||
'Juice of Sapho'} in its printed role after this answer this turn`;
|
||||
}
|
||||
|
||||
@@ -951,7 +951,7 @@ export function closeTurnActionPayments<T extends TurnActionPromise>(promises: r
|
||||
});
|
||||
}
|
||||
|
||||
/** Close transport and its native Hajr stage; later Ghola or payment can still complete the tree.
|
||||
/** Close transport and its native Hajr/Ornithopter stages; later card or payment alternatives can complete the tree.
|
||||
* The native joint solver, rather than this ledger, rejects own illegal closure. */
|
||||
export function closeTurnActionTransport<T extends TurnActionPromise>(promises: readonly T[], player: string,
|
||||
turn: number): T[] {
|
||||
@@ -962,6 +962,7 @@ export function closeTurnActionTransport<T extends TurnActionPromise>(promises:
|
||||
return settle(promise, { ...promise, transportClosed: true,
|
||||
shipment: { ...promise.shipment, pending: false }, ...(promise.cardPlays ? { cardPlays: {
|
||||
...promise.cardPlays, hajr: promise.cardPlays.hajr ? { ...promise.cardPlays.hajr, closed: true } : null,
|
||||
...(promise.cardPlays.ornithopter ? { ornithopter: { ...promise.cardPlays.ornithopter, closed: true } } : {}),
|
||||
} } : {}) });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user