Restore safe Duke grants in early legacy games
Verify and publish checkpoint container / container (push) Successful in 10m31s
Verify and publish checkpoint container / container (push) Successful in 10m31s
This commit is contained in:
1 parent
59cb336c80
commit
22b463a395
10 files changed
+263
-11
No files matched your search
@@ -1,22 +1,40 @@
|
||||
import { selectedPhaseModuleRoster, type FactionModuleProfile } from './faction-module-profile';
|
||||
|
||||
/** Public uncertainty about a possible concealed Duke capture. Fresh games
|
||||
* explicitly begin false; only a public resolution may restore false. True,
|
||||
* absent, or malformed values never establish safety. */
|
||||
* explicitly begin false; some early legacy states can prove safety publicly. */
|
||||
export type DukeCapturePolicyContext = Omit<Partial<FactionModuleProfile>, 'players'> & {
|
||||
advanced: boolean;
|
||||
players: readonly { faction: string }[];
|
||||
dukeCaptureUncertain?: boolean;
|
||||
status?: string;
|
||||
turn?: number;
|
||||
phase?: number;
|
||||
battle?: unknown;
|
||||
lastBattleContext?: unknown;
|
||||
pendingCapture?: unknown;
|
||||
response?: { kind: string } | null;
|
||||
decision?: { kind: string } | null;
|
||||
};
|
||||
|
||||
export const DUKE_CAPTURE_UNCERTAIN_BLOCK =
|
||||
'Duke Vidal acquisition is unavailable while Harkonnen capture history may be unresolved.';
|
||||
|
||||
/** Before the first battle of the first turn, a legacy game has no capture
|
||||
* history. Use public phase and continuation state only, never Duke custody. */
|
||||
export function legacyDukeCaptureHistoryClear(g: DukeCapturePolicyContext): boolean {
|
||||
return g.dukeCaptureUncertain === undefined && g.status === 'playing' && g.turn === 1 &&
|
||||
typeof g.phase === 'number' && Number.isInteger(g.phase) && g.phase >= 0 && g.phase <= 5 &&
|
||||
g.battle == null && g.lastBattleContext == null && g.pendingCapture == null &&
|
||||
g.response?.kind !== 'capture' && g.decision?.kind !== 'captureOffer' &&
|
||||
g.decision?.kind !== 'capturedLeader';
|
||||
}
|
||||
|
||||
/** This result depends only on public table configuration and the public
|
||||
* uncertainty receipt, never on actual Duke custody or capture identity. */
|
||||
export function dukeCaptureUncertaintyBlock(g: DukeCapturePolicyContext): string | null {
|
||||
return g.advanced && g.players.some(player => player.faction === 'harkonnen') &&
|
||||
g.dukeCaptureUncertain !== false ? DUKE_CAPTURE_UNCERTAIN_BLOCK : null;
|
||||
g.dukeCaptureUncertain !== false && !legacyDukeCaptureHistoryClear(g)
|
||||
? DUKE_CAPTURE_UNCERTAIN_BLOCK : null;
|
||||
}
|
||||
|
||||
/** Original native faction tables with no selected optional module. Native
|
||||
|
||||
+4
-1
@@ -147,7 +147,7 @@ import { battleCardSlotEligible, battleCategoryInspectionValue, fixedBattleInspe
|
||||
import { HARASS_WITHDRAW_CARD, HarassWithdrawError, isHarassWithdraw, quoteHarassWithdraw, defaultHarassWithdrawAllocation, harassWithdrawNeedsAllocation, type HarassWithdrawSelection, type HarassWithdrawContext, type HarassWithdrawPreview } from './harass-withdraw';
|
||||
import { originalAuctionInspectionLot } from './auction-inspection-source';
|
||||
import { nextDukeCaptureUncertainty } from './duke-capture-uncertainty';
|
||||
import { dukeCaptureUncertaintyBlock, plainAdvancedEcazDukeProfile } from './ecaz-duke-capture-policy';
|
||||
import { dukeCaptureUncertaintyBlock, legacyDukeCaptureHistoryClear, plainAdvancedEcazDukeProfile } from './ecaz-duke-capture-policy';
|
||||
import { discoveryChoices, discoveryStashSignature, type DiscoveryStash } from './discovery-actions';
|
||||
import { createStormSource, validateStormSource, discoveryStormOffer, createDiscoveryStorm, chooseDiscoveryStorm, validateDiscoveryStorm, finishDiscoveryStorm, discoveryStormDistance, discoveryStormSaphoModes, reorderDiscoveryStorm, type StormMovementSource, type DiscoveryStorm } from './discovery-storm';
|
||||
import { completeDiscoveryFlight, discoveryFlightOffer, quoteDiscoveryFlight, validateDiscoveryFlight, type DiscoveryFlightReceipt } from './discovery-flight';
|
||||
@@ -31327,6 +31327,7 @@ function reconcileHomeworldEcazDuke(g: Game) {
|
||||
!duke.leader.dead && !duke.leader.capturedBy && !duke.leader.gholaBy && !duke.leader.concealed) {
|
||||
g.dukeVidal = consumeDuke(duke);
|
||||
log(g, 'Occupied Ecaz displaced Duke Vidal’s ordinary faction grant; the occupier chooses its eligible recipient.');
|
||||
g.dukeCaptureUncertain = false;
|
||||
}
|
||||
const available = homeworldRule(() => quoteHomeworldEcazDuke(g));
|
||||
if (!available.blocked && available.event && available.owner && available.recipients.length === 1)
|
||||
@@ -44038,6 +44039,7 @@ export function normalizeAutomaticGame(state: Game): Game {
|
||||
ecazCollectionIntegrity(state);
|
||||
ecazAllianceIntegrity(state);
|
||||
const g = structuredClone(state);
|
||||
if (legacyDukeCaptureHistoryClear(g)) g.dukeCaptureUncertain = false;
|
||||
reconcileHomeworldEcazDuke(g);
|
||||
syncTupileSlots(g);
|
||||
prepareTupileCleanup(g);
|
||||
@@ -44155,6 +44157,7 @@ function applyActionInner(
|
||||
'Finish the original occupied Tupile hand cleanup before another action.');
|
||||
ecazTreacheryIntegrity(state);
|
||||
const g = structuredClone(state);
|
||||
if (execution === 'live' && legacyDukeCaptureHistoryClear(g)) g.dukeCaptureUncertain = false;
|
||||
if ((g.pendingNexusEmperorPayment || g.pendingNexusEmperorSellerPurchase || g.pendingAmbassador?.nexusPurchase) &&
|
||||
!g.truthtrance && !pendingNexusTraitors(g)) {
|
||||
const p = getPlayer(g, id);
|
||||
|
||||
+1
-1
@@ -1727,7 +1727,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Destroyed tokens return to supply; triggered regular tokens are set aside, Bene Gesserit is permanently removed, and replenishment waits for all five random tokens. Storm/explosion destruction remains separate. The reusable Ecaz token returns to supply after acquisition or alliance proposal. Basic has an optional new-ally Duke loan; the supplied Advanced p30 automatically grants an available Duke after accepted consent.',
|
||||
'When an eligible entrant reaches the reusable Ecaz token, Ecaz may acquire the available shared Duke for itself or its current reciprocal ally. The token returns to supply and its companions are unchanged. Direct acquisition persists unused until battle use or Moritani acquisition; that shared tenure is provisional. It is distinct from the optional Basic or automatic Advanced new-alliance turn loan. A fresh token entry is required, with no standing transfer action.',
|
||||
'Unallied Ecaz may propose an alliance to an unallied entrant. Acceptance activates alliance abilities immediately. Basic then offers an optional Duke loan; Advanced automatically adds an available Duke under supplied PDF p30, without another acknowledgement. The first version releases a surviving loan after one battle, provisionally; an unused living loan is set aside at turn end. Unavailable Duke never prevents consent.',
|
||||
'In Advanced games with Harkonnen, a public capture that could have involved Duke makes the direct grant and automatic loan unavailable, regardless of which leader was secretly captured. A public event establishing the actual Duke, such as a revealed ordinary Duke battle or named revival, clears this uncertainty; a later possible capture can restore it. Older games without the capture record remain guarded until a supported public resolution. This provisional restriction protects the hidden captive identity and may prevent an otherwise legal grant.',
|
||||
'In Advanced games with Harkonnen, a public capture that could have involved Duke makes the direct grant and automatic loan unavailable, regardless of which leader was secretly captured. A public event establishing the actual Duke, such as a revealed ordinary Duke battle, named revival or logged occupied-Ecaz displacement of an ordinary grant, clears this uncertainty; a later possible capture can restore it. An older game without the record can also establish clear history through public first-turn pre-battle state; later or ambiguous history remains guarded until a supported resolution. This provisional restriction protects the hidden captive identity and may prevent an otherwise legal grant.',
|
||||
'When another eligible faction enters a marked stronghold, Ecaz may trigger its Ambassador for itself or its current ally, or leave it in place. Ecaz itself, its ally, advisors and the faction matching the physical Ambassador do not trigger it. Ordinary shipment and native Guild transport settle their own income, BG Intrusion and accompaniment controls before queued entry, without replaying payment or forces. Actual busy controls and unsupported paired or module combinations remain guarded.',
|
||||
'In supported Basic games with Ecaz and Moritani, a single entry that earns both an Ambassador and Terror opportunity retains both in their existing provisional storm order, including selected native factions and the independent Ecaz cards. A Fremen relocation or Guild shipment from the Ambassador resolves its own child arrival before the original queue continues. If that child reveals the same Terror token, it cannot trigger again for the original entrant; if it is left hidden, the original opportunity remains. Unsupported module interactions remain guarded.',
|
||||
'Supported Advanced games with both Ecaz and Moritani and other native faction decks also offer both entry reactions in provisional storm order. CHOAM may prevent an eligible move first; otherwise the move happens once before the token decisions. Fremen and Guild Ambassador effects that create another arrival remain unavailable in this Advanced overlap.',
|
||||
|
||||
Reference in new issue
Block a user