Initialize occupation benefits for registered World Skills starts

This commit is contained in:
Quintine committed 2026-10-26 11:59:03 +11:00
1 parent 7f9daa10fa
commit 396f9e0ccb
21 files changed
+737 -26

No files matched your search

+4 -3
View File
@@ -17,7 +17,7 @@ import { MovementClaimError, liveMovementPromises, movementEventAllowed, movemen
import { findMovementCompletion, type MovementCompletion } from './movement-promise-search';
import { classicNexusModulesProfile, mixedE1E2NexusProfile, mixedE3NexusProfile, pairedE3NexusProfile, pairedNexusModulesProfile, singleE1E2NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
import { independentDiscoveryProfile, independentNexusProfile, nexusCoreProfile, nativeFactionTechProfile, selectedFactionStrongholdProfile, selectedIndependentCardsProfile } from './faction-module-profile';
import { homeworldPhaseModulesProfile } from './homeworld-module-profile';
import { homeworldOccupationLeaderSkillsProfile, homeworldPhaseModulesProfile } from './homeworld-module-profile';
import { classicDiscoveryLeaderSkillsProfile, classicDiscoveryNexusProfile, homeworldDiscoveryProfile, discoveryModeSupported, discoveryModuleProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
import { advancedMoritaniLeaderSkillsProfile, selectedDeckLeaderSkillsProfile, homeworldLeaderSkillsProfile, independentTreacheryLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, guildBetrayalNexusLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeEcazLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedE3LeaderSkillsProfile, pairedE3NexusLeaderSkillsProfile, pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
@@ -10858,7 +10858,7 @@ export function initializeHomeworldOccupationGameForAudit(state: Game, leaderSki
'Occupation with Leader Skills requires original classic Homeworlds without other modules.');
const g = leaderSkills ? initializeLeaderSkillsGameForAudit(state)
: initializeSetupGameForAudit(state, true, false, false, false, false, false, true);
initializeHomeworldOccupationState(g);
if (!g.homeworldOccupationPreview) initializeHomeworldOccupationState(g);
if (moritaniAssassinateModeSupported(g)) initializeMoritaniAssassinateState(g);
return g;
}
@@ -12098,6 +12098,7 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
mixedE3 || standaloneE3NexusLeaderSkillsProfile(g) || pairedE3 || guildBetrayalNexusLeaderSkillsProfile(g), false,
independentDiscoveryProfile(g) || classicDiscoveryLeaderSkillsProfile(g) || nativeDiscoveryLeaderSkillsProfile(g) || (g.discoveryEnabled === true && classicNexus), true,
g.expansions.length === 1 && g.expansions[0] === 'choam', nativeExpansionLeaderSkillsProfile(g), independent);
if (homeworldOccupationLeaderSkillsProfile(initialized)) initializeHomeworldOccupationState(initialized);
if (advancedMoritaniLeaderSkillsProfile(initialized) || ((independentDiscoveryProfile(initialized) || independentNexusProfile(initialized) || mixedE3 || pairedE3) && initialized.advanced &&
byFaction(initialized, 'moritani'))) initializeMoritaniAssassinateState(initialized);
if ((independentDiscoveryProfile(initialized) || independentNexusProfile(initialized) || nativeEcazLeaderSkillsProfile(initialized) || mixedE3 || homeworlds || nativeDiscoveryLeaderSkillsProfile(initialized) || pairedE3) && byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
@@ -24222,7 +24223,7 @@ function homeworldOccupiedIncomeIntegrity(g: Game) {
return;
}
requireRule(g.homeworldOccupationPreview === true && g.homeworlds &&
(homeworldPhaseModulesProfile(g) || (!g.nexusCards &&
(homeworldPhaseModulesProfile(g) || homeworldOccupationLeaderSkillsProfile(g) || (!g.nexusCards &&
(!g.leaderSkills || !g.expansions.length && homeworldLeaderSkillsProfile(g)) &&
!g.techTokens && !g.strongholdCards && !g.discoveryEnabled)) &&
Array.isArray(history) && pending !== undefined,
+24
View File
@@ -1,5 +1,6 @@
import { FACTIONS } from './catalog';
import type { FactionModuleProfile } from './faction-module-profile';
import { homeworldLeaderSkillsProfile } from './leader-skill-profile';
export type HomeworldPhaseModulesProfile = FactionModuleProfile & {
homeworldOccupationPreview?: unknown;
@@ -48,3 +49,26 @@ export function homeworldPhaseModulesProfile(game: HomeworldPhaseModulesProfile)
(!game.homeworldTupilePreview || game.homeworldOccupationPreview === true && choam) &&
(!game.homeworldEcazVictoryPreview || game.homeworldOccupationPreview === true && ecaz);
}
/** Occupation benefits for an already-admitted original World/all14 setup.
* The same unchanged library admission predicate remains authoritative; this
* only selects the clean occupation context and accepts its native stamps. */
export function homeworldOccupationLeaderSkillsProfile(game: HomeworldPhaseModulesProfile): boolean {
if (!game || typeof game !== 'object' || !game.homeworlds || !game.leaderSkills ||
typeof game.advanced !== 'boolean' || !registeredRoster(game) || !optionalFlag(game.ecazTreachery) ||
(game.techTokens && game.players.length < 3) || (game.strongholdCards && !game.advanced) ||
game.nexusCards || game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.sandtrout || game.semutaPreview || game.advancedPreview || game.kullPreview || game.nexusKullPreview ||
game.guildBetrayalPreview || game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview) return false;
if (![game.ecazOccupyPreview, game.moritaniAssassinatePreview, game.homeworldOccupationPreview,
game.homeworldTupilePreview, game.homeworldEcazVictoryPreview, game.spiceBankerIncomePreview].every(optionalFlag)) return false;
const ecaz = game.players.some(player => player.faction === 'ecaz');
const moritani = game.players.some(player => player.faction === 'moritani');
const choam = game.players.some(player => player.faction === 'choam');
return (!game.ecazOccupyPreview || ecaz) &&
(!game.moritaniAssassinatePreview || game.advanced && moritani) &&
(!game.homeworldTupilePreview || game.homeworldOccupationPreview === true && choam) &&
(!game.homeworldEcazVictoryPreview || game.homeworldOccupationPreview === true && ecaz) &&
homeworldLeaderSkillsProfile(game);
}
+2 -1
View File
@@ -4815,6 +4815,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'Homeworlds are an optional module from Ecaz & Moritani, available under the official rules with Basic or Advanced play. They are separate locations outside Arrakis; they do not receive ordinary storm movement or count as ordinary strongholds.',
'Clean World starts admit registered classic/no-deck and selected-family paired/mixed/all-three rosters with optional independent cards, Tech3–6 and Advanced Strongholds2–6. Skills or Nexus are not incidental prerequisites. Fresh setup includes original occupation qualification, bank/percentage benefits, native Tupile and Ecaz victory state alongside physical locations; existing played games are not retrofitted. Original module-specific profiles and complete-mode gates stay separate.',
'Fresh World all14 uses actual registered seats and selected ordinary decks in Basic/Advanced, including mixed Ecaz/Moritani with E1/E2 natives. Neither Nexus, independent cards, one exact family nor a seated native from every selected deck is required. Original Tech3+/Advanced Strongholds2+ may remain. Existing no-card Nexus/Discovery and Semuta keep their separate limits. Actual hands, Ix card-before-training and native five-disc assignment remain; Auditor/shared Duke never train. Native Suk saves stay at that home; external Emperor returns retain original typed destinations. Nested locations gain no ordinary Stronghold effect or invented saved history.',
'Fresh clean registered World/all14 starts also include original occupation benefit state beside normal Banker. Admission and native role limits remain. Real other-payer Bank payments can earn a deferred skill grant; occupied Collection and phase tokens do not become immediate Banker cash. Actual Mentat settles the original skill receipt once. Nexus/Discovery/Semuta contexts retain their own scope; played games are not retrofitted.',
'Each faction uses its own card and matching world token. In Basic play the Emperor uses Kaitain alone. Advanced play adds Salusa Secundus for its Sardaukar; movement can transfer both force types between the two Emperor worlds.',
'Registered selected-family World all14 composes independent36/38/50-card setup and original Tech3+/Advanced Strongholds2+, including mixed E3. Clean bare World cards now use the registered selected-family profile with optional phase modules and original occupation state. Recruits uses current native rates/typed limits and recorded paid/free use; Banker and phase-end Axlotl remain separate. Reinforcements seals exact normal-first sources and excludes an own-World fee from dial/casualties while preserving original face/support. Suk rescues committed losses, not fees. Harass returns actual visitors through native custody and is forbidden on every own World. Original Nexus/Discovery/Semuta and role/effect guards remain.',
'Native reserves determine high or low population. Salusa counts Sardaukar, while the other cards count the faction’s reserves there. Reaching the high face’s minimum restores high population. Occupation also applies the low penalty.',
@@ -5008,7 +5009,7 @@ export const RULE_TOPICS: RuleTopic[] = [
status: 'Partial',
detail:
"Basic Tupile current-sole follow-up passes70/70 cases in10 files, types/lint/build, two original public gameplay/rendering programmes and four legal cleanup profiles. Its provisional policy and broader acceptance limits stay explicit. Earlier mixed E1/E2 and paired E3 Nexus added31 original physical programmes plus2 Loyalty-stock regressions. Final489/489 affected checks across42 files pass with types/lint. All64 original families finish258/288 six-seat games with338931 accepted/339426 attempted actions and9013 JSON across three frozen trees and preserved-prefix continuations. All30 remaining source-guard traces are read. Actual390px original Ixian offer/training, typed native and foreign-world payment/free Suboid plan and paired Ecaz foreign entry/living shared Duke preserve20 physical counters. Human Ixian loses, so no winning Suk rescue is claimed. Earlier qualified evidence remains canonical; full rules, assurance, calibration and deployed acceptance remain open. Current Junction follow-up passes131/131 affected cases and14 runtime/policy paths (11 original/3 controlled), with four actual original-action390px journeys. No saved/deployed acceptance is claimed.",
evidence: ['tests/homeworld-module-profile.test.ts', 'tests/homeworld-phase-modules-runtime.test.ts', 'tests/homeworld-phase-modules-controls.test.tsx', 'tests/homeworld-phase-occupation-income.test.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-classic-skills-runtime.test.ts',
evidence: ['tests/homeworld-occupation-skills-profile.test.ts', 'tests/world-skills-occupation-runtime.test.ts', 'tests/world-skills-occupation-controls.test.tsx', 'tests/world-skills-occupation-income.test.ts', 'tests/homeworld-module-profile.test.ts', 'tests/homeworld-phase-modules-runtime.test.ts', 'tests/homeworld-phase-modules-controls.test.tsx', 'tests/homeworld-phase-occupation-income.test.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-classic-skills-runtime.test.ts',
'tests/homeworld-classic-discovery-runtime.test.ts',
'tests/homeworld-skills-discovery-runtime.test.ts',
'tests/homeworld-optional-modules-runtime.test.ts',