Retain selected decks and original optional modules through normal setup, distinguish native Ixian Betrayal from absent-faction replacement, route explicit CHOAM fuel before printed effects, and preserve native No-Field invoices and pair continuation. Connect canonical native cancellation stock and existing human/minimal AI consumers. Validation: 194 affected tests across 19 files, types/lint/build, bounded native invoice review and seven finite original-action/rendered-control programmes. Runtime ec5ad6c18996d8d958ec0d01132ef3e3ea6d23d36188902eca9a63d4db2efbf8; documentation-only proof overlay a5892004937434429916496497c07a3838a324b4a5d64d4a0eb804ab5212d5bd. Other envelopes, full combinations, comprehensive assurance and deployed acceptance remain open. No saved-room/provider changes.
50 lines
2.4 KiB
TypeScript
50 lines
2.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { applyAction, createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import type { FactionId } from '../game/catalog';
|
|
|
|
export type SelectedIndependentEntryOptions = {
|
|
expansions: Game['expansions'];
|
|
factions: readonly FactionId[];
|
|
advanced?: boolean;
|
|
skills?: boolean;
|
|
homeworlds?: boolean;
|
|
tech?: boolean;
|
|
strongholds?: boolean;
|
|
};
|
|
|
|
/** Ordinary host controls and original private choices; no dealt inventory edits. */
|
|
export function createSelectedIndependentEntry(options: SelectedIndependentEntryOptions) {
|
|
const [host, ...others] = options.factions;
|
|
let game = createGame('SELECTEDINDEPENDENTNEXUS', newPlayer(host, host, host), options.advanced ?? false, options.expansions);
|
|
for (const faction of others) joinGame(game, newPlayer(faction, faction, faction));
|
|
const actions: {actor: string; action: Action}[] = [];
|
|
const act = (actor: string, action: Action) => {
|
|
actions.push(structuredClone({actor, action}));
|
|
game = applyAction(game, actor, action);
|
|
};
|
|
act(host, {type:'moduleOption', module:'ecazTreachery', enabled:true});
|
|
act(host, {type:'moduleOption', module:'nexusCards', enabled:true});
|
|
if (options.skills) act(host, {type:'moduleOption', module:'leaderSkills', enabled:true});
|
|
if (options.homeworlds) act(host, {type:'homeworlds', enabled:true});
|
|
if (options.tech) act(host, {type:'techTokens', enabled:true});
|
|
if (options.strongholds) act(host, {type:'strongholdCards', enabled:true});
|
|
for (const player of game.players) act(player.id, {type:'ready'});
|
|
const lobby = structuredClone(game);
|
|
act(host, options.advanced ? {type:'start', advancedPreview:true} : {type:'start'});
|
|
const setup = structuredClone(game);
|
|
for (let count = 0; game.status === 'setup' && count < 100; count++) {
|
|
let next: {actor:string; action:Action} | undefined;
|
|
for (const player of game.players) {
|
|
const view = viewGame(game, player.id);
|
|
view.players.find(seat => seat.id === player.id)!.bot = 'Easy';
|
|
const action = botActions(view)[0];
|
|
if (action) { next = {actor:player.id, action}; break; }
|
|
}
|
|
assert.ok(next, `Original ${game.setupStage} setup must have an owned legal choice`);
|
|
act(next.actor, next.action);
|
|
}
|
|
assert.equal(game.status, 'playing');
|
|
return {lobby, setup, game, actions};
|
|
}
|