Compose classic Homeworld optional modules through original rules
This commit is contained in:
1 parent
3dd36bd9ff
commit
fa025220f8
18 files changed
+1220
-94
No files matched your search
+32
-19
@@ -43,6 +43,13 @@ import { privateOutputDirectory, sourceSnapshot } from './verification';
|
||||
const DEFAULT_SEED = 20_260_926;
|
||||
const DEFAULT_MAX_ACTIONS = 3_500;
|
||||
const DIFFICULTIES = ['Easy', 'Medium', 'Hard', 'Brutal'] as const;
|
||||
const HOMEWORLD_CLASSIC_MODULE_PROFILES = [
|
||||
'homeworld-skills', 'homeworld-discovery',
|
||||
'homeworld-skills-tech', 'homeworld-skills-stronghold', 'homeworld-skills-stronghold-tech',
|
||||
'homeworld-discovery-tech', 'homeworld-discovery-stronghold', 'homeworld-discovery-stronghold-tech',
|
||||
'homeworld-discovery-skills', 'homeworld-discovery-skills-tech',
|
||||
'homeworld-discovery-skills-stronghold', 'homeworld-discovery-skills-stronghold-tech',
|
||||
] as const;
|
||||
type Profile = 'base' | 'choam' | 'ecaz' | 'ecaz-treachery' | 'ecaz-occupy' | 'moritani-assassinate' | 'stronghold-factions' | 'combined' | 'combined-nexus' | 'combined-homeworld-nexus' | 'homeworld' | 'nexus' | 'homeworld-nexus' | 'choam-roster' | 'ecaz-roster' | 'ix-roster' | 'choam-nexus' | 'ecaz-nexus' | 'ix-nexus' | 'moritani-skills' | 'tleilaxu-skills' | 'ix-skills' | 'choam-skills' | 'richese-skills' | 'ecaz-skills' | 'skills-tech' | 'skills-stronghold' | 'skills-stronghold-tech'
|
||||
| 'factions-tech' | 'stronghold-factions-tech' | 'nexus-tech' | 'nexus-stronghold' | 'nexus-stronghold-tech'
|
||||
| 'paired-nexus-tech' | 'paired-nexus-stronghold' | 'paired-nexus-stronghold-tech' | 'nexus-skills' | 'paired-nexus-skills'
|
||||
@@ -57,7 +64,7 @@ type Profile = 'base' | 'choam' | 'ecaz' | 'ecaz-treachery' | 'ecaz-occupy' | 'm
|
||||
| 'paired-discovery-nexus' | 'paired-discovery-nexus-tech' | 'paired-discovery-nexus-stronghold' | 'paired-discovery-nexus-stronghold-tech'
|
||||
| 'paired-discovery-nexus-skills' | 'paired-discovery-nexus-skills-tech' | 'paired-discovery-nexus-skills-stronghold' | 'paired-discovery-nexus-skills-stronghold-tech'
|
||||
| 'e3-discovery-nexus-skills' | 'e3-discovery-nexus-skills-tech' | 'e3-discovery-nexus-skills-stronghold' | 'e3-discovery-nexus-skills-stronghold-tech'
|
||||
| 'homeworld-skills' | 'homeworld-discovery';
|
||||
| (typeof HOMEWORLD_CLASSIC_MODULE_PROFILES)[number];
|
||||
const discoveryStrongholdProfile = (profile: string) => profile === 'discovery-stronghold' ||
|
||||
profile === 'discovery-stronghold-tech' || profile === 'discovery-skills-stronghold' ||
|
||||
profile === 'discovery-skills-stronghold-tech' || profile === 'native-discovery-skills-stronghold' ||
|
||||
@@ -80,7 +87,16 @@ const nativeDiscoveryNexusSkillsProfile = (profile: string) =>
|
||||
profile === 'paired-discovery-nexus-skills-stronghold' || profile === 'paired-discovery-nexus-skills-stronghold-tech' ||
|
||||
profile === 'e3-discovery-nexus-skills' || profile === 'e3-discovery-nexus-skills-tech' ||
|
||||
profile === 'e3-discovery-nexus-skills-stronghold' || profile === 'e3-discovery-nexus-skills-stronghold-tech';
|
||||
const homeworldClassicModuleProfile = (profile: string) => profile === 'homeworld-skills' || profile === 'homeworld-discovery';
|
||||
const homeworldClassicModuleProfile = (profile: string) =>
|
||||
(HOMEWORLD_CLASSIC_MODULE_PROFILES as readonly string[]).includes(profile);
|
||||
const homeworldClassicSkillsProfile = (profile: string) =>
|
||||
homeworldClassicModuleProfile(profile) && profile.includes('-skills');
|
||||
const homeworldClassicDiscoveryProfile = (profile: string) =>
|
||||
homeworldClassicModuleProfile(profile) && profile.includes('-discovery');
|
||||
const homeworldClassicTechProfile = (profile: string) =>
|
||||
homeworldClassicModuleProfile(profile) && profile.endsWith('-tech');
|
||||
const homeworldClassicStrongholdProfile = (profile: string) =>
|
||||
homeworldClassicModuleProfile(profile) && profile.includes('-stronghold');
|
||||
type Rules = 'basic' | 'advanced';
|
||||
|
||||
type Scenario = {
|
||||
@@ -156,8 +172,10 @@ const BASE_SCENARIOS: readonly Scenario[] = [2, 3, 4, 5, 6].flatMap((players) =>
|
||||
})),
|
||||
);
|
||||
const HOMEWORLD_CLASSIC_MODULE_SCENARIOS: readonly Scenario[] =
|
||||
(['homeworld-skills', 'homeworld-discovery'] as const).flatMap((profile, band) =>
|
||||
BASE_SCENARIOS.map((scenario, index): Scenario => ({ ...scenario, profile, ordinal: 6400 + band * 100 + index })));
|
||||
HOMEWORLD_CLASSIC_MODULE_PROFILES.flatMap((profile, band) =>
|
||||
BASE_SCENARIOS.map((scenario, index): Scenario => ({ ...scenario, profile, ordinal: 6400 + band * 100 + index }))
|
||||
.filter(scenario => (!homeworldClassicTechProfile(profile) || scenario.roster.length >= 3) &&
|
||||
(!homeworldClassicStrongholdProfile(profile) || scenario.rules === 'advanced')));
|
||||
const NEXUS_SKILLS_SCENARIOS: readonly Scenario[] = BASE_SCENARIOS.map((scenario, index) => ({
|
||||
...scenario, profile: 'nexus-skills', ordinal: 1410 + index,
|
||||
}));
|
||||
@@ -565,9 +583,10 @@ const SKILLS_STRONGHOLD_SCENARIOS: readonly Scenario[] =
|
||||
const strongholdSkillProfile = (profile: string) => profile === 'skills-stronghold' || profile === 'skills-stronghold-tech';
|
||||
const discoverySkillsProfile = (profile: string) => profile === 'discovery-skills' || profile === 'discovery-skills-tech' ||
|
||||
profile === 'discovery-skills-stronghold' || profile === 'discovery-skills-stronghold-tech';
|
||||
const skillProfile = (profile: string) => profile === 'homeworld-skills' || nativeDiscoveryNexusSkillsProfile(profile) || discoveryNexusSkillsProfile(profile) || nativeDiscoverySkillsProfile(profile) || discoverySkillsProfile(profile) || e3NexusSkillsProfile(profile) || pairedNexusSkillsModuleProfile(profile) || nexusSkillsModuleProfile(profile) || profile === 'nexus-skills' || profile === 'paired-nexus-skills' || profile === 'moritani-skills' || profile === 'tleilaxu-skills' || profile === 'ix-skills' || profile === 'choam-skills' || profile === 'richese-skills' || profile === 'ecaz-skills' || profile === 'skills-tech' || strongholdSkillProfile(profile);
|
||||
const skillProfile = (profile: string) => homeworldClassicSkillsProfile(profile) || nativeDiscoveryNexusSkillsProfile(profile) || discoveryNexusSkillsProfile(profile) || nativeDiscoverySkillsProfile(profile) || discoverySkillsProfile(profile) || e3NexusSkillsProfile(profile) || pairedNexusSkillsModuleProfile(profile) || nexusSkillsModuleProfile(profile) || profile === 'nexus-skills' || profile === 'paired-nexus-skills' || profile === 'moritani-skills' || profile === 'tleilaxu-skills' || profile === 'ix-skills' || profile === 'choam-skills' || profile === 'richese-skills' || profile === 'ecaz-skills' || profile === 'skills-tech' || strongholdSkillProfile(profile);
|
||||
const discoveryProfile = (profile: string) => profile === 'discovery' || profile === 'discovery-tech' ||
|
||||
profile === 'discovery-stronghold' || profile === 'discovery-stronghold-tech' || profile === 'homeworld-discovery';
|
||||
profile === 'discovery-stronghold' || profile === 'discovery-stronghold-tech' ||
|
||||
(homeworldClassicDiscoveryProfile(profile) && !homeworldClassicSkillsProfile(profile));
|
||||
const assassinationScenario = (scenario: Scenario) => scenario.rules === 'advanced' &&
|
||||
scenario.roster.includes('moritani') &&
|
||||
(scenario.profile === 'moritani-assassinate' || discoveryProfile(scenario.profile) || nativeStrongholdProfile(scenario.profile) || nativeTechProfile(scenario.profile) || skillProfile(scenario.profile));
|
||||
@@ -635,7 +654,7 @@ function usage() {
|
||||
+ '\nDiscovery uses fresh classic, paired E1/E2 or standalone Ecaz OR Moritani setups with original selected decks, six Discovery territory cards, Great Maker and eight physical tokens in Basic/Advanced2..6. Discovery-tech preserves unused original Tech3..6. Native setup, actual token entry, typed force paths, stash and nested location controls reuse original handlers. No Skills/HW/Nexus/Strongholds, mixed E3 families, new contested/shared-payer rulings, public starts or played-game conversion.'
|
||||
+ '\nDiscovery-skills selects classic/base33/all14 Skills with original Discovery7/8, Basic/Advanced2..6. Discovery-skills-tech preserves unused original Tech3..6. Original trained posture, physical free entry, fixed-three carried movement, paid nested routes, stash capacity, skill battle rescue/rewards and distinct-party Bureaucrat invoices remain. Native families, Nexus/Strongholds/HW, Banker/Mentat previews, pending contested/shared/mixed effects, public starts and save conversion stay separate.'
|
||||
+ '\nDiscovery-nexus uses classic/base33/all12 Nexus/Discovery7+8, Basic/Advanced2..6. Discovery-nexus-tech adds original Tech3+; discovery-nexus-stronghold adds Advanced Strongholds2+; both requires3+ Advanced. Original Great Maker/worm/vote/ride, settled-alliance end-Spice closing deal, end-Mentat held-card settlement, next-turn entry and source-clear borrowed force/tariff/card effects remain. No Skills/HW/native families, mixed-module Fremen Betrayal, new pending rulings, public starts or save conversion.'
|
||||
+ '\nHomeworld-skills and homeworld-discovery use original classic/base33 rosters in Basic/Advanced2..6. The first retains all14 original offered training, actual Homeworld combat/rescue and split native custody; the second retains Discovery7/8, actual Maker vote/typed reserve ride, next-turn parent-board entry and split Imperial nested shipment. Original setup/cards/force sources remain. No other optional modules, native families, public starts, save conversion or pending-rule changes.'
|
||||
+ '\nHomeworld-skills, homeworld-discovery and homeworld-discovery-skills use original classic/base33 rosters in Basic/Advanced2..6, all14 offered training when Skills is selected and Discovery7/8 when Discovery is selected. Each family may add -tech (original Tech3+), -stronghold (Advanced2+) or -stronghold-tech (Advanced3+). Original setup/cards/native force sources, physical rescue, Maker/entry, phase-end industry and source-local held effects remain. No Nexus, native families, public starts, save conversion or pending-rule changes.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -760,13 +779,13 @@ function freshGame(scenario: Scenario) {
|
||||
player.ready = true;
|
||||
}
|
||||
if (scenario.ecazTreachery) game.ecazTreachery = true;
|
||||
if (discoveryTechProfile(scenario.profile) || scenario.profile === 'skills-tech' || scenario.profile === 'skills-stronghold-tech' || nativeTechProfile(scenario.profile) || nexusTechProfile(scenario.profile)) game.techTokens = createTechTokens();
|
||||
if (discoveryStrongholdProfile(scenario.profile) || strongholdSkillProfile(scenario.profile) || nexusStrongholdProfile(scenario.profile)) game.strongholdCards = createStrongholdCards();
|
||||
if (homeworldClassicTechProfile(scenario.profile) || discoveryTechProfile(scenario.profile) || scenario.profile === 'skills-tech' || scenario.profile === 'skills-stronghold-tech' || nativeTechProfile(scenario.profile) || nexusTechProfile(scenario.profile)) game.techTokens = createTechTokens();
|
||||
if (homeworldClassicStrongholdProfile(scenario.profile) || discoveryStrongholdProfile(scenario.profile) || strongholdSkillProfile(scenario.profile) || nexusStrongholdProfile(scenario.profile)) game.strongholdCards = createStrongholdCards();
|
||||
if (homeworldClassicModuleProfile(scenario.profile) || scenario.profile === 'homeworld' || scenario.profile === 'homeworld-occupation' || scenario.profile === 'homeworld-nexus' || scenario.profile === 'combined-homeworld-nexus')
|
||||
game.homeworlds = { custody: null };
|
||||
if (scenario.profile === 'paired-nexus-skills' || scenario.profile === 'nexus-skills' || scenario.profile === 'nexus' || nexusModuleProfile(scenario.profile) || scenario.profile === 'homeworld-nexus' || scenario.profile === 'choam-nexus' || scenario.profile === 'ecaz-nexus' || scenario.profile === 'ix-nexus' || scenario.profile === 'combined-nexus' || scenario.profile === 'combined-homeworld-nexus')
|
||||
game.nexusCards = { cards: null, phase: null };
|
||||
if (nativeDiscoverySkillsProfile(scenario.profile) || discoverySkillsProfile(scenario.profile) || discoveryNexusProfile(scenario.profile)) game.discoveryEnabled = true;
|
||||
if (homeworldClassicDiscoveryProfile(scenario.profile) || nativeDiscoverySkillsProfile(scenario.profile) || discoverySkillsProfile(scenario.profile) || discoveryNexusProfile(scenario.profile)) game.discoveryEnabled = true;
|
||||
if (discoveryProfile(scenario.profile)) {
|
||||
game.discoveryEnabled = true;
|
||||
return initializeDiscoveryGameForAudit(game);
|
||||
@@ -838,13 +857,13 @@ function resumedGame(path: string) {
|
||||
throw new Error('--resume sample scenarios exclude unsupported optional modules.');
|
||||
const scenario = [...SCENARIOS, ...COMBINED_NEXUS_SCENARIOS, ...COMBINED_HOMEWORLD_NEXUS_SCENARIOS, ...BASE_SCENARIOS, ...MODULE_SCENARIOS, ...EXPANSION_ROSTER_SCENARIOS, ...ECAZ_TREACHERY_SCENARIOS, ...ECAZ_OCCUPY_SCENARIOS, ...PAIRED_NEXUS_SCENARIOS, ...MORITANI_ASSASSINATE_SCENARIOS, ...STRONGHOLD_FACTIONS_SCENARIOS, ...MORITANI_SKILLS_SCENARIOS, ...TLEILAXU_SKILLS_SCENARIOS, ...IX_SKILLS_SCENARIOS, ...CHOAM_SKILLS_SCENARIOS, ...RICHESE_SKILLS_SCENARIOS, ...ECAZ_SKILLS_SCENARIOS, ...SKILLS_TECH_SCENARIOS, ...SKILLS_STRONGHOLD_SCENARIOS, ...NATIVE_TECH_SCENARIOS, ...CLASSIC_NEXUS_MODULE_SCENARIOS, ...PAIRED_NEXUS_MODULE_SCENARIOS, ...NEXUS_SKILLS_SCENARIOS, ...PAIRED_NEXUS_SKILLS_SCENARIOS, ...NEXUS_SKILLS_MODULE_SCENARIOS, ...PAIRED_NEXUS_SKILLS_MODULE_SCENARIOS, ...E3_NEXUS_SKILLS_SCENARIOS, ...HOMEWORLD_OCCUPATION_SCENARIOS, ...DISCOVERY_SCENARIOS, ...DISCOVERY_SKILLS_SCENARIOS, ...DISCOVERY_NEXUS_SCENARIOS, ...DISCOVERY_STRONGHOLD_SCENARIOS, ...NATIVE_DISCOVERY_SKILLS_SCENARIOS, ...DISCOVERY_NEXUS_SKILLS_SCENARIOS, ...PAIRED_DISCOVERY_NEXUS_SCENARIOS, ...NATIVE_DISCOVERY_NEXUS_SKILLS_SCENARIOS, ...HOMEWORLD_CLASSIC_MODULE_SCENARIOS].find(
|
||||
(candidate) =>
|
||||
(nativeDiscoverySkillsProfile(candidate.profile) || discoveryProfile(candidate.profile) || discoverySkillsProfile(candidate.profile) || discoveryNexusProfile(candidate.profile)) === discovery &&
|
||||
(homeworldClassicDiscoveryProfile(candidate.profile) || nativeDiscoverySkillsProfile(candidate.profile) || discoveryProfile(candidate.profile) || discoverySkillsProfile(candidate.profile) || discoveryNexusProfile(candidate.profile)) === discovery &&
|
||||
(homeworldClassicModuleProfile(candidate.profile) || candidate.profile === 'homeworld' || candidate.profile === 'homeworld-occupation' || candidate.profile === 'homeworld-nexus' || candidate.profile === 'combined-homeworld-nexus') === !!game.homeworlds &&
|
||||
(candidate.profile === 'paired-nexus-skills' || candidate.profile === 'nexus-skills' || candidate.profile === 'nexus' || nexusModuleProfile(candidate.profile) || candidate.profile === 'homeworld-nexus' || candidate.profile === 'choam-nexus' || candidate.profile === 'ecaz-nexus' || candidate.profile === 'ix-nexus' || candidate.profile === 'combined-nexus' || candidate.profile === 'combined-homeworld-nexus') === !!game.nexusCards &&
|
||||
(candidate.profile === 'homeworld-occupation') === !!game.homeworldOccupationPreview &&
|
||||
skillProfile(candidate.profile) === !!game.leaderSkills &&
|
||||
(discoveryTechProfile(candidate.profile) || candidate.profile === 'skills-tech' || candidate.profile === 'skills-stronghold-tech' || nativeTechProfile(candidate.profile) || nexusTechProfile(candidate.profile)) === !!game.techTokens &&
|
||||
(discoveryStrongholdProfile(candidate.profile) || nativeStrongholdProfile(candidate.profile) || strongholdSkillProfile(candidate.profile) || nexusStrongholdProfile(candidate.profile)) === !!game.strongholdCards &&
|
||||
(homeworldClassicTechProfile(candidate.profile) || discoveryTechProfile(candidate.profile) || candidate.profile === 'skills-tech' || candidate.profile === 'skills-stronghold-tech' || nativeTechProfile(candidate.profile) || nexusTechProfile(candidate.profile)) === !!game.techTokens &&
|
||||
(homeworldClassicStrongholdProfile(candidate.profile) || discoveryStrongholdProfile(candidate.profile) || nativeStrongholdProfile(candidate.profile) || strongholdSkillProfile(candidate.profile) || nexusStrongholdProfile(candidate.profile)) === !!game.strongholdCards &&
|
||||
(candidate.profile === 'ecaz-treachery' || candidate.ecazTreachery === true) === !!game.ecazTreachery &&
|
||||
assassinationScenario(candidate) === !!game.moritaniAssassinatePreview &&
|
||||
(candidate.profile === 'ecaz-occupy' || ((nativeDiscoveryNexusSkillsProfile(candidate.profile) || nativeDiscoverySkillsProfile(candidate.profile) || nativeTechProfile(candidate.profile) || discoveryProfile(candidate.profile)) && candidate.roster.includes('ecaz'))) === !!game.ecazOccupyPreview &&
|
||||
@@ -949,12 +968,6 @@ function simulate(
|
||||
verifySampleCustody(game, expected, previous);
|
||||
if (actions % 37 === 0) {
|
||||
const restored = JSON.parse(JSON.stringify(game)) as Game;
|
||||
for (const player of game.players)
|
||||
assert.deepEqual(
|
||||
viewGame(restored, player.id),
|
||||
viewGame(game, player.id),
|
||||
'restored view',
|
||||
);
|
||||
for (const player of game.players) {
|
||||
const view = viewGame(restored, player.id);
|
||||
for (const other of view.players.filter(
|
||||
|
||||
Reference in new issue
Block a user