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-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-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 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-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 --profile nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-nexus-three --profile nexus --players 3 --rules advanced 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, Default and `--profile all` retain the original six fixed CHOAM/Richese,
Ecaz/Moritani and combined Basic/Advanced samples. `--profile base` selects ten Ecaz/Moritani and combined Basic/Advanced samples. `--profile base` selects ten
additional samples: Basic and Advanced with two through six players. additional samples: Basic and Advanced with two through six players.
`--players 2..6` narrows the base or Nexus profile; omission or `all` retains `--players 2..6` narrows the base, Homeworld or Nexus profile; omission or
all five counts. The fixed base roster adds Atreides, Harkonnen, Fremen, `all` retains all five counts. The fixed base roster adds Atreides, Harkonnen,
Emperor, Guild and Bene Gesserit in that order. These are sample rosters, not Fremen, Emperor, Guild and Bene Gesserit in that order. These are sample
an assertion that these are the only rules-permitted player-count rosters, not an assertion that these are the only rules-permitted player-count
configurations. The `homeworld` profile runs four base factions; `nexus` runs configurations. Homeworld and Nexus each run two through six base factions
two through six, each under Basic and Advanced using the actual under Basic and Advanced using the actual optional-module setup seams.
optional-module setup seams. Nexus output names include the player count; Their output names include the player count. Homeworld's original four-seat
the six-seat random seed offsets remain 38–39, and smaller rosters use 40–47. seed offsets remain 36–37; its other rosters use 48–51 and 54–57. Nexus's
They do not open public module starts or assert complete module interaction 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. coverage.
All-AI Nexus samples now make public reciprocal offers as well as accepting 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 the subsequent draw when a seat remains unallied. These samples do not
establish complete card-effect coverage. 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, `--profile moritani-skills` adds five Basic samples with Moritani, then Emperor,
Guild, Harkonnen, Fremen and Bene Gesserit. `--players` also narrows this profile; Guild, Harkonnen, Fremen and Bene Gesserit. `--players` also narrows this profile;
Advanced requests fail before running. Its seed offsets are 16–20, leaving 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'], ['nexus', 39, 'nexusCards', 'homeworlds'],
] as const) { ] as const) {
const out = join(area, profile); const out = join(area, profile);
const name = profile === 'nexus' ? 'nexus-6-advanced' : 'homeworld-advanced'; const name = profile === 'nexus' ? 'nexus-6-advanced' : 'homeworld-4-advanced';
const playerFilter = profile === 'nexus' ? ['--players', '6'] : []; const playerFilter = ['--players', profile === 'nexus' ? '6' : '4'];
assert.equal(run(out, '--profile', profile, '--rules', 'advanced', ...playerFilter, '--seed', '1000', '--max-actions', '1').status, 1); assert.equal(run(out, '--profile', profile, '--rules', 'advanced', ...playerFilter, '--seed', '1000', '--max-actions', '1').status, 1);
assertFailedEvidence(out, name, 1000 + ordinal, false); assertFailedEvidence(out, name, 1000 + ordinal, false);
const snapshot = join(out, `failed-${name}.json`); 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) => { void test('three-seat Nexus samples preserve their module and roster across saved continuation', (t) => {
const area = temporary(t); const area = temporary(t);
for (const [rules, ordinal] of [['basic', 42], ['advanced', 43]] as const) { 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[] = [ const MODULE_SCENARIOS: readonly Scenario[] = [
...(['basic', 'advanced'] as const).map((rules, index) => ({ ...[2, 3, 4, 5, 6].flatMap((players) =>
ordinal: 36 + index, (['basic', 'advanced'] as const).map((rules, index) => ({
profile: 'homeworld' as const, ordinal: players === 4 ? 36 + index : 48 + (players - 2) * 2 + index,
rules, profile: 'homeworld' as const,
expansions: [], rules,
roster: BASE_ROSTER.slice(0, 4), expansions: [],
})), roster: BASE_ROSTER.slice(0, players),
})),
),
...[2, 3, 4, 5, 6].flatMap((players) => ...[2, 3, 4, 5, 6].flatMap((players) =>
(['basic', 'advanced'] as const).map((rules, index) => ({ (['basic', 'advanced'] as const).map((rules, index) => ({
ordinal: players === 6 ? 38 + index : 40 + (players - 2) * 2 + 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] ' + '[--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] ' + '[--rules both|basic|advanced] [--players all|2|3|4|5|6] [--max-actions POSITIVE] ' +
'[--resume FAILED_GAME.json]\n' + '[--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) { 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.roster.length}-${scenario.rules}`
: `${scenario.profile}-${scenario.rules}`; : `${scenario.profile}-${scenario.rules}`;
} }
@@ -538,8 +540,8 @@ async function main() {
const profile = parseProfile(values.profile); const profile = parseProfile(values.profile);
const rules = parseRules(values.rules); const rules = parseRules(values.rules);
const players = parsePlayers(values.players); const players = parsePlayers(values.players);
if (supplied('players') && profile !== 'base' && profile !== 'nexus' && !skillProfile(profile)) if (supplied('players') && !['base', 'homeworld', 'nexus'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires --profile base, nexus or a skill profile.'); throw new Error('--players requires --profile base, homeworld, nexus or a skill profile.');
if (skillProfile(profile) && rules === 'advanced') if (skillProfile(profile) && rules === 'advanced')
throw new Error('Expansion Leader Skills profiles currently support only Basic samples.'); throw new Error('Expansion Leader Skills profiles currently support only Basic samples.');
const resume = values.resume ? resumedGame(values.resume) : null; const resume = values.resume ? resumedGame(values.resume) : null;