Compose independent Skills with original Tech and Stronghold modules

Unify fresh/live classic and native phase admission, retaining original counts, earned custody, separate income ledgers and physical card/skill continuations. Plain paired E3 phases no longer require cards or Nexus. Coupled44-file471/471, types/lint/build, genuine programmes and390px normal entry pass. Controlled card inputs explicit; no save retrofit or deployment.
This commit is contained in:
Quintine committed 2026-10-17 22:12:44 +11:00
1 parent 17adffa6a7
commit b9330d489e
31 files changed
+797 -104

No files matched your search

+20 -5
View File
@@ -5,7 +5,9 @@ import { territory } from '../game/board';
import { finishEcazOccupySetup, type EcazOccupyFixture } from './fixture-ecaz-occupy';
export type HomeworldEcazStep = { actor: string; action: Action };
export type HomeworldEcazOccupyFixture = EcazOccupyFixture & { actions: HomeworldEcazStep[] };
export type HomeworldEcazOccupyFixture = EcazOccupyFixture & {
actions: HomeworldEcazStep[]; afterFirstMentat?: Game; strongholdTerritory?: string;
};
export const homeworldEcazClean = (game: Game) =>
!game.phaseOpening && !game.response && !game.decision && !game.pendingTreacheryDiscard;
@@ -39,6 +41,7 @@ export function nextHomeworldEcazStep(game: Game): HomeworldEcazStep | null {
export function createHomeworldEcazOccupyFixture(options: {
advanced?: boolean; skills?: boolean; independent?: boolean;
allyFaction?: 'emperor' | 'moritani';
tech?: boolean; strongholds?: boolean;
/** No-World control uses the same genuine alliance/Arrakis route. */
homeworlds?: boolean;
/** Select only actual offered setup choices; the captured initial stays untouched. */
@@ -56,6 +59,8 @@ export function createHomeworldEcazOccupyFixture(options: {
game = applyAction(game, actor, action);
};
if (homeworlds) act('ecaz', { type: 'homeworlds', enabled: true });
if (options.tech) act('ecaz', { type: 'techTokens', enabled: true });
if (options.strongholds) act('ecaz', { type: 'strongholdCards', enabled: true });
if (options.independent) act('ecaz', { type: 'moduleOption', module: 'ecazTreachery', enabled: true });
if (options.skills) act('ecaz', { type: 'moduleOption', module: 'leaderSkills', enabled: true });
for (const player of game.players) act(player.id, { type: 'ready' });
@@ -82,10 +87,19 @@ export function createHomeworldEcazOccupyFixture(options: {
orderBlow(false, 0);
if (advanced) orderBlow(false, 1);
reach(g => g.phase === 5 && homeworldEcazClean(g));
const strongholdTerritory = options.strongholds ? 'arrakeen' : undefined;
while (game.phase === 5) {
if (homeworldEcazClean(game)) act(game.active!, { type: 'endMovement' }); else step();
if (!homeworldEcazClean(game)) { step(); continue; }
const actor = game.active!;
if (strongholdTerritory && actor === 'ecaz' && !game.players.find(player => player.id === actor)!.shipped) {
act(actor, { type: 'ship', territory: strongholdTerritory,
sector: territory(strongholdTerritory).sectors[0], amount: 4, elite: 0, allyPayment: 0, smuggler: false });
reach(homeworldEcazClean);
}
act(actor, { type: 'endMovement' });
}
reach(g => g.turn === 2 && g.phase === 0 && !g.phaseOpening);
const afterFirstMentat = options.tech || options.strongholds ? structuredClone(game) : undefined;
orderBlow(true, 0);
orderBlow(false, 1);
if (advanced) orderBlow(false, 2);
@@ -93,7 +107,7 @@ export function createHomeworldEcazOccupyFixture(options: {
act('ecaz', { type: 'alliance', target: ally });
act(ally, { type: 'alliance', target: 'ecaz' });
reach(g => g.phase === 5 && homeworldEcazClean(g));
const battleTerritory = ['arrakeen', 'sietch_tabr', 'habbanya_ridge_sietch']
const battleTerritory = strongholdTerritory ?? ['arrakeen', 'sietch_tabr', 'habbanya_ridge_sietch']
.find(id => territory(id).sectors[0] !== game.storm &&
!game.players.some(player => Object.entries(player.forces).some(([key, amount]) =>
amount > 0 && key.startsWith(`${id}:`))))!;
@@ -103,7 +117,7 @@ export function createHomeworldEcazOccupyFixture(options: {
while (game.phase === 5) {
if (!homeworldEcazClean(game)) { step(); continue; }
const actor = game.active!;
if (!shipped.has(actor)) {
if (!shipped.has(actor) && !(strongholdTerritory && actor === 'ecaz')) {
act(actor, { type: 'ship', territory: battleTerritory, sector,
amount: actor === 'guild' ? 2 : 4,
elite: actor === ally && ally === 'emperor' && (advanced || homeworlds) ? 2 : 0, allyPayment: 0 });
@@ -118,6 +132,7 @@ export function createHomeworldEcazOccupyFixture(options: {
act(choice.chooser, { type: 'chooseBattle', territory: battleTerritory,
target: choice.attacker === choice.chooser ? choice.defender : choice.attacker });
if (!options.skills) assert.equal(game.decision?.kind, 'ecazBattleLead');
return { game, initial, afterSetup, actions, ecaz: 'ecaz', ally, opponent: 'guild',
return { game, initial, afterSetup, actions, afterFirstMentat, strongholdTerritory,
ecaz: 'ecaz', ally, opponent: 'guild',
territory: battleTerritory, location, ecazForces: 4 };
}