Prototype modifier-aware Smuggler battle collection

This commit is contained in:
Quintine committed 2026-10-17 11:33:07 +11:00
1 parent d6d71c7c02
commit 63298be1e1
22 files changed
+836 -84

No files matched your search

+1 -2
View File
@@ -3,7 +3,7 @@ import { ecazOccupyDialChoices, ecazOccupyOwnProfile, ecazOccupyPolicyActions }
import { quoteEcazOccupyDial } from './ecaz-occupy-battle';
import { recruitsPlayAction } from './recruits';
import type { Card } from './cards';
import { bureaucratBattlePenalty, canUsePlanetologistBattleSpecial, leaderSkillBattleBonus, usesSurvivingSkilledLeader } from './leader-skill-combat';
import { bureaucratBattlePenalty, canUsePlanetologistBattleSpecial, leaderSkillBattleBonus } from './leader-skill-combat';
import { defaultSmugglerBattleAllocation, smugglerBattlePlanBlock } from './smuggler-battle';
import { battleCardSlotEligible, battleCategoryInspectionValue, fixedBattleInspectionMatches, validBattleSlotPair, type BattlePlanInspectionField } from './battle-card-slots';
import { mirrorWeaponModeBlock } from './mirror-weapon-mode';
@@ -791,7 +791,6 @@ function plans(g: GameView): Action[] {
for (const defense of defenses) {
const kwisatz =
!!leader &&
!(b.smugglerCollectionEnabled && usesSurvivingSkilledLeader(battleSkills, 'smuggler', leader, true)) &&
!!me.kwisatz?.active &&
!me.kwisatz.dead &&
!b.kwisatzBlocked &&
+4 -4
View File
@@ -23048,16 +23048,16 @@ function currentBattleResolutionQuote(g: Game, canceledVoter?: string) {
(nativeExpansionLeaderSkillsProfile(g) || g.players.every((p) => faction(p.faction).expansion === 'base')),
'These Leader Skill victory effects with expansion factions or other optional modules are still being integrated.');
const collection = b.smugglerCollection;
const survives = !!collection &&
const collects = !!collection && !quote.effects?.stunned &&
!(collection.player === b.attacker ? quote.leaderDeaths.attacker : quote.leaderDeaths.defender);
const smugglerAllocation: Extract<Decision, {kind: 'smugglerCollection'}> | null =
collection && smugglerBattleAllocationRequired(collection, survives)
collection && smugglerBattleAllocationRequired(collection, collects)
? { kind: 'smugglerCollection', event: b.event!, player: collection.player,
territory: b.territory, amount: collection.amount, piles: collection.piles! }
: null;
const smuggler = collection && !smugglerAllocation
? nexusRule(() => settleSmugglerBattle(collection, survives, g.spice)) : null;
if (collection && survives)
? nexusRule(() => settleSmugglerBattle(collection, collects, g.spice)) : null;
if (collection && collects)
requireRule(Number.isSafeInteger(getPlayer(g, collection.player).spice + collection.amount),
'Smuggler collection would overflow the spice balance.');
const afterCollection = smuggler?.spice ?? g.spice;
+6 -6
View File
@@ -4801,7 +4801,7 @@ export const RULE_TOPICS: RuleTopic[] = [
steps: [
'Basic Tleilaxu with base opponents retains its existing full skill deck. Fresh Advanced native Tleilaxu, optionally Ixians and/or CHOAM, plus classic tables use distinct required Ix/CHOAM decks and all fourteen skills. Starting choices, actual traitors and three native Face Dancers remain. Unrelated overlays and public starts stay gated.',
'Rihani draws become unrevealed Face Dancers. Keep one new card by revealing and returning one unrevealed old card; the other new card returns privately. Only the owner sees the inspection history and new identities.',
'A successful own-leader revival preserves the optional skill draw after revival responses. Winner skill and card cleanup precede Face Dance; a killed skilled leader returns its card once. Zoal copies the opposing disc value for unmodified Smuggler collection.',
'A successful own-leader revival preserves the optional skill draw after revival responses. Winner skill and card cleanup precede Face Dance; a killed skilled leader returns its card once. Zoal copies the opposing disc value before the shared provisional Smuggler modifier cap.',
'Actual foreign revival pays its native half price and fills the controlled living pool only up to five, but never draws or assigns a skill. The original faction keeps the sole physical disc; Tleilaxu controls it. A native trainer can supply its normal role bonus to that different selected disc, not its trained lower bonus. Own revival still offers its eligible optional draw. Auditor and the Ecaz-only Duke/Ghola exclusions remain.',
'Foreign death and negotiated original-owner buyback use original handlers. Harkonnen capture temporarily overrides Tleilaxu control without moving the disc into another physical roster. Its actual plan reveal removes the concealed snapshot; surviving use returns the ghola to Tleilaxu. Execution preserves the original dead-ghola return path. No skill attaches to a foreign disc.',
'Other unfinished skill effects retain their stated limits. This development combination does not certify the full expansion or every optional-module interaction.',
@@ -4925,7 +4925,7 @@ export const RULE_TOPICS: RuleTopic[] = [
category: 'Advanced & expansions',
coverage: 'Partial',
developmentStage: 'Prototyped',
summary: 'Fourteen physical skills, private assignment choices, five battle disciplines, Planetologist movement/battle effects and Suk Graduate rescues, Mentat/Bureaucrat scoring and Bureaucrat payment redirection, Sandmaster victory spice, Rihani exchanges, optional Smuggler shipping and unmodified battle collection, Sandmaster route and worm-ride collection, sealed Spice Banker battle spending and optional Diplomat base-defense copying in development tables. Other effects and combined-module acceptance remain unfinished.',
summary: 'Fourteen physical skills, private assignment choices, five battle disciplines, Planetologist movement/battle effects and Suk Graduate rescues, Mentat/Bureaucrat scoring and Bureaucrat payment redirection, Sandmaster victory spice, Rihani exchanges, optional Smuggler shipping and provisional modifier-inclusive battle collection, Sandmaster route and worm-ride collection, sealed Spice Banker battle spending and optional Diplomat base-defense copying in development tables. Other effects and combined-module acceptance remain unfinished.',
steps: [
'Fresh all14 Leader Skills preserves original classic, supported E1/E2 or standalone Ecaz OR Moritani setup with Homeworlds/Discovery/Tech3+/Advanced Strongholds under each envelope. Explicit --nexus-cards additionally admits single/paired one-family E1/E2, BOTH selected Ix+CHOAM native families/union47 and standalone OR paired Ecaz/Moritani/ecaz33 with classics. Basic/Advanced2–6, original Ix pre-training offer, ordinary native discs and actual hands remain; Auditor/shared Duke never train. Winning normal/trained Suk uses source-labelled native, visitor or nested fixed-loss pools; native saved counters stay at that home and external Emperor returns allocate original homes. Original phase-end industry, held benefits, cleanup/Tech before Face Dance and native assassination remain; unreconciled bands, mixed E3/three-family, public starts and played-save conversion stay separate.',
'Explicit guild-betrayal --leader-skills retains the original classic native-Guild Nexus profile without Homeworlds, Tech, Strongholds, Discovery, Semuta or other overlays. All14 training remains native. Ordinary skill quotes now admit the composition, restoring actual Mentat private questions before posture and Sandmaster ground collection rather than only enabling Bureaucrat. Native card custody, paid forces, original Betrayal Use/Pass and movement/plan continuation stay unchanged. Other combinations and complete-mode acceptance remain separate.',
@@ -4955,18 +4955,18 @@ export const RULE_TOPICS: RuleTopic[] = [
'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.',
'Normal Mentat questions use the user-approved uniform private card-show step before posture and faction powers in supported Leader Skills games. The opponent shows the named held weapon or chooses another held card, even for an exact match or sole eligible card. Weirding Way is a nameable default weapon; Chemistry is not. Cards stay held and need not enter a Battle Plan. New battles activate the question without an extra opt-in; already-opened unversioned battles retain their original flow. Empty hands skip without an answer. Private history records an observation, not a later hand guarantee.',
'Bureaucrat may redirect two spice when one other player pays at least five to a player other than the skill owner, once per phase. Payer costs stay unchanged; declining preserves later use. Bribes qualify at transfer and remain unavailable until Mentat Pause. Supported ordinary/Richese auctions, Guild shipments and paid Tleilaxu revival have connected paths; a separate free-revival award cannot make a smaller price qualify. Allied payer shares never pool to reach five. Trainer or recipient contributions cannot qualify but do not hide an eligible other payer. Public prompts show gross and recipient, not private shares or which payer qualified. Supported classic and native E1/E2/E3 profiles and Homeworld Skills retain original costs and callbacks. Native Guild/Emperor/revival Karama cancellation resolves before Bureaucrat; Richese retains direct seller credit with no invented cancellation window. Basic has no Black Market; free Karama purchases remain normal-only. By user ruling Bureaucrat takes two off gross before occupied Homeworld allocation. Fresh classic occupation plus Skills now connects this: eight becomes two to Bank, three native and three occupied, with full original payer contributions retained. Decline gives four plus four; native income cancellation preserves the separate occupied share and creates no Bureaucrat offer. The low/occupied Guild two-odd contributor rounding boundary is unchanged. By user ruling Guild Nexus Betrayal also carries Bureaucrat on its holder receipt in the bounded classic Nexus plus Skills composition; the holder receives the fee minus two. Emperor gifts remain excluded by user ruling, without threshold disclosure. Other profiles, overlays and full-mode acceptance remain unfinished.',
'Smuggler records all existing positive battle-territory piles when plans reveal, capped in total by the selected leader’s unmodified strength. A surviving controller collects even when losing or no forces remain. A partial collection across several piles asks that owner for exact sector amounts; all/none and single-pile collection stay automatic. This allocation policy is provisional. Pending spice cannot fund the plan. Collection precedes Sandmaster addition; a drained pile receives none. Modified strength and other combined-module limits remain guarded. Older saved battles retain their recorded behavior.',
'Smuggler records existing positive battle-territory piles at public reveal. Provisional modifier policy: cap by copied/printed disc plus declared Leader Skill bonuses and Kwisatz2. Pending spice cannot fund plans. A surviving controller collects even when losing or no forces remain, unless an actual zero-strength weapon effect resolves. Artillery Strike supplies that effect; the ordinary projectile Stunner does not. A sole own Traitor bypasses weapons and retains collection. Partial multi-pile collection asks its owner for exact sectors; all/none and single piles stay automatic. Both the modifier and allocation policies are explicit interpretations, not publisher rulings. Collection precedes Sandmaster; a drained pile receives none. Other module guards remain.',
'In classic and supported E1/E2 native Tech composition, actual Smuggler prices and normal Banker qualifying fees remain distinct from phase-end Tech income. Tech piles cannot fund their triggering action; Banker front-shield grants still wait for actual Mentat. Winning physical Suk rescue and Sandmaster board spice precede original played-card cleanup and mandatory winner token reward. One token transfers automatically, several require a selection, and no token skips without an invented decline. A later Face Dancer retains the original winner’s cards, bounty and Tech reward, kills the matching trainer without another bounty and replaces only actual survivors from real sources. Native Tleilaxu-only free revival excludes Axlotl; CHOAM initial income is not Charity. Richese’s free companion does not change its marker price or create the concealed counters early.',
'Physical Fremen Ambassador relocation now offers optional Sandmaster destination collection before arrival reactions, without an invented route or ordinary movement use. Same-territory repositioning earns none; concealed relocation and other special movement still retain their own limits. Normal Banker income has separate fresh classic or supported native source-to-Mentat previews with provisional original-earned-faction custody, not general activation. Diplomat retreat retains its documented conservative outcome order. Native Advanced foreign gholas/Face Dancers, skill-first Moritani assassination and ordinary-disc Ecaz skills follow their bounded contracts; shared-Duke assignment, combined Occupy skills and other combined modules remain unfinished. This prototype does not enable public module starts.',
...LEADER_SKILL_CARDS.map(card => `${card.name}. Normal: ${card.normal.join(' ')} Skilled battle: ${card.battle.join(' ')}`),
],
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 shipping, No-Field accompaniment and unmodified battle collection, plus Sandmaster ground, worm, physical Ambassador and native HMS allocation are connected. Normal Mentat private questions and historical observations use the user-approved uniform step in new supported battles without an extra opt-in. Spice Banker lower spending and native Diplomat printed-defense copying are connected; trained retreat transfers undialed forces before losing casualties. Normal Banker income remains a separate profile; other combined interactions stay open. Bureaucrat redirects supported third-party payments with saved once-per-phase use, while user-classified gifts stay outside payment handling.',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 shipping, No-Field accompaniment and provisional modifier-inclusive battle collection, plus Sandmaster ground, worm, physical Ambassador and native HMS allocation are connected. Normal Mentat private questions and historical observations use the user-approved uniform step in new supported battles without an extra opt-in. Spice Banker lower spending and native Diplomat printed-defense copying are connected; trained retreat transfers undialed forces before losing casualties. Normal Banker income remains a separate profile; other combined interactions stay open. Bureaucrat redirects supported third-party payments with saved once-per-phase use, while user-classified gifts stay outside payment handling.',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 surviving-leader battle collection with reveal-time guidance and owned multi-pile allocations. Banker exposes separate sealed spending and authorized inspection. Sandmaster exposes explicit legal routes, per-territory collection choices, optional worm/physical Ambassador destination checkboxes and direct victory-pile placement when several piles remain. 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/smuggler-battle-collection.tsx','components/spice-banker.tsx','components/sandmaster-movement.tsx','components/sandmaster-worm.tsx','components/sandmaster-victory-spice.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, exact multi-pile Smuggler allocations, Sandmaster route, worm, physical Ambassador destination collection and victory-pile placement, 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:'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 modifier-inclusive collection plans, exact multi-pile Smuggler allocations, Sandmaster route, worm, physical Ambassador destination collection and victory-pile placement, 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. The 13 October 2026 ruling resolves the captured-card replacement question: the original assignment is retained and no replacement is drawn while it exists; 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'] },
{ area:'Verification',status:'Partial',detail:'Classic Homeworld Nexus optional modules add13 meaningful cases; affected242/242, types/lint/build pass. Twenty-two original six-seat games across15 families finish14233/14233 actions with375 JSON on one unchanged source tree. Human390px original typed invasion/zero-Suk and exact-three native return retain Kaitain14/Salusa5/visitor1; actual random Shrine entry, typed Maker ride and both-pile closing Moritani draw retain original armies. Native35-case and earlier source-qualified evidence remains in canonical logs. Native compositions, pending effects, full rules/combinations and deployed acceptance remain open.',evidence:['tests/homeworld-classic-nexus-skills-runtime.test.ts','tests/homeworld-classic-discovery-nexus-runtime.test.ts','tests/homeworld-native-e1e2-modules-runtime.test.ts','tests/homeworld-native-e3-modules-runtime.test.ts','tests/homeworld-classic-skills-runtime.test.ts','tests/homeworld-skills-discovery-runtime.test.ts','tests/homeworld-optional-modules-runtime.test.ts','tests/leader-skills-engine.test.ts','tests/leader-skills-capture.test.ts','tests/captured-skill-revival.test.ts','tests/fixture-captured-skill-revival.ts','tests/captured-skill-special-revival.test.ts','tests/fixture-captured-skill-special-revival.ts','tests/leader-battle-effects.test.ts','tests/suk-graduate.test.ts','tests/mentat-question.test.ts','tests/bureaucrat-payment.test.ts','tests/smuggler-battle.test.ts','tests/spice-banker.test.ts','tests/advanced-native-skills.test.ts','tests/fixture-advanced-native-skills.ts','tests/skills-tech-payments-runtime.test.ts','tests/skills-tech-battle-runtime.test.ts','tests/ix-choam-skills-tech-runtime.test.ts','tests/tleilaxu-skills-tech-runtime.test.ts','tests/richese-skills-tech-runtime.test.ts','tests/classic-skills-stronghold-runtime.test.ts','tests/native-skills-stronghold-runtime.test.ts','tests/ecaz-skills-modules-runtime.test.ts','tests/moritani-skills-modules-runtime.test.ts','docs/LEADER_SKILLS_RUNTIME.md','docs/HOMEWORLD_RUNTIME_IMPLEMENTATION.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Earlier native/optional-module and recovery evidence remains dated in linked contracts. Modified Smuggler adds actual seven-loss KH activation, short/ample reveal caps, shielded Artillery zeroing, sole-own-Traitor precedence and ordinary Stunner collection. Focused23 cases pass. Four390px journeys/32 actions retain winner cleanup and separate Collection; eight four-policy programmes/64 actions use offered modified KH plans. Provisional policy is not a publisher ruling or full-mode/deployed acceptance.', evidence: ['tests/smuggler-kwisatz-journey.test.ts', 'tests/fixture-smuggler-kwisatz-journey.ts', 'tests/smuggler-artillery-journey.test.ts', 'tests/fixture-smuggler-artillery-journey.ts', 'docs/SMUGGLER_BATTLE.md', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts','tests/homeworld-native-e1e2-modules-runtime.test.ts','tests/homeworld-native-e3-modules-runtime.test.ts','tests/homeworld-classic-skills-runtime.test.ts','tests/homeworld-skills-discovery-runtime.test.ts','tests/homeworld-optional-modules-runtime.test.ts','tests/leader-skills-engine.test.ts','tests/leader-skills-capture.test.ts','tests/captured-skill-revival.test.ts','tests/fixture-captured-skill-revival.ts','tests/captured-skill-special-revival.test.ts','tests/fixture-captured-skill-special-revival.ts','tests/leader-battle-effects.test.ts','tests/suk-graduate.test.ts','tests/mentat-question.test.ts','tests/bureaucrat-payment.test.ts','tests/smuggler-battle.test.ts','tests/spice-banker.test.ts','tests/advanced-native-skills.test.ts','tests/fixture-advanced-native-skills.ts','tests/skills-tech-payments-runtime.test.ts','tests/skills-tech-battle-runtime.test.ts','tests/ix-choam-skills-tech-runtime.test.ts','tests/tleilaxu-skills-tech-runtime.test.ts','tests/richese-skills-tech-runtime.test.ts','tests/classic-skills-stronghold-runtime.test.ts','tests/native-skills-stronghold-runtime.test.ts','tests/ecaz-skills-modules-runtime.test.ts','tests/moritani-skills-modules-runtime.test.ts','docs/LEADER_SKILLS_RUNTIME.md','docs/HOMEWORLD_RUNTIME_IMPLEMENTATION.md'] },
],
},
{
+17 -15
View File
@@ -39,10 +39,6 @@ export function smugglerBattlePlanBlock(plan: SmugglerBattlePlan, supported: boo
pile?: { territory: string; spice: Readonly<Record<string, number>> }): string | null {
if (!usesSurvivingSkilledLeader(plan.assignments, 'smuggler', plan.leader?.id, true)) return null;
if (!supported) return 'Smuggler battle collection with expansion factions or other optional modules is still being integrated.';
if (plan.kwisatz || leaderSkillBattleBonus({ assignments: plan.assignments,
selectedLeader: { id: plan.leader!.id, kind: 'disc' }, weapon: plan.weapon,
defense: plan.defense, skilledLeaderSurvives: true }).bonus)
return 'Smuggler collection with modified leader strength awaits its ruling. Choose an unmodified plan or another leader.';
if (pile) {
try { smugglerBattlePile(pile.territory, pile.spice); }
catch (error) { return error instanceof Error ? error.message : 'Smuggler spice is unavailable.'; }
@@ -74,9 +70,15 @@ export function createSmugglerBattle(input: {
if (!usesSurvivingSkilledLeader(input.plan.assignments, 'smuggler', input.plan.leader?.id, true)) return null;
const blocked = smugglerBattlePlanBlock(input.plan, input.supported);
if (blocked) throw new Error(blocked);
const strength = input.plan.leader!.strength;
// Provisional reveal-time cap: copied disc value plus the existing leader
// bonuses and Kwisatz. Actual weapon zeroing is applied with survival.
const strength = input.plan.leader!.strength + leaderSkillBattleBonus({
assignments: input.plan.assignments,
selectedLeader: { id: input.plan.leader!.id, kind: 'disc' },
weapon: input.plan.weapon, defense: input.plan.defense, skilledLeaderSurvives: true,
}).bonus + (input.plan.kwisatz ? 2 : 0);
if (!input.event || !input.player || !Number.isSafeInteger(input.turn) || input.turn < 1 ||
!Number.isSafeInteger(strength) || strength < 0) throw new Error('Smuggler needs the current battle and an unmodified leader.');
!Number.isSafeInteger(strength) || strength < 0) throw new Error('Smuggler needs the current battle and a valid leader strength.');
const pile = smugglerBattlePile(input.territory, input.spice);
const receipt: SmugglerBattleReceipt = { event: input.event, turn: input.turn,
territory: input.territory, player: input.player, leader: input.plan.leader!.id,
@@ -116,9 +118,9 @@ function normalizeSmugglerBattleAllocation(receipt: SmugglerBattleReceipt,
return Object.fromEntries(Object.entries(allocations).filter(([, amount]) => amount > 0)
.sort(([a], [b]) => a.localeCompare(b)));
}
/** Only a surviving leader's partial collection from multiple piles needs an owner choice. */
export function smugglerBattleAllocationRequired(receipt: SmugglerBattleReceipt, survives: boolean): boolean {
return survives && receipt.stage === 'pending' && receipt.allocations === undefined &&
/** A partial multi-pile collection needs a choice only when collection is eligible. */
export function smugglerBattleAllocationRequired(receipt: SmugglerBattleReceipt, eligible: boolean): boolean {
return eligible && receipt.stage === 'pending' && receipt.allocations === undefined &&
Object.keys(recordedPiles(receipt)).length > 1 && receipt.amount > 0 && receipt.amount < receipt.before;
}
export function chooseSmugglerBattleAllocation(receipt: SmugglerBattleReceipt,
@@ -130,21 +132,21 @@ export function chooseSmugglerBattleAllocation(receipt: SmugglerBattleReceipt,
chosen.signature = smugglerBattleSignature(chosen);
return chosen;
}
/** Survival controls both winning and losing leaders; this is not bank/player income. */
export function settleSmugglerBattle(receipt: SmugglerBattleReceipt, survives: boolean, spice: Readonly<Record<string, number>>) {
/** Eligibility requires survival without a resolved zero-strength weapon effect. */
export function settleSmugglerBattle(receipt: SmugglerBattleReceipt, eligible: boolean, spice: Readonly<Record<string, number>>) {
validateSmugglerBattleReceipt(receipt);
const snapshot = smugglerBattlePile(receipt.territory, spice);
const expected = smugglerBattlePile(receipt.territory, recordedPiles(receipt));
if (JSON.stringify(snapshot) !== JSON.stringify(expected))
throw new Error('The saved Smuggler collection changed its reveal-time spice or receipt.');
if (smugglerBattleAllocationRequired(receipt, survives))
if (smugglerBattleAllocationRequired(receipt, eligible))
throw new Error('Choose the Smuggler collection sectors before settling the surviving leader.');
const amount = survives ? receipt.amount : 0;
const allocations = !survives || !amount ? {} : receipt.allocations ??
const amount = eligible ? receipt.amount : 0;
const allocations = !eligible || !amount ? {} : receipt.allocations ??
(receipt.key ? { [receipt.key]: amount } : recordedPiles(receipt));
const remaining = { ...spice };
for (const [key, collected] of Object.entries(allocations)) remaining[key] -= collected;
const settled: SmugglerBattleReceipt = { ...receipt, stage: survives ? 'collected' : 'void', signature: '' };
const settled: SmugglerBattleReceipt = { ...receipt, stage: eligible ? 'collected' : 'void', signature: '' };
settled.signature = smugglerBattleSignature(settled);
return { receipt: settled, amount, spice: remaining };
}