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

+7 -3
View File
@@ -1,4 +1,4 @@
import type { CombatForces } from './combat';
import { validCombatForces, type CombatForces } from './combat';
import {
homeworldForceGroups, type HomeworldCustody, type HomeworldCustodyContext,
type HomeworldForces,
@@ -63,14 +63,18 @@ export function quoteReinforcementsHomeworldCost(context: HomeworldCustodyContex
/** On an own World the reserve fee and combat army share physical counters.
* Reserve the fee first, but retain the original sealed support/strength traits.
* Provisionally spend ordinary counters without a temporary role first, then
* remove that role only from converted counters actually used to pay the fee.
* Foreign-World and Arrakis callers pass no on-location cost. */
export function reinforcementsBattleForces(forces: CombatForces,
onLocationCost?: HomeworldForces): CombatForces {
if (!onLocationCost) return forces;
const { normal, elite } = onLocationCost;
if (!Number.isSafeInteger(normal) || normal < 0 || !Number.isSafeInteger(elite) || elite < 0 ||
if (!validCombatForces(forces) || !Number.isSafeInteger(normal) || normal < 0 || !Number.isSafeInteger(elite) || elite < 0 ||
normal > forces.normal || elite > forces.elite)
throw new ReinforcementsError('The reserve fee cannot also fund the physical Homeworld dial.');
if (normal + elite === 0) return forces;
return { ...forces, normal: forces.normal - normal, elite: forces.elite - elite };
const remainingNormal = forces.normal - normal;
return { ...forces, normal: remainingNormal, elite: forces.elite - elite,
...(forces.temporaryElite === undefined ? {} : {temporaryElite: Math.min(forces.temporaryElite, remainingNormal)}) };
}