Compose native Advanced Moritani skills with skill-first assassination
This commit is contained in:
1 parent
cb1f81977e
commit
d41a7e03a9
18 files changed
+1122
-88
No files matched your search
+52
-18
@@ -1,4 +1,4 @@
|
||||
import { advancedNativeLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, ordinaryLeaderSkillModeSupported } from './leader-skill-profile';
|
||||
import { advancedMoritaniLeaderSkillsProfile, advancedNativeLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, ordinaryLeaderSkillModeSupported } from './leader-skill-profile';
|
||||
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { quoteSpicePlacement, stormExposesTerritory, stormSectorAfter, wormConsumesForces } from './disaster-rules';
|
||||
import { isStormCardDistance, type StormCardComponent } from './storm-cards';
|
||||
@@ -7510,7 +7510,7 @@ function moritaniAssassinateModeSupported(g: Game) {
|
||||
return g.advanced && g.expansions.length === 1 && g.expansions[0] === 'ecaz' &&
|
||||
g.players.some(p => p.faction === 'moritani') &&
|
||||
g.players.every(p => ['moritani','atreides','beneGesserit','guild','emperor','fremen'].includes(p.faction)) &&
|
||||
!g.nexusCards && !g.leaderSkills && !g.strongholdCards && !g.homeworlds &&
|
||||
!g.nexusCards && (!g.leaderSkills || advancedMoritaniLeaderSkillsProfile(g)) && !g.strongholdCards && !g.homeworlds &&
|
||||
!g.discoveryEnabled && !g.discoveries && !g.techTokens;
|
||||
}
|
||||
function moritaniAssassinateContext(g: Game, receipt: MoritaniAssassinateReceipt) {
|
||||
@@ -7541,7 +7541,7 @@ function moritaniAssassinateIntegrity(g: Game) {
|
||||
receipt.owner === state.owner && TERRITORIES.some(t => t.id === receipt.territory),
|
||||
'The assassination history no longer matches its original battle participants.');
|
||||
}
|
||||
const pending = state.opportunities.filter(r => r.stage === 'choice');
|
||||
const pending = state.opportunities.filter(r => r.stage === 'skills' || r.stage === 'choice');
|
||||
requireRule(pending.length <= 1, 'Finish the current assassination opportunity first.');
|
||||
const obligation = g.lastBattleContext?.moritaniAssassinate;
|
||||
if (obligation) {
|
||||
@@ -7555,15 +7555,25 @@ function moritaniAssassinateIntegrity(g: Game) {
|
||||
g.lastBattleContext?.event === r.event && g.lastBattleContext.winner === r.opponent &&
|
||||
g.lastBattleContext.result === 'normal' && g.lastBattleContext.territory === r.territory &&
|
||||
obligation?.event === r.event, 'The assassination choice lost its resolved battle.');
|
||||
requireRule(resume && resume.event === r.event && resume.continuation.event === r.event &&
|
||||
resume.continuation.winner === r.opponent && resume.continuation.territory === r.territory &&
|
||||
resume.continuation.result === 'normal' && JSON.stringify(resume.continuation.combatants) === JSON.stringify(g.lastBattleContext.combatants) &&
|
||||
resume.signature === JSON.stringify(resume.continuation) && resume.signature === obligation.continuation,
|
||||
'The assassination choice lost or changed its saved cleanup.');
|
||||
const decisions = homeworldSavedDecisions(g).filter(d => d.kind === 'moritaniAssassinate');
|
||||
requireRule(decisions.length === 1 && decisions[0].player === r.owner &&
|
||||
(decisions[0] as Extract<Decision,{kind:'moritaniAssassinate'}>).event === r.event,
|
||||
'The assassination choice lost its owning decision.');
|
||||
if (r.stage === 'skills') {
|
||||
const discard = g.pendingTreacheryDiscard?.continuation;
|
||||
const decision = g.decision;
|
||||
requireRule(g.leaderSkills && !resume &&
|
||||
((discard?.kind === 'battleResolved' && discard.event === r.event) ||
|
||||
(decision?.player === r.opponent &&
|
||||
(decision.kind === 'battleLosses' || decision.kind === 'sukRescue' || decision.kind === 'rihani'))),
|
||||
'Assassination must retain the winner’s original skill and casualty continuation.');
|
||||
} else {
|
||||
requireRule(resume && resume.event === r.event && resume.continuation.event === r.event &&
|
||||
resume.continuation.winner === r.opponent && resume.continuation.territory === r.territory &&
|
||||
resume.continuation.result === 'normal' && JSON.stringify(resume.continuation.combatants) === JSON.stringify(g.lastBattleContext.combatants) &&
|
||||
resume.signature === JSON.stringify(resume.continuation) && resume.signature === obligation.continuation,
|
||||
'The assassination choice lost or changed its saved cleanup.');
|
||||
const decisions = homeworldSavedDecisions(g).filter(d => d.kind === 'moritaniAssassinate');
|
||||
requireRule(decisions.length === 1 && decisions[0].player === r.owner &&
|
||||
(decisions[0] as Extract<Decision,{kind:'moritaniAssassinate'}>).event === r.event,
|
||||
'The assassination choice lost its owning decision.');
|
||||
}
|
||||
} else requireRule(!resume && g.decision?.kind !== 'moritaniAssassinate', 'A settled assassination cannot retain its decision.');
|
||||
if (g.status === 'playing' || g.status === 'finished') {
|
||||
const retired = state.opportunities.filter(r => r.stage === 'replaced').map(r => r.card!);
|
||||
@@ -7601,6 +7611,7 @@ function recordMoritaniAssassinateOpportunity(g: Game, b: Battle, winner: Player
|
||||
traitorCalled:Object.values(b.traitorCalls).some(Boolean)};
|
||||
if (!moritaniAssassinateTrigger(trigger)) return;
|
||||
const receipt = createMoritaniAssassinateOpportunity(trigger);
|
||||
if (g.leaderSkills) receipt.stage = 'skills';
|
||||
state.opportunities.push(receipt);
|
||||
syncMoritaniAssassinateReceipt(g,receipt);
|
||||
}
|
||||
@@ -7816,7 +7827,7 @@ function bankerIncomeProfile(g: Game): boolean {
|
||||
!g.nexusCards && !g.homeworlds && !g.techTokens &&
|
||||
!g.strongholdCards && !g.discoveryEnabled && !g.discoveries && !g.discoveryStash &&
|
||||
!g.greatMaker && !g.ecazTreachery && !g.semutaPreview && !g.mentatQuestionPreview &&
|
||||
!g.moritaniAssassinatePreview && !g.moritaniAssassinate && !g.advancedPreview &&
|
||||
((!g.moritaniAssassinatePreview && !g.moritaniAssassinate) || advancedMoritaniLeaderSkillsProfile(g)) && !g.advancedPreview &&
|
||||
!g.kullPreview && !g.nexusKullPreview && !g.guildBetrayalPreview && !g.richeseBetrayalPreview &&
|
||||
!g.nexusIxianReplacementPreview && !g.nexusIxianBetrayalPreview && !g.nexusHarkonnenBetrayalPreview;
|
||||
}
|
||||
@@ -9268,15 +9279,18 @@ function requireFreshFactionInventory(state: Game) {
|
||||
});
|
||||
}), 'The faction initializer cannot overwrite existing inventories, leader custody or revival history.');
|
||||
}
|
||||
function initializeMoritaniAssassinateState(g: Game): void {
|
||||
g.moritaniAssassinatePreview = true;
|
||||
g.moritaniAssassinate = {version:1,owner:byFaction(g,'moritani')!.id,normalTraitorCall:false,opportunities:[]};
|
||||
g.moritaniAssassinateCallEvents = [];
|
||||
}
|
||||
/** Genuine, explicitly opted-in preview; no player action enables this profile. */
|
||||
export function initializeMoritaniAssassinateGameForAudit(state: Game): Game {
|
||||
requireRule(!state.moritaniAssassinatePreview && !state.moritaniAssassinate && !state.moritaniAssassinateResume && !state.moritaniAssassinateCallEvents,
|
||||
'The assassination preview starts only from a fresh lobby.');
|
||||
requireRule(moritaniAssassinateModeSupported(state), 'The assassination preview needs Advanced Moritani with ordinary base opponents except Harkonnen, the Ecaz expansion and no optional modules.');
|
||||
const g = initializeFactionExpansionsGameForAudit(state);
|
||||
g.moritaniAssassinatePreview = true;
|
||||
g.moritaniAssassinate = {version:1,owner:byFaction(g,'moritani')!.id,normalTraitorCall:false,opportunities:[]};
|
||||
g.moritaniAssassinateCallEvents = [];
|
||||
initializeMoritaniAssassinateState(g);
|
||||
return g;
|
||||
}
|
||||
/** Development-only Semuta reaction in fresh Richese games, optionally with
|
||||
@@ -9335,8 +9349,10 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
|
||||
requireRule(!state.leaderSkills, 'Leader Skills cannot redeal existing skill cards.');
|
||||
const g = structuredClone(state);
|
||||
g.leaderSkills = createLeaderSkills(random);
|
||||
return initializeSetupGameForAudit(g, false, false, false, false, true,
|
||||
const initialized = initializeSetupGameForAudit(g, false, false, false, false, true,
|
||||
g.expansions.length === 1 && g.expansions[0] === 'choam', nativeExpansionLeaderSkillsProfile(g));
|
||||
if (advancedMoritaniLeaderSkillsProfile(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
return initialized;
|
||||
}
|
||||
/** Fresh all-fourteen-card classic or supported native skills; no public income toggle. */
|
||||
export function initializeSpiceBankerIncomeGameForAudit(state: Game): Game {
|
||||
@@ -9348,8 +9364,10 @@ export function initializeSpiceBankerIncomeGameForAudit(state: Game): Game {
|
||||
g.leaderSkills = createLeaderSkills(random);
|
||||
g.spiceBankerIncomePreview = true;
|
||||
g.spiceBankerIncome = bankerIncomeRule(() => createBankerIncomeState(bankerIncomeContext(g)));
|
||||
return initializeSetupGameForAudit(g, false, false, false, false, true,
|
||||
const initialized = initializeSetupGameForAudit(g, false, false, false, false, true,
|
||||
g.expansions.length === 1 && g.expansions[0] === 'choam', g.expansions.length > 0);
|
||||
if (advancedMoritaniLeaderSkillsProfile(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
return initialized;
|
||||
}
|
||||
/** Gated development setup for the independent three-card Ecaz variant. */
|
||||
export function initializeEcazTreacheryGameForAudit(state: Game): Game {
|
||||
@@ -21291,6 +21309,22 @@ function projectedRihani(g: Game, owner: string) {
|
||||
}
|
||||
function finishWinner(g: Game, winner: Player, t: string, cards: string[]) {
|
||||
if (startRihaniVictory(g, winner, t, cards)) return;
|
||||
const assassination = g.moritaniAssassinate?.opportunities.find(r => r.stage === 'skills');
|
||||
if (assassination) {
|
||||
const context = g.lastBattleContext!;
|
||||
requireRule(assassination.event === context.event && assassination.opponent === winner.id &&
|
||||
assassination.territory === t && context.result === 'normal',
|
||||
'Finish the original winner’s skills before its assassination response.');
|
||||
assassination.stage = 'choice';
|
||||
syncMoritaniAssassinateReceipt(g, assassination);
|
||||
const continuation = {
|
||||
kind: 'battleResolved' as const, event: context.event, result: 'normal' as const,
|
||||
combatants: context.combatants, territory: t, winner: winner.id, cards,
|
||||
};
|
||||
context.moritaniAssassinate!.continuation = JSON.stringify(continuation);
|
||||
continueResolvedBattle(g, continuation);
|
||||
return;
|
||||
}
|
||||
if (g.pendingIxSubstitution) {
|
||||
const pending = g.pendingIxSubstitution;
|
||||
requireRule((pending.winner ?? pending.player) === winner.id && pending.territory === t &&
|
||||
|
||||
@@ -38,6 +38,31 @@ export function basicMoritaniLeaderSkillsProfile(game: LeaderSkillProfile): bool
|
||||
FACTIONS.some(faction => faction.id === player.faction && faction.expansion === 'base'));
|
||||
}
|
||||
|
||||
/** Existing bounded Advanced assassination roster, composed with native skills. */
|
||||
export function advancedMoritaniLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
|
||||
const players = game.players;
|
||||
if (game.advanced !== true || game.expansions.length !== 1 ||
|
||||
game.expansions[0] !== 'ecaz' || !players || players.length < 2 || players.length > 6 ||
|
||||
!noOtherLeaderSkillModules(game) || game.semutaPreview || game.advancedPreview ||
|
||||
game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview ||
|
||||
game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
|
||||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview) return false;
|
||||
let native = false;
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
const player = players[i];
|
||||
for (let j = 0; j < i; j++) if (players[j].faction === player.faction) return false;
|
||||
if (player.faction === 'moritani') native = true;
|
||||
else {
|
||||
if (player.faction === 'harkonnen') return false;
|
||||
let classic = false;
|
||||
for (const faction of FACTIONS)
|
||||
if (faction.id === player.faction && faction.expansion === 'base') { classic = true; break; }
|
||||
if (!classic) return false;
|
||||
}
|
||||
}
|
||||
return native;
|
||||
}
|
||||
|
||||
/** Native Basic Tleilaxu, including Face Dancers; foreign gholas are Advanced. */
|
||||
export function basicTleilaxuLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
|
||||
return game.advanced === false && game.expansions.length === 1 &&
|
||||
@@ -103,7 +128,8 @@ export function advancedNativeLeaderSkillsProfile(game: LeaderSkillProfile): boo
|
||||
}
|
||||
|
||||
export function nativeExpansionLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
|
||||
return basicExpansionLeaderSkillsProfile(game) || advancedNativeLeaderSkillsProfile(game);
|
||||
return basicExpansionLeaderSkillsProfile(game) || advancedNativeLeaderSkillsProfile(game) ||
|
||||
advancedMoritaniLeaderSkillsProfile(game);
|
||||
}
|
||||
|
||||
/** Shared by rule quotes, controls and minimal legal bot participation. */
|
||||
|
||||
@@ -9,7 +9,7 @@ export type MoritaniAssassinateReceipt = {
|
||||
opponent: string;
|
||||
faction: FactionId;
|
||||
opposingLeader: string;
|
||||
stage: 'choice' | 'declined' | 'revealed' | 'replaced';
|
||||
stage: 'skills' | 'choice' | 'declined' | 'revealed' | 'replaced';
|
||||
card: string | null;
|
||||
bounty: number;
|
||||
replacement: string | null;
|
||||
@@ -211,17 +211,17 @@ export function validateMoritaniAssassinate(
|
||||
(index === 0 || r.turn >= state.opportunities[index - 1].turn) &&
|
||||
identifier(r.opposingLeader) &&
|
||||
!!printed(r.faction, r.opposingLeader) &&
|
||||
['choice', 'declined', 'revealed', 'replaced'].includes(r.stage) &&
|
||||
['skills', 'choice', 'declined', 'revealed', 'replaced'].includes(r.stage) &&
|
||||
r.signature === moritaniAssassinateSignature(r),
|
||||
'Assassinate has a changed, duplicated or malformed battle opportunity.',
|
||||
);
|
||||
events.add(r.event);
|
||||
if (r.stage === 'choice' || r.stage === 'declined') {
|
||||
if (r.stage === 'skills' || r.stage === 'choice' || r.stage === 'declined') {
|
||||
requireAssassinate(
|
||||
r.card === null &&
|
||||
r.bounty === 0 &&
|
||||
r.replacement === null &&
|
||||
(r.stage !== 'choice' ||
|
||||
((r.stage !== 'skills' && r.stage !== 'choice') ||
|
||||
(index === state.opportunities.length - 1 &&
|
||||
(!custody || r.turn === custody.turn))),
|
||||
'An unspent Assassinate choice cannot contain a reveal, bounty or replacement.',
|
||||
|
||||
+10
-10
@@ -4482,20 +4482,20 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
category: 'Advanced & expansions',
|
||||
coverage: 'Partial',
|
||||
developmentStage: 'Prototyped',
|
||||
summary: 'Basic Moritani and base opponents use the full fourteen-card Leader Skills deck in development tables.',
|
||||
summary: 'Native Basic and bounded Advanced Moritani compose fourteen skills with Terror, original assassination and skill-first aftermath.',
|
||||
steps: [
|
||||
'Assign skills after starting Treachery Cards and before Traitor choices. Moritani places its six starting forces last. All fourteen skills remain in the physical deck; each player keeps one of two private choices and publicly assigns it to an eligible leader.',
|
||||
'Connected movement, shipping, payment and battle skills use the same controls and legal quotes as the base profile. Terror Assassination returns a killed leader’s exact skill once; a later own-leader revival offers the normal private optional replacement. Sabotage affects Treachery Cards, not skills.',
|
||||
'An Enemy of My Enemy alliance preserves each faction’s assignments. A losing ally cannot retain a used Planetologist Special or the Worthless card used for a Diplomat copy, because those cards must be discarded.',
|
||||
'This profile uses Basic rules, Moritani with base opponents and no other optional modules. Existing unfinished Leader Skill effects and disputed cases remain unfinished. Advanced Moritani, Ecaz and further roster/module combinations need separate integration; ordinary public starts remain disabled.',
|
||||
'Basic Moritani with base opponents keeps its existing entry, including Harkonnen. Fresh Advanced Moritani plus classic opponents except Harkonnen uses the ecaz deck and the original assassination preview with all fourteen skills. Winner Suk rescue and Rihani choice finish before post-loss assassination, without replaying casualties. The actual winning disc remains excluded; another eligible trained disc returns its skill once and pays only printed bounty. Normal traitor revelation forfeits assassination for the whole game. Ecaz/Duke skill assignment, other native families, optional modules and public starts remain separate.',
|
||||
],
|
||||
related: ['leader-skills', 'expansion-faction-games', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Genuine setup and shared public-profile guards connect existing skill effects to Basic Moritani. Terror custody and ally-retention disposal reuse settled contracts; unfinished effects remain explicit.', evidence: ['game/leader-skill-profile.ts', 'game/engine.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Genuine setup, shared native skill quotes, original Terror/retention, skill-first Suk/Rihani continuation and actual trained-disc assassination/one Mentat replacement compose in the bounded Advanced roster. Basic and unresolved module/roster boundaries remain.', evidence: ['game/leader-skill-profile.ts', 'game/moritani-assassinate.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Implemented', detail: 'Existing private offers, public assignments, battle and revival choices are reused; Planetologist controls share the expanded server predicate.', evidence: ['components/leader-skills.tsx', 'components/planetologist-movement.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Existing profiles retain minimal legal participation through shared public choices. Strategy and difficulty calibration remain deferred until game features are complete.', evidence: ['game/bots.ts', 'tools/faction-games.ts'] },
|
||||
{ area: 'Documentation', status: 'Implemented', detail: 'The integration guide records entry, exact scope, existing source contracts and remaining effects without claiming full module compliance.', evidence: ['docs/MORITANI_LEADER_SKILLS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Genuine setup, all physical assignments, Terror death/revival, movement/payment/battle continuations, mandatory retention exclusions and authenticated concurrent recovery have focused checks. Sample and browser results are recorded separately; full combined-rule acceptance remains open.', evidence: ['tests/moritani-skills-integration.test.ts', 'tests/moritani-skills-custody.test.ts', 'tests/moritani-skills-battle.test.ts', 'tests/moritani-skills-recovery.test.ts', 'tests/moritani-skills-worm.test.ts'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Affected64/64 and CLI2/2 pass. Five genuine Advanced games finish1,425 accepted actions/no rejection/37 JSON continuations/24 battles; none opens assassination. Nine actual controlled cases cover original skill-first decisions, manual/four-policy assassination death/card return/one replacement and private Mentat/Banker opt-ins. Actual human/checkpoint evidence is recorded separately; full module acceptance remains open.', evidence: ['tests/advanced-moritani-skills.test.ts', 'tests/advanced-moritani-skills-assassinate.test.ts', 'tests/moritani-skills-integration.test.ts', 'tests/moritani-assassinate-engine.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -4507,7 +4507,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
summary: 'The fresh banker-income preview gains one deferred spice once per phase when another player makes one qualifying payment to the Spice Bank, then collects automatically at native Mentat.',
|
||||
searchText: 'Spice Banker normal income bank payment four spice phase deferred front shield original owner death capture Mentat',
|
||||
steps: [
|
||||
'This separate fresh local profile admits unique two-to-six classic factions in Basic or Advanced with the base deck, supported Basic Ixian/Tleilaxu/CHOAM/Moritani skill rosters with their single required deck, or bounded Advanced native Ixians, Tleilaxu and/or CHOAM plus classic seats with distinct required family decks. All fourteen skills and native setup remain. Native foreign revival never assigns a skill. Unrelated native families, Nexus, Homeworld, Discovery, Tech Tokens and other overlays stay guarded; no public start or old-game retrofit.',
|
||||
'This separate fresh local profile admits two-to-six classic factions in Basic or Advanced with the base deck, the supported Basic native skill families, bounded Advanced Ixian/Tleilaxu/CHOAM plus classic seats with distinct required decks, or the separate Advanced Moritani/non-Harkonnen classic assassination roster with the ecaz deck. All fourteen skills and native setup remain. Foreign revival never assigns a skill, and Terror placement remains free. Unrelated native families and optional overlays stay guarded; no public start or old-game retrofit.',
|
||||
'The printed normal band is mandatory once per phase: when another player makes a payment of at least four spice to the Spice Bank, gain one spice and place it in front of the shield. It is an additional bank grant, not a reduction of the payer’s payment or another player’s income.',
|
||||
'Only an actual completed positive bank payment qualifies. A payment to a player, zero/free/waived cost, self-payment, invalid declaration or unfinished possible payment supplies no income. Several smaller payments do not combine to reach four.',
|
||||
'Use the original actual bank portion after recipient and counter settlement, not a quoted fee or guessed wallet change. Paid auctions, funded shipments, paid revival and eligible resolved battle support keep their native continuation once. Canceled Emperor, Guild or Tleilaxu revival income can leave a real bank payment. Tleilaxu self-revival pays the Bank; other-player revival normally pays Tleilaxu. Its separate free-revival bank award is not payer debit: three paid plus that one reward never becomes four paid to the Bank. A stopped shipment supplies no payment.',
|
||||
@@ -4522,7 +4522,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
],
|
||||
related: ['leader-skills', 'battle', 'bidding', 'revival', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Implemented', detail: 'Actual final bank payer legs, native skill eligibility, one physical Banker phase grant and original-owner automatic Mentat collection connect through fresh classic, supported Basic native and bounded Advanced Ixian/Tleilaxu/CHOAM skill profiles. Tleilaxu self-revival and canceled revival income preserve actual payer cost separately from the free award.', evidence: ['game/engine.ts', 'game/spice-banker-income.ts'] },
|
||||
{ area: 'Implementation', status: 'Implemented', detail: 'Actual final bank payer legs, one physical Banker phase grant and original-owner Mentat collection connect through fresh classic and supported native skill profiles, including separate bounded Advanced Moritani assassination. Tleilaxu paid revival remains distinct from its free award; free Terror placement is not a new payment.', evidence: ['game/engine.ts', 'game/spice-banker-income.ts'] },
|
||||
{ area: 'Player controls', status: 'Implemented', detail: 'A full-width read-only public front-shield notice explains unavailable spice and automatic collection; genuine original payment and final Collection controls work at 390px through refresh. No new income action or confirmation.', evidence: ['components/spice-banker-income.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four minimal legal policies preserve identical available-budget actions with deferred income present or absent. Original wallets exclude front-shield currency; strategy/calibration remain deferred.', evidence: ['game/bots.ts', 'game/spice-banker.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Printed gain/threshold/one-payment/phase/collection, original payment separation, provisional earned custody and per-payer split composition are explicit without universal rulings.', evidence: ['docs/LEADER_SKILLS_RULES.md', 'docs/SPICE_BANKER_RUNTIME.md', 'docs/RULE_DECISIONS.md'] },
|
||||
@@ -4537,7 +4537,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
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.',
|
||||
steps: [
|
||||
'A development profile also connects Basic Moritani with base opponents and no other optional modules. It keeps the full deck and existing unfinished-effect boundaries; see Moritani with Leader Skills.',
|
||||
'Basic Moritani retains base opponents including Harkonnen. Its separate bounded Advanced classic/non-Harkonnen roster now composes the original assassination preview and all fourteen skills: winner Suk rescue and Rihani choice finish first, then an eligible different trained disc may be assassinated with its exact card return and printed bounty. No casualties repeat; original set-aside and one private Mentat replacement remain. See Moritani with Leader Skills for the exact scope.',
|
||||
'Fresh Advanced development profiles compose native Ixians, Tleilaxu and/or CHOAM with classic opponents, two through six unique seats and distinct required Ix/CHOAM decks. All fourteen skills and original native starting choices remain. Auditor never receives a skill. Typed cyborg support flows through Suk rescue before native substitution; CHOAM keeps original payer/income and Smuggler-before-Collection order. Foreign-ghola revival gives control without a new skill. Basic entries are unchanged; captured replacement, Advanced Atreides Suk/KH and unrelated overlays remain guarded.',
|
||||
'With this optional module, starting Treachery Cards are dealt after prediction and before skill assignment. Every faction privately receives two skills, keeps one, returns the other to the shuffled deck, and assigns its chosen card to an eligible leader. Skill and leader become public before traitor selection.',
|
||||
'Before Battle Plans, leave the skilled leader face up to keep its normal effect, or move it behind the shield for possible use. A face-up leader cannot be selected for the plan. Concealing it while choosing another leader gives neither effect. A leader that must be used because no alternative remains moves behind the shield automatically.',
|
||||
@@ -4551,12 +4551,12 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'A surviving skilled Sandmaster victory automatically adds three board spice to an existing pile in the battle territory, even with no surviving forces. Multiple piles await a placement ruling.',
|
||||
'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, supported Basic native or bounded Advanced Ixian/Tleilaxu/CHOAM banker-income profile; ordinary/default starts are unchanged.',
|
||||
'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.',
|
||||
'Mentat has an explicit private developer preview for naming a specific weapon before posture and faction powers, including supported Basic Ixian/Tleilaxu/CHOAM/Moritani and bounded Advanced Ixian/Tleilaxu/CHOAM skill starts. 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.',
|
||||
'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.',
|
||||
'Other special Sandmaster relocation remains missing. Normal Banker income has a separate fresh classic, supported Basic native or bounded Advanced Ixian/Tleilaxu/CHOAM source-to-Mentat preview with provisional original-earned-faction custody, not general activation. Diplomat retreat uses a documented conservative outcome-order interpretation in standalone Leader Skills play. Native Advanced foreign gholas and Face Dancers follow the bounded Tleilaxu contract; other combined modules remain unfinished. This development prototype does not enable public module starts.',
|
||||
'Other special Sandmaster relocation remains missing. 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 and skill-first Moritani assassination follow their bounded contracts; Ecaz/Duke assignment 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', 'moritani-leader-skills', 'setup','choam-modules','implementation-checklist'],
|
||||
|
||||
Reference in new issue
Block a user