Compose classic independent cards with World Nexus rules

Retain independent36, original Homeworlds and Nexus12 through classic fresh setup with optional Skills and phase modules. Resolve own-World Reinforcements fees before computing temporary Sardaukar roles, preserving original Cunning declarations, printed traits and physical counters. Record the provisional ordinary-nonrole-first policy and align human/AI quotes and captions.

Validation: types/lint/build, 128 affected tests across 15 files, bounded rule-source review and finite original-action smoke for Recruits, Harass, local-fee Cunning, all four legal policies and rendered source controls. Verified runtime c219ec587a54b44079539168d93835a5af904c8afb4de58bcec0a3d60c809f0c; documentation-only proof update 8ee21598e7be0c89a08fbad50786c346ebc8ae705492d8b31ea00fae0ffefa3c. Full modes, combinations, assurance and deployed acceptance remain open. No saved-room/provider changes.
This commit is contained in:
Quintine committed 2026-10-18 10:20:08 +11:00
1 parent 178251ad35
commit 97c2b67650
18 files changed
+737 -30

No files matched your search

+12 -3
View File
@@ -5,12 +5,20 @@ import { combatArmy } from '../game/combat-location';
import { nativeShipmentSources } from '../game/homeworld-options';
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
import { homeworldGameIntegrity } from '../game/homeworld-game';
import { REINFORCEMENTS_CARD } from '../game/reinforcements';
import { drawClassicNexusReaction, reactionAction, reactionPlayer } from './fixture-classic-independent-nexus-reactions';
import { advanceClassicIndependentNexus, type ClassicIndependentNexusStep } from './fixture-classic-independent-nexus';
import { classicDiscoveryNexusClean, nextClassicDiscoveryNexusStep } from './fixture-discovery-classic-nexus';
export type WorldNexusBattleStep = ClassicIndependentNexusStep;
export type WorldNexusBattleOptions = { advanced?: boolean; skills?: boolean; tech?: boolean; strongholds?: boolean };
export type WorldNexusBattleOptions = {
advanced?: boolean;
skills?: boolean;
tech?: boolean;
strongholds?: boolean;
/** Opt in before the original deal; default fixtures retain base33. */
independent?: boolean;
};
type WorldNexusInvasion = { before: Game; action: Action; cost: number; after: Game };
export const worldNexusClean = (game: Game) => classicDiscoveryNexusClean(game) &&
!game.pendingGuildBetrayal && !game.pendingNexusHarkonnenBetrayal && !game.pendingNexusIxianBetrayal;
@@ -59,7 +67,8 @@ function invade(game: Game, actor: string, location: string, amount: number, act
* route exports four actual stars to an empty desert, never converts them. */
export function createWorldNexusEmperorBattle(options: WorldNexusBattleOptions & { salusa?: 'high' | 'low' } = {}) {
const fixture = drawClassicNexusReaction('emperor', 'emperor', { ...options, advanced: true,
independent: false, homeworlds: true, roster: ['atreides', 'guild', 'emperor'],
independent: options.independent ?? false, homeworlds: true, roster: ['atreides', 'guild', 'emperor'],
startingCards: options.independent ? { emperor: [REINFORCEMENTS_CARD] } : undefined,
training: options.skills ? { emperor: 'planetologist', atreides: 'warmaster', guild: 'sandmaster' } : undefined });
const actions = fixture.actions, location = 'homeworld:emperor', trained = options.skills ? ['emperor'] : [];
let game = advanceWorldNexusBattle(fixture.game, state => state.phase === 5 && worldNexusClean(state), actions);
@@ -114,7 +123,7 @@ export function createWorldNexusEmperorBattle(options: WorldNexusBattleOptions &
* ordinary call, while the invader owns the independently drawn cancel card. */
export function createWorldNexusHarkonnenBattle(options: WorldNexusBattleOptions = {}) {
const fixture = drawClassicNexusReaction('harkonnen', 'atreides', { ...options,
independent: false, homeworlds: true, roster: ['atreides', 'harkonnen', 'guild'],
independent: options.independent ?? false, homeworlds: true, roster: ['atreides', 'harkonnen', 'guild'],
training: options.skills ? { harkonnen: 'planetologist', atreides: 'warmaster', guild: 'sandmaster' } : undefined });
const actions = fixture.actions, location = 'homeworld:harkonnen';
let game = advanceWorldNexusBattle(fixture.game, state => state.phase === 5 && state.active === 'atreides' &&