Files
Dune/tests/moritani-skills-integration.test.ts
Quintine 17adffa6a7 Connect paired E3 Skills without Nexus and retain native arrival independence
Reuse original paired roster validation with plain all14 native setup and optional independent cards. Preserve real coalition, trained death and Mentat paths; extend the existing paid Ambassador/Terror queue to paired Skills and card-only factions. Frozen29-file323/323, types/lint/build, native programmes and390px entry pass. No save conversion or deployment.
2026-10-17 21:12:35 +11:00

88 lines
4.6 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, normalizeAutomaticGame, viewGame, type Game } from '../game/engine';
import { LEADER_SKILL_CARDS } from '../game/leader-skill-cards';
import { planetologistMoveDraft } from '../components/planetologist-movement';
import { TERRITORIES, distance, location, splitLocation } from '../game/board';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { completedMoritaniSkillsGame, initializedMoritaniSkillsOffers, assertMoritaniSkillsCustody, moritaniSkillsPlayer as player, reloadMoritaniSkillsGame as reload, rejectMoritaniSkillsAction as reject } from './moritani-skills-fixture';
function movementPosition(game: Game): Game {
// Conserved focused position following genuine setup, not a played phase history.
for (const p of game.players) {
p.forces = {}; p.reserves = 20; p.tanks = 0;
p.spice = 20; p.moved = 0; p.shipped = false;
}
Object.assign(game, { phase: 5, active: 'm', order: ['m', 'a', 'e'], storm: 18,
ready: [], decision: null, response: null, phaseOpening: null });
return game;
}
void test('every canonical skill can be dealt and publicly assigned to Basic Moritani without reducing the deck', () => {
for (const card of LEADER_SKILL_CARDS) {
const game = completedMoritaniSkillsGame({ requestedSkill: card.id });
assert.equal(game.leaderSkills!.assignments.find(a => a.owner === 'm')!.skill, card.id);
assertMoritaniSkillsCustody(game);
for (const p of game.players) {
const view = viewGame(game, p.id);
assert.equal(view.leaderSkills!.offer, null);
assert.equal(view.leaderSkills!.assignments.length, 3);
assert.deepEqual(viewGame(reload(game), p.id), view);
}
}
});
void test('all four profiles legally choose from the same private Moritani setup offer', () => {
const game = initializedMoritaniSkillsOffers({ requestedSkill: 'planetologist' });
for (const difficulty of DIFFICULTIES) {
const view = viewGame(reload(game), 'm');
view.players.find(p => p.id === 'm')!.bot = difficulty;
const action = botActions(view)[0];
assert.equal(action.type, 'leaderSkill');
const next = applyAction(game, 'm', action);
assert.equal(next.leaderSkills!.assignments.find(a => a.owner === 'm')?.leader, action.leader);
assertMoritaniSkillsCustody(next);
}
});
void test('Moritani Planetologist controls and bots use the same extended movement through saved views', () => {
const game = movementPosition(completedMoritaniSkillsGame({ requestedSkill: 'planetologist' }));
const from = 'red_chasm:7';
player(game, 'm').forces = { [from]: 3 }; player(game, 'm').reserves = 17;
const to = TERRITORIES.filter(t => t.type === 'sand').flatMap(t => t.sectors.map(s => location(t.id, s)))
.find(key => splitLocation(key).sector !== 18 && distance(from, key, k => splitLocation(k).sector === 18) === 2)!;
assert.ok(to);
const target = splitLocation(to);
const draft = planetologistMoveDraft(viewGame(game, 'm'), 'range', target.territory, target.sector, { [from]: 3 }, {});
assert.equal(draft.blocked, null); assert.ok(draft.action);
const done = applyAction(reload(game), 'm', draft.action);
assert.equal(player(done, 'm').forces[to], 3);
assert.equal(player(done, 'm').forces[from] ?? 0, 0);
assert.equal(player(done, 'm').moved, 1);
assertMoritaniSkillsCustody(done);
});
void test('Moritani Smuggler ships the free companion once and Bureaucrat redirects a real paid bribe after restore', () => {
let game = movementPosition(completedMoritaniSkillsGame({ requestedSkill: 'smuggler' }));
const ship = { type: 'ship', territory: 'carthag', sector: 11, amount: 3, smuggler: true };
game = applyAction(game, 'm', ship);
assert.equal(player(game, 'm').forces['carthag:11'], 3);
assert.equal(player(game, 'm').reserves, 17);
assert.equal(player(game, 'm').spice, 18);
assert.deepEqual(normalizeAutomaticGame(reload(game)), game);
reject(game, 'm', ship); assertMoritaniSkillsCustody(game);
game = movementPosition(completedMoritaniSkillsGame({ requestedSkill: 'bureaucrat' }));
game = applyAction(game, 'a', { type: 'bribe', target: 'e', amount: 5 });
assert.equal(game.decision?.kind, 'bureaucratPayment');
assert.equal(game.decision?.player, 'm');
const event = game.bureaucratPaymentEvent;
const done = applyAction(reload(game), 'm', { type: 'decision', event, redirect: true });
assert.equal(player(done, 'a').spice, 15);
assert.equal(player(done, 'e').bribes, 3);
assert.equal(done.bureaucratPayments!.used.length, 1);
reject(done, 'm', { type: 'decision', event, redirect: true });
assertMoritaniSkillsCustody(done);
});