Include Ghola force returns in Basic movement promises

Use exact physical Ghola preparation and known/paid Box recovery without
crediting conditional income or normal revival allowances. Guide real
force return, paid shipment and qualifying ground movement; retain the
existing incomplete-mode and final acceptance boundaries.
This commit is contained in:
Quintine committed 2026-10-24 16:22:32 +11:00
1 parent c311ed4251
commit 49992d322f
14 files changed
+767 -14

No files matched your search

+10 -1
View File
@@ -1,3 +1,4 @@
import { gholaForcePreparations } from './ghola-force-preparation';
import { earlyAuctionFunding } from './early-auction-funding';
import { recordMovementDiscardKnowledge, knownMovementDiscards, recordMovementBoxKnowledge, recordMovementClaimKnowledge, clearMovementDiscardKnowledge, type MovementDiscardKnowledge } from './movement-discard-knowledge';
import { movementShipmentPreparations } from './movement-shipment-preparation';
@@ -16552,7 +16553,7 @@ function groundMovementCompletionAtMovement(game: Game, actor: string, promises:
},
*preparations(state, owner) {
const player = getPlayer(state, owner), paid = state.pendingNullentropy;
const relevant = (card: Card) => card.effect === 'hajr' || card.effect === 'ornithopter' ||
const relevant = (card: Card) => card.effect === 'hajr' || card.effect === 'ornithopter' || card.effect === 'ghola' ||
(player.faction === 'choam' && card.kind === 'worthless' && card.name === 'Kulon');
if (paid) {
if (paid.player !== owner || nullentropyIntegrity(state)) return;
@@ -16572,6 +16573,14 @@ function groundMovementCompletionAtMovement(game: Game, actor: string, promises:
try { return groundMovementProbe(applyActionInner(source, actor, action, 'movementPreparation'), actor); }
catch (error) { if (error instanceof RuleError) return null; throw error; }
});
const ghola = gholaOptions(state, player);
if (ghola.available) for (const action of gholaForcePreparations({
cards: ghola.cards, tanks: player.tanks, eliteTanks: player.elites?.tanks ?? 0,
maxForces: ghola.maxForces, eliteRemaining: ghola.eliteRemaining,
})) {
try { yield { game: groundMovementProbe(applyActionInner(state, owner, action, 'movementPreparation'), owner), actions: [action] }; }
catch (error) { if (!(error instanceof RuleError)) throw error; }
}
for (const card of player.hand) {
if (!player.shipped && !state.karamaShipping && canUseAsKaramaRole(state, player, card)) {
const action: Action = { type: 'card', card: card.id, mode: 'shipment', target: owner };
+16
View File
@@ -0,0 +1,16 @@
import type { Action } from './engine';
/** Enumerate physical choices from the owner's current authoritative Ghola
* options. This is not a new revival allowance or a strategic shortlist. */
export function* gholaForcePreparations(quote: {
cards: readonly string[];
tanks: number;
eliteTanks: number;
maxForces: number;
eliteRemaining: number;
}): Generator<Action> {
for (const card of quote.cards) for (let amount = 1; amount <= quote.maxForces; amount++)
for (let elite = Math.max(0, amount - (quote.tanks - quote.eliteTanks));
elite <= Math.min(amount, quote.eliteTanks, quote.eliteRemaining); elite++)
yield { type: 'card', card, amount, elite };
}
+1 -1
View File
@@ -7,7 +7,7 @@ type Entries = FreshDiscardBatch['entries'];
type Knowledge = MovementDiscardKnowledge | undefined;
const relevant = (card: Card): boolean =>
(card.kind === 'special' && card.effect === 'hajr') ||
(card.kind === 'special' && (card.effect === 'hajr' || card.effect === 'ghola')) ||
(card.kind === 'special' && card.id === 'richese-ornithopter' && card.effect === 'ornithopter') ||
(card.kind === 'worthless' && card.name === 'Kulon');
+7 -3
View File
@@ -32,7 +32,7 @@ function* groups(player: Player, keys: string[], amount: number, index = 0,
}
/** Finite owned-action reachability. Each move consumes a real entitlement;
* Hajr, Ornithopter, Kulon and Box use owned physical cards and known or
* Hajr, Ornithopter, Ghola, Kulon and Box use owned physical cards and known or
* already-paid recovery choices, never invented unknown future draws.
* The adapter shares authoritative movement validation/physical commitment.
* Opposing arrival outcomes are not simulated or assumed. */
@@ -45,8 +45,12 @@ export function findMovementCompletion(source: Game, actor: string,
if (!needsMove && adapter.canFinish(game, actor)) return { actions };
const player = game.players.find(candidate => candidate.id === actor)!;
const physical = Object.values(player.forces).reduce((sum, amount) => sum + amount, 0);
if (live.some(promise => promise.answer && promise.minimum > physical + (player.shipped ? 0 : player.reserves))) return null;
const key = JSON.stringify([player.forces, player.elites, player.moved, player.shipped, player.reserves, player.hand.map(card => card.id),
// A held recovery card may return more than five over several real plays
// or Box reuse. Tanks are only an optimistic bound; the adapter still
// validates every owned card and never inspects unknown Box faces here.
const recovery = player.hand.some(card => card.effect === 'ghola' || card.effect === 'nullentropyBox') ? player.tanks : 0;
if (live.some(promise => promise.answer && promise.minimum > physical + (player.shipped ? 0 : player.reserves + recovery))) return null;
const key = JSON.stringify([player.forces, player.elites, player.moved, player.shipped, player.reserves, player.tanks, player.hand.map(card => card.id),
player.hajrPlays, player.ornithopterExtraMoves, player.spice, game.choamMovement,
game.aid, game.karamaShipping, game.guildRateBlocked, game.shipmentPromises,
game.discard.map(card => card.id), game.movementDiscardKnowledge?.[actor], game.pendingNullentropy,
+8 -6
View File
@@ -2641,6 +2641,7 @@ export const RULE_TOPICS: RuleTopic[] = [
steps: [
'Choose a dead leader for leader revival, or leave the leader choice empty to return up to five forces. A card pays for one of these choices; it cannot revive both a leader and the Kwisatz Haderach.',
'A binding battle answer or prescience commitment may require this preparation. The private guide identifies a Ghola play that can fulfill the answer, without spending or revealing the card until you choose to play it. Resolve any revival-income response before using that income to support a battle.',
'A Basic ground-movement promise can also require Ghola force preparation. The guide returns physical Tanks to reserves, then preserves an unused affordable shipment and one later matching ground move. Ghola itself does not fulfill movement or consume normal revival allowances. A known discarded Ghola may be recovered with the paid Box and played again.',
'Returned forces go to reserves. A leader revived with this card can fight again this turn, including in another territory. Atreides can also select a dead Kwisatz Haderach; revive it separately from its accompanying leader. Returning a controlled foreign ghola does not transfer it back to its original faction. Additional advanced and expansion interactions remain under audit.',
'An actual Ecaz native Ghola return uses that disc in its current ordinary cohort without consuming the ordinary phase slot. A repeat waits for the remaining native cohort and another opening. Independent Duke Ghola preserves a satisfied five-Tanks opening and returns him set aside; only Ecaz may revive him.',
'A saved play preserves the revived pieces, any earned technology income and the discarded card. Recovery restores any pending Tleilaxu income response without reviving or paying twice. Private preparation guidance waits for recovery to finish.',
@@ -2651,7 +2652,7 @@ export const RULE_TOPICS: RuleTopic[] = [
status: 'Partial',
detail:
'Ordinary and typed forces, controlled leaders and Kwisatz retain original use and custody. Actual Ecaz native Ghola/foreign returns now record completed native cohort identity; independent Duke resurrection preserves its five-Tanks opening without aligning death counters. Ecaz-only living set-aside custody remains; broader combinations and assurance stay unfinished.',
evidence: ['game/engine.ts', 'game/ecaz-duke-revival.ts', 'game/ecaz-revival-cohort.ts', 'game/revival.ts'],
evidence: ['game/ghola-force-preparation.ts', 'game/engine.ts', 'game/ecaz-duke-revival.ts', 'game/ecaz-revival-cohort.ts', 'game/revival.ts'],
},
{
area: 'Player controls',
@@ -2936,18 +2937,19 @@ export const RULE_TOPICS: RuleTopic[] = [
'Ordinary reserve shipment may prepare or reinforce a later ground group using its actual cost, native allocation and pledged payment split. Shipment itself neither fulfills movement Yes nor violates movement No. Joint guidance honors a required shipment before End; an already declared reserve or native Guild transport keeps its exact source, destination and funding while responses finish.',
'Waiting answers use current board/resources without assuming foreign income, movement, cooperation or discarded cards. Another player cannot fulfill or close your promise by moving or ending. Ordinary guidance appears on your actual turn; your already-paid Box choice can finish now. Deferred gifts/accompaniment cannot voluntarily spend the last route before another player responds.',
'Earlier classic answers include owned Charity and ordinary Revival, reopen readiness when needed, and reserve a current winning auction invoice using cash or a held Karama. Only current preparation, Ready, bid-pass or owned payment appears before Movement. Unknown purchases and future income are not assumed. Forced Storm losses can release impossibility; old movement allowances reset at actual phase entry. A separate held Karama may prepare Guild shipment rates. Native Once Around and owned Silent bids reserve exact funding; a submitted ally locks its full pledge until reveal. Current native guidance passes or submits zero with the original event. A paid sale is not charged again, and foreign invoice outcomes precede release.',
'Private feasible answers and the next owned step use actual movement legality, typed groups, held Hajr, both Ornithopter modes and original cohorts, current city access, held native Kulon and owned Box recovery. Before paying the two-spice Box fee, only a known card still in discard supplies a route; unknown future draws/gifts are not invented. Native relocation and wider preparation remain unfinished.',
'Private feasible answers and the next owned step use actual movement legality, typed groups, held Hajr, both Ornithopter modes and original cohorts, current city access, held native Kulon, physical Ghola force returns and owned Box recovery. Before paying the two-spice Box fee, only a known card still in discard supplies a route; unknown future draws/gifts are not invented. Native relocation and wider preparation remain unfinished.',
'Ghola returns up to five eligible forces to reserves using the physical card and current typed allowance, without using normal revival slots. It does not fulfill ground movement: an unused affordable shipment and later matching move remain necessary. A known spent Ghola can be recovered and replayed through Box. Possible Tleilaxu income remains conditional and supplies no forecast cash.',
'A paid Box search retains its full private choice and original event. A guided choice preserves Yes; an incompatible selection rejects before shuffle and keeps that paid search available without another fee. No-only promises retain ordinary selection before movement can end. The existing provisional atomic full-hand exchange remains.',
'Voluntary actions cannot erase the last required route. Native counter responses finish before impossibility is decided. An opposing loss releases only incompatible answers, retaining No constraints and still-possible Yes answers in accepted order. This owned-route and release priority is a provisional software composition of the general current-turn rule, not a publisher movement-specific FAQ.',
'Persistent answer and next-step controls do not time out. All four profiles use the same private legal guidance. Broader Truthtrance questions, complete modes, full combinations and final acceptance remain unfinished.',
],
related: ['card-truthtrance', 'truthtrance-shipment', 'movement', 'implementation-checklist'],
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'Basic declared native families now join classic phases0–4. Exact Richese own/donor invoices, full submitted-ally Silent locks and already-paid boundaries prepare the owned turn without speculative bought cards/income. Fixed non-CHOAM preopening Charity and shared ordinary force-revival accounting preserve real native counter decisions. Mandatory Ix phase5 opening retains the future turn/reset. CHOAM own unresolved opening, later rounds, wider preparations/modules remain unfinished.', evidence: ['game/early-auction-funding.ts', 'game/movement-promises.ts', 'game/movement-promise-search.ts', 'game/movement-shipment-preparation.ts', 'game/movement-discard-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Persistent busy-safe guidance includes current Charity, Revival, Ready, ordinary bid-pass/payment and event-bearing native Once Around pass/Silent zero. Foreign pending funding cannot force a speculative positive bid or expose future movement. Actual native pass/zero handlers preserve unread obligations. Existing waiting/card controls remain; browser/visual acceptance stays open.', evidence: ['components/movement-claim-fields.tsx', 'components/movement-promises.tsx', 'components/shipment-promises.tsx', 'components/truthtrance.tsx', 'components/nullentropy-box.tsx', 'components/game-table.tsx', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/truth-movement-early-controls.test.tsx', 'tests/truth-movement-native-early-controls.test.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Eight existing-profile runs across native Once Around/Silent make56 accepted early-preparation/later-movement proposals plus148 separate native continuations. Zero/pass preserves the route while foreign funding resolves; all-No retains ordinary payment. Minimal legal participation only; no strategy tuning or calibration.', evidence: ['game/bots.ts', 'tests/truth-movement-waiting-engine.test.ts', 'tests/truth-movement-early-engine.test.ts', 'tests/truth-movement-native-early-engine.test.ts'] },
{ area: 'Implementation', status: 'Partial', detail: 'Basic declared native families now join classic phases0–4. Exact Richese own/donor invoices, full submitted-ally Silent locks and already-paid boundaries prepare the owned turn without speculative bought cards/income. Fixed non-CHOAM preopening Charity and shared ordinary force-revival accounting preserve real native counter decisions. Mandatory Ix phase5 opening retains the future turn/reset. Held Ghola and known/paid Box recovery now add exact force returns and later shipment; normal revival allowances stay separate and native income is not precredited. CHOAM own unresolved opening, later rounds, wider preparations/modules remain unfinished.', evidence: ['game/ghola-force-preparation.ts', 'game/early-auction-funding.ts', 'game/movement-promises.ts', 'game/movement-promise-search.ts', 'game/movement-shipment-preparation.ts', 'game/movement-discard-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Physical Ghola force-return guidance now precedes actual reserve shipment and ground movement, with income/disposal controls retaining priority. Persistent busy-safe guidance includes current Charity, Revival, Ready, ordinary bid-pass/payment and event-bearing native Once Around pass/Silent zero. Foreign pending funding cannot force a speculative positive bid or expose future movement. Actual native pass/zero handlers preserve unread obligations. Existing waiting/card controls remain; browser/visual acceptance stays open.', evidence: ['components/movement-claim-fields.tsx', 'components/movement-promises.tsx', 'components/shipment-promises.tsx', 'components/truthtrance.tsx', 'components/nullentropy-box.tsx', 'components/game-table.tsx', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/truth-movement-early-controls.test.tsx', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/truth-movement-ghola-controls.test.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Eight existing-profile held-Ghola/known-Box runs accept40 proposals plus24 separate native continuations. Physical return, exact paid recovery, unused shipment and qualifying ground move use the same private legal guidance. Earlier native funding paths remain. Minimal legal participation only; no strategy tuning or calibration.', evidence: ['game/bots.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-native-early-engine.test.ts'] },
{ area: 'Documentation', status: 'Implemented', detail: 'General FAQ authority, existential single-event meaning, explicit software composition, source selection and unfinished scope are documented.', evidence: ['docs/TRUTHTRANCE_MOVEMENT_PROMISES.md', 'docs/RULE_DECISIONS.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Current29-file244/244, types/lint/build and six gameplay/control/table-rendering programmes pass. E2/E1/E3 original Charity prefixes25/36/25 retain native setup/cards and Emperor10 spice/20 reserves, then actual invoice2/shipment8/ground8. Controlled escrow, original-card/prior-loss and allowed/canceled Charity/revival cases disclose staging. Initial Ix opening failure is repaired and final frozen union passes. Earlier counts remain historical. Full modes, wider forecast scope, browser/deployed and comprehensive assurance remain open.', evidence: ['tests/truth-movement-waiting-engine.test.ts', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/fixture-truth-movement-waiting.ts', 'tests/fixture-truth-movement-waiting-box.ts', 'tests/fixture-hajr-replay.ts', 'tests/truth-movement-early-engine.test.ts', 'tests/truth-movement-early-controls.test.tsx', 'tests/fixture-truth-movement-early.ts', 'tests/early-auction-funding.test.ts', 'tests/truth-movement-native-early-engine.test.ts', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/fixture-truth-movement-native-early.ts', 'docs/TRUTHTRANCE_MOVEMENT_PROMISES.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Current22-file182/182, types/lint/build and four actual-control/GameTable SSR programmes pass. Original held/Box prefixes42/104/75/151 use native setup, actual cache purchase, paid desert shipments, Storm casualties and settled bribes without Advanced city income. Eight minimal-profile runs accept40 proposals plus24 separate native continuations. Controlled acquisitions/losses/knowledge and counter-enabled income disclose staging. Ghola discard relevance and a fixture field collision are repaired before final frozen proof. Earlier counts remain historical. Full modes, wider preparations, browser/deployed and comprehensive assurance remain open.', evidence: ['tests/truth-movement-waiting-engine.test.ts', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/fixture-truth-movement-waiting.ts', 'tests/fixture-truth-movement-waiting-box.ts', 'tests/fixture-hajr-replay.ts', 'tests/truth-movement-early-engine.test.ts', 'tests/truth-movement-early-controls.test.tsx', 'tests/fixture-truth-movement-early.ts', 'tests/early-auction-funding.test.ts', 'tests/truth-movement-native-early-engine.test.ts', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/fixture-truth-movement-native-early.ts', 'tests/fixture-truth-movement-ghola.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-ghola-controls.test.tsx', 'tests/movement-discard-knowledge.test.ts', 'docs/TRUTHTRANCE_MOVEMENT_PROMISES.md'] },
],
},
{