Connect printed Diplomat defenses and Ecaz Tanked Duke Cunning
This commit is contained in:
1 parent
039fe4e87f
commit
be8703dc51
17 files changed
+811
-182
No files matched your search
@@ -378,11 +378,12 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
if (copy === undefined) return defense;
|
||||
const quote = quoteDiplomatDefense({ assignments: side.leaderSkills ?? [], selectedLeader: side.plan.leader,
|
||||
weapon: card(side, side.plan.weapon), defense: card(side, side.plan.defense),
|
||||
opposingDefense: card(other, other.plan.defense) });
|
||||
opposingDefense: card(other, other.plan.defense), opposingWeapon: card(other, other.plan.weapon),
|
||||
expandedDefenses: true });
|
||||
requireQuote(copy && quote && !side.lateDefense && !other.lateDefense &&
|
||||
!side.stronghold && !other.stronghold && !input.homeworld &&
|
||||
copy.leader === quote.leader && copy.source === quote.source && copy.kind === quote.kind && quote.cards.includes(copy.card),
|
||||
'The copied Diplomat defense lost its native trainer, committed Worthless or opposing base defense.');
|
||||
'The copied Diplomat defense lost its native trainer, committed Worthless or opposing printed defense.');
|
||||
return copiedDiplomatDefense(quote, copy.card);
|
||||
};
|
||||
const effectiveAd = effectiveDefense(a, d, ad), effectiveDd = effectiveDefense(d, a, dd);
|
||||
|
||||
+58
-16
@@ -1,12 +1,13 @@
|
||||
import { ordinaryLeaderSkillModeSupported, type LeaderSkillProfile } from './leader-skill-profile';
|
||||
import { baseDeck, type Card } from './cards';
|
||||
import { baseDeck, ixDeck, type Card } from './cards';
|
||||
import type { BattleLeaderSkill } from './leader-skill-combat';
|
||||
import { isPortableSnooper, validBattleCardPair } from './battle-cards';
|
||||
|
||||
export type DiplomatDefenseQuote = {
|
||||
leader: string;
|
||||
cards: string[];
|
||||
source: string;
|
||||
kind: 'shield' | 'snooper';
|
||||
kind: 'shield' | 'snooper' | 'shieldSnooper' | 'weirdingWay' | 'chemistry';
|
||||
};
|
||||
|
||||
/** Planning eligibility does not inspect the opponent's unsubmitted cards. */
|
||||
@@ -22,45 +23,86 @@ export function diplomatTrainer(
|
||||
);
|
||||
}
|
||||
|
||||
const baseDefenses = new Map(
|
||||
baseDeck()
|
||||
/** Consumer-facing role label; hybrid and non-Shield printed roles stay distinct. */
|
||||
const DEFENSE_LABELS: Record<DiplomatDefenseQuote['kind'], string> = {
|
||||
shield: 'Projectile defense',
|
||||
snooper: 'Poison defense',
|
||||
shieldSnooper: 'Projectile and poison defense (Shield)',
|
||||
weirdingWay: 'Projectile defense (not a Shield)',
|
||||
chemistry: 'Poison defense (stops Poison Tooth)',
|
||||
};
|
||||
|
||||
export function diplomatDefenseLabel(kind: DiplomatDefenseQuote['kind']): string {
|
||||
return DEFENSE_LABELS[kind];
|
||||
}
|
||||
|
||||
/** Classify the used defensive role; the quote below checks the canonical face.
|
||||
* A sealed Portable Snooper has the ordinary poison-defense role. */
|
||||
export function copiedDefenseKind(
|
||||
card: Card | undefined,
|
||||
): DiplomatDefenseQuote['kind'] | null {
|
||||
if (!card) return null;
|
||||
if (isPortableSnooper(card)) return 'snooper';
|
||||
return Object.hasOwn(DEFENSE_LABELS, card.kind)
|
||||
? (card.kind as DiplomatDefenseQuote['kind'])
|
||||
: null;
|
||||
}
|
||||
|
||||
const baseCards = baseDeck();
|
||||
const baseDefenses: Partial<Record<string, Card>> = Object.fromEntries(
|
||||
baseCards
|
||||
.filter((card) => card.kind === 'shield' || card.kind === 'snooper')
|
||||
.map((card) => [card.id, card]),
|
||||
);
|
||||
|
||||
/** A copied role does not change either physical card or either sealed plan. */
|
||||
const printedDefenses: Partial<Record<string, Card>> = Object.fromEntries(
|
||||
[...baseCards, ...ixDeck()]
|
||||
.filter((card) => copiedDefenseKind(card))
|
||||
.map((card) => [card.id, card]),
|
||||
);
|
||||
|
||||
/** A copied role does not change either physical card or either sealed plan.
|
||||
* `expandedDefenses` defaults to true for new battles and direct callers; an
|
||||
* already-open version-1 battle passes false so it keeps exactly its original
|
||||
* canonical base Shield/Snooper sources, with no retroactive choice or pair
|
||||
* validation. */
|
||||
export function quoteDiplomatDefense(input: {
|
||||
assignments: readonly BattleLeaderSkill[];
|
||||
selectedLeader: string | null | undefined;
|
||||
weapon?: Card;
|
||||
defense?: Card;
|
||||
opposingDefense?: Card;
|
||||
opposingWeapon?: Card;
|
||||
expandedDefenses?: boolean;
|
||||
}): DiplomatDefenseQuote | null {
|
||||
const assignment = diplomatTrainer(input.assignments, input.selectedLeader);
|
||||
if (!assignment || (input.defense && input.defense.kind !== 'worthless'))
|
||||
return null;
|
||||
const expanded = input.expandedDefenses !== false;
|
||||
const source = input.opposingDefense;
|
||||
const printed = source && baseDefenses.get(source.id);
|
||||
const kind = copiedDefenseKind(source);
|
||||
const printed =
|
||||
source && (expanded ? printedDefenses[source.id] : baseDefenses[source.id]);
|
||||
if (
|
||||
!source ||
|
||||
!printed ||
|
||||
printed.kind !== source.kind ||
|
||||
printed.name !== source.name
|
||||
!kind ||
|
||||
(!(expanded && isPortableSnooper(source)) &&
|
||||
(!printed ||
|
||||
printed.kind !== source.kind ||
|
||||
printed.name !== source.name ||
|
||||
printed.effect !== source.effect)) ||
|
||||
(expanded && !validBattleCardPair(input.opposingWeapon, source))
|
||||
)
|
||||
return null;
|
||||
const cards = [input.weapon, input.defense]
|
||||
.filter((card): card is Card => card?.kind === 'worthless')
|
||||
.map((card) => card.id);
|
||||
if (!cards.length || new Set(cards).size !== cards.length) return null;
|
||||
return {
|
||||
leader: assignment.leader,
|
||||
cards,
|
||||
source: source.id,
|
||||
kind: source.kind as 'shield' | 'snooper',
|
||||
};
|
||||
return { leader: assignment.leader, cards, source: source.id, kind };
|
||||
}
|
||||
|
||||
/** Native skill inputs must already exclude dead or foreign-ghola trainers. */
|
||||
/** Copy the already-used role, not its original own-slot prerequisite or activation.
|
||||
* Native skill inputs must already exclude dead or foreign-ghola trainers. */
|
||||
export function copiedDiplomatDefense(
|
||||
quote: DiplomatDefenseQuote,
|
||||
card: string,
|
||||
|
||||
+21
-6
@@ -40,9 +40,10 @@ function validTurn(turn: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Custody only: the caller authorizes the faction power and resolves Karama first.
|
||||
* Blocking captured/ghola acquisition is an implementation boundary for unresolved
|
||||
* Moritani interactions, not a verified blanket restriction on the printed power.
|
||||
* The caller authorizes the faction power and resolves any applicable Karama.
|
||||
* Ecaz Nexus Cunning independently revives the set-aside shared disc from Tanks.
|
||||
* Captured/ghola acquisition remains an unresolved implementation boundary,
|
||||
* not a verified blanket restriction on the printed powers.
|
||||
*/
|
||||
export function acquireDuke(
|
||||
state: DukeState,
|
||||
@@ -55,13 +56,27 @@ export function acquireDuke(
|
||||
throw new Error('Choose a player to control Duke Vidal.');
|
||||
if (!['moritani', 'ecaz', 'ecazNexus', 'ally'].includes(source))
|
||||
throw new Error('Choose a valid source of Duke Vidal custody.');
|
||||
if (state.leader.dead)
|
||||
throw new Error('Duke Vidal is in the Tanks and cannot be acquired.');
|
||||
if (state.leader.dead && source !== 'ecazNexus')
|
||||
throw new Error('Duke Vidal is in the Tanks and cannot be acquired by this source.');
|
||||
if (state.leader.capturedBy || state.leader.gholaBy)
|
||||
throw new Error(
|
||||
'Acquiring Duke Vidal from captured or ghola custody is still being implemented.',
|
||||
);
|
||||
return { ...cloneDuke(state), controller, acquiredTurn: turn, source };
|
||||
if (state.leader.dead &&
|
||||
(state.controller !== null || state.acquiredTurn !== null || state.source !== null))
|
||||
throw new Error('Duke Vidal must be set aside in the Tanks before Ecaz Nexus acquisition.');
|
||||
if (source === 'ecazNexus' && !state.leader.dead && state.leader.usedAt)
|
||||
throw new Error('Duke Vidal has already been used in battle this turn.');
|
||||
const acquired = cloneDuke(state);
|
||||
if (state.leader.dead) {
|
||||
acquired.leader.dead = false;
|
||||
// An independent resurrection permits a new battle use without erasing deaths.
|
||||
delete acquired.leader.usedAt;
|
||||
}
|
||||
acquired.controller = controller;
|
||||
acquired.acquiredTurn = turn;
|
||||
acquired.source = source;
|
||||
return acquired;
|
||||
}
|
||||
|
||||
/** The battle caller resolves leader death and bounty before releasing temporary control. */
|
||||
|
||||
+9
-7
@@ -25,7 +25,7 @@ import { quoteSandmasterMovement, validateSandmasterMovement, sandmasterCollecti
|
||||
import { sandmasterWormCollection } from './sandmaster-worm';
|
||||
import { spiceBankerModeSupported, validateSpiceBankerSpend } from './spice-banker';
|
||||
import { BankerIncomeError, createBankerIncomeState, validateBankerIncomeState, quoteBankerIncome, commitBankerIncome, quoteBankerIncomeCollection, commitBankerIncomeCollection, projectBankerIncome, type BankerIncomeState, type BankerIncomeAuthority, type BankerIncomeContext } from './spice-banker-income';
|
||||
import { quoteDiplomatDefense, diplomatDefenseModeSupported, type DiplomatDefenseQuote } from './diplomat-defense';
|
||||
import { quoteDiplomatDefense, diplomatDefenseModeSupported, diplomatDefenseLabel, type DiplomatDefenseQuote } from './diplomat-defense';
|
||||
import { diplomatRetreatChoices, type DiplomatRetreatSelection, type DiplomatRetreatDestination } from './diplomat-retreat';
|
||||
import { ECAZ_START_FORCES, quoteEcazStartingForces } from './ecaz-setup';
|
||||
import { battleCardSlotEligible, battleCategoryInspectionValue, fixedBattleInspectionMatches, validBattleSlotPair } from './battle-card-slots';
|
||||
@@ -707,7 +707,7 @@ export type Battle = {
|
||||
mentatQuestionEvent?: string;
|
||||
mentatQuestion?: MentatQuestionReceipt;
|
||||
/** New battles opt into the revealed Diplomat step; legacy battles are not reopened. */
|
||||
diplomatDefenseVersion?: 1;
|
||||
diplomatDefenseVersion?: 1 | 2;
|
||||
diplomatDefenseEvent?: string;
|
||||
diplomatDefense?: DiplomatDefenseReceipt;
|
||||
/** New battles may pause after the outcome is known, before loser losses. */
|
||||
@@ -21021,6 +21021,8 @@ function diplomatDefenseOffer(g: Game): (DiplomatDefenseQuote & { player: string
|
||||
selectedLeader: plan.leader,
|
||||
weapon: cardOf(p, plan.weapon), defense: cardOf(p, plan.defense),
|
||||
opposingDefense: cardOf(other, opposingPlan.defense),
|
||||
opposingWeapon: cardOf(other, opposingPlan.weapon),
|
||||
expandedDefenses: b.diplomatDefenseVersion === 2,
|
||||
});
|
||||
if (quote) return { ...quote, player };
|
||||
}
|
||||
@@ -21043,7 +21045,7 @@ function diplomatDefenseIntegrity(g: Game) {
|
||||
const decisions = homeworldSavedDecisions(g).filter(d => d.kind === 'diplomatDefense');
|
||||
if (b?.diplomatDefenseVersion === undefined && b?.diplomatDefense === undefined &&
|
||||
b?.diplomatDefenseEvent === undefined && !decisions.length) return;
|
||||
requireRule(b && b.diplomatDefenseVersion === 1,
|
||||
requireRule(b && (b.diplomatDefenseVersion === 1 || b.diplomatDefenseVersion === 2),
|
||||
'The saved Diplomat choice lost its original battle version.');
|
||||
const receipt = b.diplomatDefense;
|
||||
const offer = diplomatDefenseOffer(g);
|
||||
@@ -21075,7 +21077,7 @@ function diplomatDefenseIntegrity(g: Game) {
|
||||
}
|
||||
function offerDiplomatDefense(g: Game): boolean {
|
||||
const b = g.battle!;
|
||||
if (b.diplomatDefenseVersion !== 1 || b.diplomatDefense) return false;
|
||||
if ((b.diplomatDefenseVersion !== 1 && b.diplomatDefenseVersion !== 2) || b.diplomatDefense) return false;
|
||||
const offer = diplomatDefenseOffer(g);
|
||||
if (!offer) return false;
|
||||
requireRule(b.event && !Object.keys(b.traitorCalls).length,
|
||||
@@ -21087,7 +21089,7 @@ function offerDiplomatDefense(g: Game): boolean {
|
||||
b.diplomatDefense = receipt;
|
||||
b.diplomatDefenseEvent = event;
|
||||
g.decision = { kind: 'diplomatDefense', player: offer.player, event, cards: [...offer.cards], source: offer.source };
|
||||
log(g, `${getPlayer(g, offer.player).name} may use Diplomat to make one committed Worthless card copy the opposing ${offer.kind === 'shield' ? 'Shield' : 'Snooper'} before traitor decisions.`,
|
||||
log(g, `${getPlayer(g, offer.player).name} may use Diplomat to make one committed Worthless card copy the opposing ${diplomatDefenseLabel(offer.kind)} before traitor decisions.`,
|
||||
{ faction: getPlayer(g, offer.player).faction, name: 'Diplomat defense' });
|
||||
return true;
|
||||
}
|
||||
@@ -29273,7 +29275,7 @@ function applyActionInner(
|
||||
receipt.stage = receipt.card ? 'copied' : 'declined';
|
||||
receipt.signature = diplomatDefenseSignature(receipt);
|
||||
log(g, receipt.card
|
||||
? `${p.name} used Diplomat: ${cardOf(p, receipt.card)!.name} copies the opposing ${receipt.kind === 'shield' ? 'Shield' : 'Snooper'} for this battle and must be discarded afterward. The original plans and physical card identities remain unchanged.`
|
||||
? `${p.name} used Diplomat: ${cardOf(p, receipt.card)!.name} copies the opposing ${diplomatDefenseLabel(receipt.kind)} for this battle and must be discarded afterward. The original plans and physical card identities remain unchanged.`
|
||||
: `${p.name} declined to copy the opposing defense with Diplomat.`,
|
||||
{ faction: p.faction, name: 'Diplomat defense' });
|
||||
nextRevealedDecision(g);
|
||||
@@ -31477,7 +31479,7 @@ function applyActionInner(
|
||||
event: battleEvent,
|
||||
...(g.leaderSkills ? { smugglerCollectionVersion: 1 as const } : {}),
|
||||
...(g.mentatQuestionPreview === true && g.leaderSkills && mentatQuestionModeSupported(g) ? { mentatQuestionVersion: 1 as const } : {}),
|
||||
...(g.leaderSkills && diplomatDefenseModeSupported(g) ? { diplomatDefenseVersion: 1 as const } : {}),
|
||||
...(g.leaderSkills && diplomatDefenseModeSupported(g) ? { diplomatDefenseVersion: 2 as const } : {}),
|
||||
...(g.leaderSkills && ordinaryLeaderSkillModeSupported(g) ? { diplomatRetreatVersion: 1 as const } : {}),
|
||||
...(g.ecazTreachery ? { harassAllocationVersion: 1 as const } : {}),
|
||||
...(byFaction(g, 'richese')
|
||||
|
||||
+11
-6
@@ -15,7 +15,7 @@ type DukeBefore = {
|
||||
controller: string | null;
|
||||
acquiredTurn: number | null;
|
||||
source: DukeSource | null;
|
||||
leader: { id: typeof DUKE_VIDAL_ID; dead: false; capturedBy: null; gholaBy: null };
|
||||
leader: { id: typeof DUKE_VIDAL_ID; dead: boolean; capturedBy: null; gholaBy: null };
|
||||
};
|
||||
|
||||
/** A completed physical card play, not a second copy of the Duke's current custody. */
|
||||
@@ -72,10 +72,13 @@ export function quoteNexusEcazDuke(g: Game, owner: string, automaticPending = fa
|
||||
blocked = 'Use Ecaz Nexus Cunning at the Battle phase boundary.';
|
||||
else if (nexusCleanPlayBlocked(g, automaticPending))
|
||||
blocked = 'Finish the current interaction before using Ecaz Nexus Cunning.';
|
||||
else if (!duke || duke.leader.id !== DUKE_VIDAL_ID || duke.leader.dead ||
|
||||
else if (!duke || duke.leader.id !== DUKE_VIDAL_ID ||
|
||||
duke.leader.capturedBy || duke.leader.gholaBy)
|
||||
blocked = 'Only a living, uncaptured, non-Ghola Duke Vidal can be acquired.';
|
||||
else if (duke.leader.usedAt)
|
||||
blocked = 'Only an uncaptured, non-Ghola Duke Vidal can be acquired.';
|
||||
else if (duke.leader.dead &&
|
||||
(duke.controller !== null || duke.acquiredTurn !== null || duke.source !== null))
|
||||
blocked = 'Duke Vidal must be set aside in the Tanks before Ecaz Nexus acquisition.';
|
||||
else if (!duke.leader.dead && duke.leader.usedAt)
|
||||
blocked = 'Duke Vidal has already been used in battle this turn.';
|
||||
else if (duke.controller === owner)
|
||||
blocked = 'Ecaz already controls Duke Vidal.';
|
||||
@@ -122,7 +125,7 @@ export function createNexusEcazDukeReceipt(
|
||||
turn: g.turn, phase: 6, sequence,
|
||||
before: {
|
||||
controller: duke.controller, acquiredTurn: duke.acquiredTurn, source: duke.source,
|
||||
leader: { id: DUKE_VIDAL_ID, dead: false, capturedBy: null, gholaBy: null },
|
||||
leader: { id: DUKE_VIDAL_ID, dead: duke.leader.dead, capturedBy: null, gholaBy: null },
|
||||
},
|
||||
after: { controller: after.controller!, acquiredTurn: after.acquiredTurn!, source: 'ecazNexus' },
|
||||
signature: '',
|
||||
@@ -161,8 +164,10 @@ export function validateNexusEcazDuke(
|
||||
!turn(receipt.turn) || receipt.turn > g.turn ||
|
||||
receipt.phase !== 6 || (receipt.turn === g.turn && g.phase < 6) ||
|
||||
!Number.isSafeInteger(index) || index < 0 || receipt.sequence !== index ||
|
||||
leader.id !== DUKE_VIDAL_ID || leader.dead !== false ||
|
||||
leader.id !== DUKE_VIDAL_ID || typeof leader.dead !== 'boolean' ||
|
||||
leader.capturedBy !== null || leader.gholaBy !== null ||
|
||||
(leader.dead &&
|
||||
(before.controller !== null || before.acquiredTurn !== null || before.source !== null)) ||
|
||||
!(before.controller === null ||
|
||||
g.players.some(player => player.id === before.controller)) ||
|
||||
(before.controller === null
|
||||
|
||||
+3
-3
@@ -4508,7 +4508,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'When Moritani is absent, a defeated combatant with an eligible publicly played card receives the same postbattle choice whether or not they hold Moritani Nexus. An unallied holder may spend it to retain one played card that a winner could keep; other played cards follow ordinary discard. Without a publicly eligible card there is no prompt. The choice is after the battle result and before later income/capture; the public prompt protects hidden custody and is an interface inference. This Secret Ally effect has no invented ordinary Karama response. Native Moritani allied retention remains a different, cancelable ability.',
|
||||
'With actual Advanced Stronghold Cards, Arrakeen bank support changes the real payer debit, not personal income. Emperor Cunning temporary Sardaukar remain physically ordinary counters and retain ordinary Tanks custody. Original losing Moritani retention precedes mandatory original-winner Tech, then CHOAM inspection excludes used opposing cards even if retained. Ordinary city Collection remains separate; none of these windows repeats the original support, card cost or casualties.',
|
||||
'Ecaz Betrayal is available to an unallied holder with Ecaz seated and reciprocally allied in a shared Arrakis territory. At the opening of Shipment and Movement, choose one such territory; the ally returns its entire normal and starred force group from every sector there to reserves, leaving Ecaz forces in place. The Nexus card is spent before a Karama response. Cancellation preserves the board but not the card. Only the base/Ecaz Nexus roster without combined modules is supported.',
|
||||
'Ecaz Cunning lets an unallied native holder spend its physical Nexus at a quiet Battle boundary to take the existing living, unclaimed Duke for this turn, even from Moritani. Original standalone OR paired Ecaz/Moritani/classic Nexus profiles admit this bounded effect with or without Skills and optional Homeworlds/Discovery/Tech/Advanced Strongholds. The one shared Duke remains separate from ordinary training and is set aside after use or at turn end. Advanced Harkonnen is independently excluded even where standalone no-Skills roster admission otherwise permits it. Captured, dead or foreign-Ghola Duke custody and unqualified reaction timing remain guarded; this Battle boundary is the existing product interpretation, not a printed phase restriction.',
|
||||
'Ecaz Cunning lets an unallied native holder spend its physical Nexus at a quiet Battle boundary to take the existing uncaptured, non-Ghola Duke for this turn, including a Moritani-controlled living disc or the set-aside disc in Tanks. Tanks acquisition preserves death history, revives the same disc and clears its old battle-use stamp under an explicit provisional independent-resurrection policy, without a paid revival charge or ordinary allowance. Original standalone OR paired Ecaz/Moritani/classic profiles keep their supported optional modules. The shared Duke stays outside ordinary training and is set aside after use or at turn end. Captured/Ghola custody, Advanced Harkonnen and unqualified reaction timing remain guarded; the quiet Battle boundary is product timing, not a printed restriction.',
|
||||
'Fremen Betrayal has two bounded advance-declaration alternatives for an unallied holder in a classic Nexus table. Before the first Spice Blow or worm appearance, spend the physical card to prevent Fremen ordinary and Cunning worm rides for the turn; worm survival, destruction and Nexus still resolve. This clean timing is a product boundary, not an official post-appearance priority ruling. Instead, before Fremen move during Shipment and Movement, spend it to suppress their native two-territory advantage for that turn while ordinary movement and independent ornithopters remain. Only one alternative can be used even if the card is recycled later that turn. Owner controls show the currently available choice; combined modules and reactive post-worm timing remain gated.',
|
||||
'When Ecaz is absent, an unallied holder may spend its Nexus card at a quiet play boundary to ask whether any one player, including themself, holds one of the holder’s native faction leaders as an ordinary Traitor Card. The owner receives a private yes/no answer saved at the moment of use; no name or count is disclosed. Dead or captured native leaders count, but foreign Gholas, Duke Vidal, Cheap Hero and Face Dancers do not. Native-leader scope and private-only audience are explicit product interpretations, not publisher rulings; exceptional controlled-leader and public-disclosure combinations remain open.',
|
||||
'When Richese is absent, its Secret Ally replaces your ordinary reserve shipment with up to five physical forces charged as one. Select the explicit shipment checkbox; forces, elite identity, destination restrictions, Homeworld reserve sources and normal movement remain unchanged. Fremen remain free within their normal reinforcement range; Guild and an active Karama rate retain their existing tariff and payment routing. This is not a concealed No-Field or an extra shipment.',
|
||||
@@ -4734,7 +4734,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Rihani normally inspects two random Traitor Deck cards privately, then reshuffles them. A surviving selected native Rihani also offers a separate optional draw of two. Decide before seeing them: once drawn, keep one new card and publicly reveal and return one unused old card. The other new card returns secretly. A captured Rihani grants only the optional exchange. Your private history records both samples; a card just called to win is already used.',
|
||||
'Smuggler can include one free accompanying force in an off-planet shipment into an empty territory. Select the entire physical total; three forces cost the ordinary price of two. The checkbox can decline this optional benefit. Guild rounding follows the free force. Richese may instead ship one separate free reserve force beside its owned No-Field into an empty territory; the marker retains its ordinary price and hidden value. Existing forces, including advisors, prevent the bonus; ordinary Fremen on-planet reinforcement has no bonus.',
|
||||
'Spice Banker can commit one through three own spice with its Battle Plan, separate from force support. Only the surviving selected skilled disc gains that strength. The amount stays private until authorized plan inspection or public reveal. It is paid on win or loss, including leader death, except a sole traitor-call winner spends nothing. Normal deferred income connects only in the separate fresh classic or supported native banker-income profiles, including the bounded Advanced Moritani roster; ordinary/default starts are unchanged.',
|
||||
'Diplomat may use one committed Worthless card as the opponent’s base Shield or Snooper if you played no defense. After both plans reveal, choose the card or decline before traitor decisions. A native Diplomat may protect its own selected trained leader this way; a captive grants no upper effect. The chosen Worthless must be discarded afterward, even if you win. A surviving trained leader that loses a supported battle may retreat selected undialed physical counters up to its printed/copied disc strength into one empty adjacent non-stronghold territory before ordinary loser losses; modified defenses remain unfinished.',
|
||||
'Diplomat may use one committed Worthless as the opponent’s canonical printed defensive role when you played no actual defense: Shield, Snooper, Shield Snooper, defensive Weirding Way or Chemistry, or sealed Portable Snooper. Hybrid protection, Weirding’s non-Shield behavior and Chemistry’s Poison Tooth exception remain distinct. Copying the used role without its activation or own-slot prerequisite is the explicit first-version interpretation. Choose or decline after reveal, before traitors; the Worthless must be discarded even if you win. Already-open base-only battles keep that source window. A native Diplomat can protect its own selected disc; a captive grants no upper band. A surviving trained loser may retreat selected undialed physical counters up to printed/copied disc strength to one empty adjacent non-stronghold before losses. Late/mirror/numeric modifications remain separate.',
|
||||
'Mentat has an explicit private developer preview for naming a specific weapon before posture and faction powers in supported classic and native skill profiles, including bounded Advanced Moritani. The target privately shows the named held card or chooses another held card. Weirding Way is a nameable default weapon; Chemistry is not. The card stays held and need not enter a Battle Plan. Every answer uses the same private response step; ordinary activation awaits the user’s timing-design decision. Empty hands skip without an answer. Private history is an observation, not a later hand guarantee.',
|
||||
'Bureaucrat may redirect two spice from a payment of at least five between two other players, once per phase. The payer still pays in full; the recipient receives two less. Declining keeps the use available for a later payment. Paid bribes qualify when transferred, with the remaining share still unavailable until Mentat Pause. Ordinary auctions, Guild income, Richese sales and actual paid Tleilaxu revival income in supported Basic or bounded Advanced native skill profiles have connected payment paths. A separate free-revival award is not part of the paid price and stays with Tleilaxu. Split funding, gifts, other payment families and combined modules remain unfinished.',
|
||||
'Smuggler records the existing battle-territory spice when plans reveal, capped by the selected leader’s unmodified strength. If that leader survives, it collects automatically even when losing or no forces remain. A captive pays its controller. Pending spice cannot fund the plan. Collection precedes Sandmaster’s addition; a drained pile receives none. Modified strength, multiple positive piles and combined modules remain guarded. Older saved battles keep their original behavior.',
|
||||
@@ -4744,7 +4744,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
],
|
||||
related: ['spice-banker-income', 'choam-leader-skills', 'ixian-leader-skills', 'tleilaxu-leader-skills', 'richese-leader-skills', 'ecaz-leader-skills', 'moritani-leader-skills', 'setup','choam-modules','implementation-checklist'],
|
||||
checklist: [
|
||||
{ area:'Implementation',status:'Partial',detail:'Exact fourteen-card custody, source-ordered setup, public capture, concealment, death and own revival connect five role bonuses, Planetologist movement/Special substitution and Suk Graduate casualty rescue. Rihani inspection/exchange and lower Mentat/Bureaucrat/Sandmaster effects are connected. Smuggler normal reserve shipping, the owned Richese No-Field companion, unmodified battle collection and optional Sandmaster ground-route, native Fremen worm-ride and physical Fremen Ambassador destination collection are connected. A separately opted-in Mentat preview connects private pre-plan questions and historical observations while ordinary activation awaits the uniform-response decision. Spice Banker lower spending and native Diplomat base Shield/Snooper copying are connected; skilled Diplomat retreat now transfers undialed normal/elite forces before losing casualties in supported battles. Normal Banker income connects only in the separate fresh classic or supported Basic native banker-income profile; other remaining bands and combined modules remain missing. Bureaucrat redirects supported third-party auction, shipment and bribe payments with exact saved once-per-phase use.',evidence:['game/leader-skill-cards.ts','game/leader-skills.ts','game/leader-skill-combat.ts','game/suk-graduate.ts','game/rihani-decipherer.ts','game/mentat-question.ts','game/bureaucrat-payment.ts','game/leader-skill-battle-board.ts','game/smuggler-shipment.ts','game/smuggler-no-field.ts','game/smuggler-battle.ts','game/spice-banker.ts','game/spice-banker-income.ts','game/sandmaster-movement.ts','game/sandmaster-worm.ts','game/fremen-ambassador.ts','game/diplomat-defense.ts','game/diplomat-retreat.ts','game/engine.ts'] },
|
||||
{ area:'Implementation',status:'Partial',detail:'Exact fourteen-card custody, source-ordered setup, public capture, concealment, death and own revival connect five role bonuses, Planetologist movement/Special substitution and Suk Graduate casualty rescue. Rihani inspection/exchange and lower Mentat/Bureaucrat/Sandmaster effects are connected. Smuggler normal reserve shipping, the owned Richese No-Field companion, unmodified battle collection and optional Sandmaster ground-route, native Fremen worm-ride and physical Fremen Ambassador destination collection are connected. A separately opted-in Mentat preview connects private pre-plan questions and historical observations while ordinary activation awaits the uniform-response decision. Spice Banker lower spending and native Diplomat canonical printed-defense copying are connected; skilled Diplomat retreat transfers undialed normal/elite forces before losing casualties. Normal Banker income connects only in its separate fresh profiles; remaining bands and combined modules stay open. Bureaucrat redirects supported third-party auction, shipment and bribe payments with saved once-per-phase use.',evidence:['game/leader-skill-cards.ts','game/leader-skills.ts','game/leader-skill-combat.ts','game/suk-graduate.ts','game/rihani-decipherer.ts','game/mentat-question.ts','game/bureaucrat-payment.ts','game/leader-skill-battle-board.ts','game/smuggler-shipment.ts','game/smuggler-no-field.ts','game/smuggler-battle.ts','game/spice-banker.ts','game/spice-banker-income.ts','game/sandmaster-movement.ts','game/sandmaster-worm.ts','game/fremen-ambassador.ts','game/diplomat-defense.ts','game/diplomat-retreat.ts','game/engine.ts'] },
|
||||
{ area:'Player controls',status:'Partial',detail:'Private card/leader selection, readable inspectors, public battle posture and revival draw/decline, Planetologist range/gather controls, Special weapon selection and Suk Graduate physical rescue choices are connected. Rihani has private history and separate keep-new/reveal-old controls; automatic battle effects need no confirmation. Smuggler has explicit ordinary shipping opt-out and No-Field companion opt-in with physical-versus-priced force counts, plus automatic surviving-leader battle collection with reveal-time guidance. Banker exposes separate sealed spending and authorized inspection. Sandmaster exposes explicit legal routes, per-territory collection choices and optional worm/physical Ambassador destination checkboxes. Diplomat offers a named committed Worthless choice or decline after public reveal, and a separate surviving-loser destination/normal/elite retreat or decline. Bureaucrat offers redirect or full payment with explicit recipient amounts and deferred bribe custody. Remaining effect controls are missing.',evidence:['components/leader-skills.tsx','components/planetologist-movement.tsx','components/suk-graduate.tsx','components/rihani-decipherer.tsx','components/mentat-question.tsx','components/bureaucrat-payment.tsx','components/leader-skill-battle-guide.tsx','components/smuggler-shipment.tsx','components/spice-banker.tsx','components/sandmaster-movement.tsx','components/sandmaster-worm.tsx','components/ambassador-movement.tsx','components/diplomat-defense.tsx','components/diplomat-retreat.tsx','components/game-table.tsx'] },
|
||||
{ area:'AI',status:'Partial',detail:'All four profiles use private offers, legal visibility/revival actions and Planetologist movement/battle candidates and legal Suk Graduate rescues, Rihani exchanges, shared battle scoring, Smuggler reserve/owned No-Field shipments and unmodified collection plans, Sandmaster route, worm and physical Ambassador destination collection, funded Banker commitments and optional Diplomat copying from public revealed defenses or choosing legal losing-battle retreat. Bureaucrat uses the public payee alliance to redirect enemy income or preserve allied income. These are legal prototype paths; complete skill strategy and difficulty calibration remain unfinished.',evidence:['game/bots.ts'] },
|
||||
{ area:'Documentation',status:'Partial',detail:'All fourteen faces and common rules are recorded. Skilled capture follows public physical-card custody; replacement entitlement and other material timing interpretations remain explicit.',evidence:['docs/LEADER_SKILLS_RULES.md','docs/LEADER_SKILLS_CAPTURE.md','docs/LEADER_SKILLS_RUNTIME.md','docs/PLANETOLOGIST_RULES.md','docs/SUK_GRADUATE_RULES.md','docs/LEADER_BATTLE_EFFECTS.md','docs/MENTAT_QUESTION.md','docs/BUREAUCRAT_PAYMENTS.md','docs/SMUGGLER_SHIPMENT.md','docs/SPICE_BANKER_RUNTIME.md','docs/SANDMASTER_WORM.md','docs/DIPLOMAT_DEFENSE.md'] },
|
||||
|
||||
Reference in new issue
Block a user