Files
Dune/tests/ecaz-homeworld-victory-runtime.test.ts
Quintine e05a205a47 Compose the deferred Terror arrival and green the mixed E3 programmes
Queue a committed Terror entry whose arrival also opened its own
interaction — Guild income, a Bene Gesserit fighter Intrusion or advisor
choice, or Fremen storm protection — in the additive
Game.pendingArrivalReaction list, and open the oldest queued entry from
settleAutomaticContinuations once nothing is pending. Payment, force
transfer and the original movement never replay; a Terror entry already in
flight and every Ambassador combination still reject.

Both withdrawn mixed E3 programme pairs are green: Moritani 9/9 and Ecaz
14/14, stable across 20 consecutive full-file runs. Their recorded resume
constraints became real fixture repairs, and the two restored consumer
omissions were corrected against the engine rather than re-pinned.

Repair pre-existing failures inherited at 45e6084 against current engine
behavior: stale Discovery and module gates in the native skills, Ix and
expansion-faction prototypes, the advanced Moritani skill boundary, the
Richese Nexus betrayal sale-credit expectation, one incidental gate
message and one flaky Collection-readiness assertion that reproduced at
HEAD.

Offline suite 7,793/7,793 with typecheck and lint clean. No game, seat,
saved-state, deployment, public-start or played-save conversion changes.
2026-10-07 17:24:23 +11:00

122 lines
7.3 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { applyAction, normalizeAutomaticGame, viewGame, RuleError, type Game } from '../game/engine';
import { quoteVictory, VictoryQuoteError } from '../game/victory-quote';
import { strongholdProgress } from '../game/victory-progress';
import { ecazHomeworldFixture, ecazSeat, observeEcazPosition } from './fixture-ecaz-homeworld-victory';
import { biddingPhysicalIds } from './fixture-homeworld-occupied-bidding';
import { TERRITORIES } from '../game/board';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
function originalCollection(game: Game): Game {
game = normalizeAutomaticGame(game);
for (let step = 0; !(game.phase === 7 && !game.phaseOpening && !game.response && !game.decision) && step < 300; step++) {
if (game.phaseOpening) {
const player = game.players.find(player => !game.phaseOpening!.passed.includes(player.id))!;
game = applyAction(game, player.id, { type: 'ready' });
} else if (game.response) {
const player = game.players.find(player => !game.response!.passed.includes(player.id))!;
game = applyAction(game, player.id, { type: 'passResponse' });
} else if (game.decision?.kind === 'homeworldOccupiedIncome') {
const player = game.decision.player, offer = viewGame(game, player).homeworldOccupiedIncome!;
game = applyAction(game, player, { type: 'decision', event: offer.event, world: offer.world, ownAmount: offer.amount });
} else if (game.decision?.kind === 'guildTiming')
game = applyAction(game, game.decision.player, { type: 'decision', take: true });
else if (game.phase === 5 && game.active && !game.decision)
game = applyAction(game, game.active, { type: 'endMovement' });
else if (game.phase === 6 && !game.decision) {
const player = game.players.find(player => !game.ready.includes(player.id))!;
game = applyAction(game, player.id, { type: 'ready' });
} else throw new Error('The original Collection fixture requires another supported owned choice.');
}
assert.equal(game.phase, 7);
return game;
}
function originalMentat(game: Game) {
for (const player of game.players) if (!game.ready.includes(player.id))
game = applyAction(JSON.parse(JSON.stringify(game)), player.id, { type: 'ready' });
assert.equal(game.phase, 8);
return game;
}
void test('actual original Mentat awards the high Ecaz two-native-world alliance win with one joint stronghold and no ordinary point inflation', () => {
const fixture = ecazHomeworldFixture(true);
let game = originalCollection(fixture.game);
const ordinary = strongholdProgress(game).progress.find(row => row.player === 'ec')!;
assert.equal(ordinary.strongholds.length, 1); assert.equal(ordinary.jointlyOccupied.length, 1); assert.equal(ordinary.qualifies, false);
assert.equal(game.status, 'playing'); assert.deepEqual(game.winner, []);
const row = viewGame(game, 'ec').ecazHomeworldVictory!;
assert.equal(row.population, 7); assert.equal(row.qualifies, true);
assert.deepEqual(new Set(row.distinctNativeFactions), new Set(['atreides', 'emperor']));
const cards = biddingPhysicalIds(game), forces = game.players.map(player => ({ id: player.id, forces: player.forces, reserves: player.reserves, tanks: player.tanks }));
for (const difficulty of DIFFICULTIES) {
let result: Game = JSON.parse(JSON.stringify(game));
for (const player of result.players) {
const view = viewGame(result, player.id); view.players.find(seat => seat.id === player.id)!.bot = difficulty;
// The bot policy may prefer another legal Collection action; the
// original readiness itself is the contract under test here.
const ready = botActions(view).find(action => action.type === 'ready') ?? { type: 'ready' as const };
result = applyAction(result, player.id, ready);
}
assert.equal(result.status, 'finished');
assert.deepEqual(new Set(result.winner), new Set(['ec', 'al']));
assert.deepEqual(biddingPhysicalIds(result), cards);
}
game = originalMentat(game);
assert.equal(game.status, 'finished'); assert.deepEqual(new Set(game.winner), new Set(['ec', 'al']));
assert.deepEqual(biddingPhysicalIds(game), cards);
assert.deepEqual(game.players.map(player => ({ id: player.id, forces: player.forces, reserves: player.reserves, tanks: player.tanks })), forces);
});
void test('native population6 does not award this win despite the same actual joint site and two occupied native factions', () => {
const fixture = ecazHomeworldFixture(true);
ecazSeat(fixture.game, 'ec').reserves--; ecazSeat(fixture.game, 'ec').tanks++;
observeEcazPosition(fixture.game, 'controlled-native-six-before-original-check');
let game = originalCollection(fixture.game);
assert.equal(viewGame(game, 'ec').ecazHomeworldVictory!.high, false);
game = originalMentat(game);
assert.equal(game.status, 'playing'); assert.deepEqual(game.winner, []);
});
for (const predicted of ['ecaz', 'guild'] as const) void test(`actual BG prediction of ${predicted} replaces the printed Ecaz alliance win without changing public progress`, () => {
let game = originalCollection(ecazHomeworldFixture(true).game);
const progress = viewGame(game, 'ec').ecazHomeworldVictory!;
ecazSeat(game, 'observer').prediction = { faction: predicted, turn: game.turn };
assert.equal(viewGame(game, 'al').ecazHomeworldVictory!.qualifies, progress.qualifies);
game = originalMentat(game);
assert.deepEqual(game.winner, ['observer']); assert.equal(game.status, 'finished');
});
void test('the original winner quote unions a simultaneous ordinary side before applying private prediction', () => {
const fixture = ecazHomeworldFixture(true);
const game = originalCollection(fixture.game);
// Labelled surviving native counters: a different faction holds three ordinary sites.
const sites = TERRITORIES.filter(site => site.type === 'stronghold' &&
site.id !== fixture.jointStronghold && !site.sectors.includes(game.storm)).slice(0, 3)
.map(site => `${site.id}:${site.sectors[0]}`);
assert.equal(sites.length, 3);
const other = ecazSeat(game, 'observer');
assert.equal(other.faction, 'beneGesserit');
delete other.advisors; other.forces = Object.fromEntries(sites.map(site => [site, 1])); other.reserves -= 3;
observeEcazPosition(game, 'controlled-separate-ordinary-winner');
const quote = quoteVictory(game, game);
assert.deepEqual(new Set(quote.winner), new Set(['ec', 'al', 'observer']));
assert.equal(quote.ecazHomeworld?.qualifies, true);
});
void test('a wrong detached public Ecaz source cannot authorize an otherwise ordinary original victory quote', () => {
const game = originalCollection(ecazHomeworldFixture(true).game), changed = structuredClone(game);
changed.turn++;
const before = structuredClone(game);
assert.throws(() => quoteVictory(game, changed), VictoryQuoteError);
assert.deepEqual(game, before);
const markerless = structuredClone(game); delete markerless.homeworldEcazVictoryPreview;
assert.deepEqual(quoteVictory(markerless).winner, []);
assert.equal(viewGame(markerless, 'ec').ecazHomeworldVictory, null);
const malformed = structuredClone(game); delete malformed.homeworldOccupationHistory;
const frozen = structuredClone(malformed);
assert.throws(() => applyAction(malformed, 'ec', { type: 'ready' }), RuleError);
assert.deepEqual(malformed, frozen);
});