Exercise Homeworld games across two to six seats

This commit is contained in:
Quintine committed 2026-09-27 21:23:04 +10:00
1 parent 628c536218
commit 6124618d42
3 files changed
+52 -23

No files matched your search

+17 -10
View File
@@ -97,7 +97,7 @@ node --import tsx tools/faction-games.ts --out /tmp/dune-new-moritani-skills --p
node --import tsx tools/faction-games.ts --out /tmp/dune-new-tleilaxu-skills --profile tleilaxu-skills
node --import tsx tools/faction-games.ts --out /tmp/dune-new-base-three --profile base --players 3 --rules advanced
node --import tsx tools/faction-games.ts --out /tmp/dune-new-combined --profile combined --rules advanced --seed 20260926
# Homeworld (four base factions) and Nexus (two through six), Basic and Advanced.
# Homeworld and Nexus with two through six base factions, Basic and Advanced.
node --import tsx tools/faction-games.ts --out /tmp/dune-new-homeworld --profile homeworld
node --import tsx tools/faction-games.ts --out /tmp/dune-new-nexus --profile nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-nexus-three --profile nexus --players 3 --rules advanced
@@ -107,15 +107,16 @@ node --import tsx tools/faction-games.ts --out /tmp/dune-new-resume --resume /pr
Default and `--profile all` retain the original six fixed CHOAM/Richese,
Ecaz/Moritani and combined Basic/Advanced samples. `--profile base` selects ten
additional samples: Basic and Advanced with two through six players.
`--players 2..6` narrows the base or Nexus profile; omission or `all` retains
all five counts. The fixed base roster adds Atreides, Harkonnen, Fremen,
Emperor, Guild and Bene Gesserit in that order. These are sample rosters, not
an assertion that these are the only rules-permitted player-count
configurations. The `homeworld` profile runs four base factions; `nexus` runs
two through six, each under Basic and Advanced using the actual
optional-module setup seams. Nexus output names include the player count;
the six-seat random seed offsets remain 38–39, and smaller rosters use 40–47.
They do not open public module starts or assert complete module interaction
`--players 2..6` narrows the base, Homeworld or Nexus profile; omission or
`all` retains all five counts. The fixed base roster adds Atreides, Harkonnen,
Fremen, Emperor, Guild and Bene Gesserit in that order. These are sample
rosters, not an assertion that these are the only rules-permitted player-count
configurations. Homeworld and Nexus each run two through six base factions
under Basic and Advanced using the actual optional-module setup seams.
Their output names include the player count. Homeworld's original four-seat
seed offsets remain 36–37; its other rosters use 48–51 and 54–57. Nexus's
six-seat offsets remain 38–39; its smaller rosters use 40–47. These samples
do not open public module starts or assert complete module interaction
coverage.
All-AI Nexus samples now make public reciprocal offers as well as accepting
@@ -128,6 +129,12 @@ three-seat Advanced sample and five-seat Basic sample reached actual
the subsequent draw when a seat remains unallied. These samples do not
establish complete card-effect coverage.
At the 20260927 seed, all ten Homeworld samples completed with no rejected
actions and repeated JSON restores. The six-seat Basic game reached actual
Junction offers/shipments and a Caladan reinforcement decision; these
observations do not certify every Homeworld occupation or expansion
interaction.
`--profile moritani-skills` adds five Basic samples with Moritani, then Emperor,
Guild, Harkonnen, Fremen and Bene Gesserit. `--players` also narrows this profile;
Advanced requests fail before running. Its seed offsets are 16–20, leaving
+22 -2
View File
@@ -180,8 +180,8 @@ void test('Homeworld and Nexus sample snapshots resume only their matching modul
['nexus', 39, 'nexusCards', 'homeworlds'],
] as const) {
const out = join(area, profile);
const name = profile === 'nexus' ? 'nexus-6-advanced' : 'homeworld-advanced';
const playerFilter = profile === 'nexus' ? ['--players', '6'] : [];
const name = profile === 'nexus' ? 'nexus-6-advanced' : 'homeworld-4-advanced';
const playerFilter = ['--players', profile === 'nexus' ? '6' : '4'];
assert.equal(run(out, '--profile', profile, '--rules', 'advanced', ...playerFilter, '--seed', '1000', '--max-actions', '1').status, 1);
assertFailedEvidence(out, name, 1000 + ordinal, false);
const snapshot = join(out, `failed-${name}.json`);
@@ -200,6 +200,26 @@ void test('Homeworld and Nexus sample snapshots resume only their matching modul
}
});
void test('Homeworld samples cover each two-to-six-seat roster under both rules and resume the two-seat configuration', (t) => {
const area = temporary(t);
const out = join(area, 'homeworld');
assert.equal(run(out, '--profile', 'homeworld', '--seed', '1000', '--max-actions', '1').status, 1);
assert.deepEqual(json<{ results: CliResult[] }>(join(out, 'results.json')).results.map(({ name, seed }) => [name, seed]), [
['homeworld-2-basic', 1048], ['homeworld-2-advanced', 1049],
['homeworld-3-basic', 1050], ['homeworld-3-advanced', 1051],
['homeworld-4-basic', 1036], ['homeworld-4-advanced', 1037],
['homeworld-5-basic', 1054], ['homeworld-5-advanced', 1055],
['homeworld-6-basic', 1056], ['homeworld-6-advanced', 1057],
]);
const snapshot = join(out, 'failed-homeworld-2-advanced.json');
const game = json<{ players: unknown[]; homeworlds: unknown }>(snapshot);
assert.equal(game.players.length, 2);
assert.ok(game.homeworlds);
const resumed = join(area, 'resumed');
assert.equal(run(resumed, '--resume', snapshot, '--seed', '1000', '--max-actions', '1').status, 1);
assertFailedEvidence(resumed, 'homeworld-2-advanced', 1049, true);
});
void test('three-seat Nexus samples preserve their module and roster across saved continuation', (t) => {
const area = temporary(t);
for (const [rules, ordinal] of [['basic', 42], ['advanced', 43]] as const) {
+13 -11
View File
@@ -103,13 +103,15 @@ const BASE_SCENARIOS: readonly Scenario[] = [2, 3, 4, 5, 6].flatMap((players) =>
})),
);
const MODULE_SCENARIOS: readonly Scenario[] = [
...(['basic', 'advanced'] as const).map((rules, index) => ({
ordinal: 36 + index,
profile: 'homeworld' as const,
rules,
expansions: [],
roster: BASE_ROSTER.slice(0, 4),
})),
...[2, 3, 4, 5, 6].flatMap((players) =>
(['basic', 'advanced'] as const).map((rules, index) => ({
ordinal: players === 4 ? 36 + index : 48 + (players - 2) * 2 + index,
profile: 'homeworld' as const,
rules,
expansions: [],
roster: BASE_ROSTER.slice(0, players),
})),
),
...[2, 3, 4, 5, 6].flatMap((players) =>
(['basic', 'advanced'] as const).map((rules, index) => ({
ordinal: players === 6 ? 38 + index : 40 + (players - 2) * 2 + index,
@@ -195,7 +197,7 @@ function usage() {
'[--seed UINT32] [--profile all|base|choam|ecaz|combined|homeworld|nexus|moritani-skills|tleilaxu-skills|ix-skills|choam-skills] ' +
'[--rules both|basic|advanced] [--players all|2|3|4|5|6] [--max-actions POSITIVE] ' +
'[--resume FAILED_GAME.json]\n' +
'Runs genuine setup and gameplay offline. Default/all keeps the six expansion samples; base, Nexus and skill profiles default to their 2–6-player samples. Homeworld uses four base factions. --players requires --profile base, nexus or a skill profile. Output must be a new private directory outside the checkout.'
'Runs genuine setup and gameplay offline. Default/all keeps the six expansion samples; base, Homeworld, Nexus and skill profiles default to their 2–6-player samples. --players requires one of these profiles. Output must be a new private directory outside the checkout.'
);
}
@@ -226,7 +228,7 @@ function positive(value: string | undefined) {
}
function scenarioName(scenario: Scenario) {
return scenario.profile === 'base' || scenario.profile === 'nexus' || skillProfile(scenario.profile)
return scenario.profile === 'base' || scenario.profile === 'homeworld' || scenario.profile === 'nexus' || skillProfile(scenario.profile)
? `${scenario.profile}-${scenario.roster.length}-${scenario.rules}`
: `${scenario.profile}-${scenario.rules}`;
}
@@ -538,8 +540,8 @@ async function main() {
const profile = parseProfile(values.profile);
const rules = parseRules(values.rules);
const players = parsePlayers(values.players);
if (supplied('players') && profile !== 'base' && profile !== 'nexus' && !skillProfile(profile))
throw new Error('--players requires --profile base, nexus or a skill profile.');
if (supplied('players') && !['base', 'homeworld', 'nexus'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires --profile base, homeworld, nexus or a skill profile.');
if (skillProfile(profile) && rules === 'advanced')
throw new Error('Expansion Leader Skills profiles currently support only Basic samples.');
const resume = values.resume ? resumedGame(values.resume) : null;