Sample Ixian and Tleilaxu expansion rosters

This commit is contained in:
Quintine committed 2026-09-27 22:19:59 +10:00
1 parent 2fbc754484
commit 2183376edc
4 files changed
+41 -17

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Dune implementation status
## 27 September 2026 — Ixian/Tleilaxu standalone roster samples
The [expansion sample runner](VERIFICATION_WORKFLOW.md#reusable-base-and-faction-sample-games) now exercises the Ixian/Tleilaxu pair with two through six seats under Basic and Advanced rules. At seed 20260927 all ten genuine setup/play games completed: 6,365 accepted actions, 169 JSON restores and no rejected actions. The six-seat Advanced trace reaches both Ixian Technology and Tleilaxu Face Dancer decisions, along with battle, revival and shipment paths. An interrupted four-seat Advanced run also resumes to completion. The source-bound report is `/tmp/dune-ix-roster-checkpoint-20260927-final/report.json`. This is bounded playable coverage, not full E1 rules certification or public expansion-start authorization.
## 27 September 2026 — CHOAM/Richese and Ecaz/Moritani roster samples
The [sample runner](VERIFICATION_WORKFLOW.md#reusable-base-and-faction-sample-games) now covers each expansion pair in Basic and Advanced at two through six seats, using genuine faction setup, legal AI decisions and periodic JSON restoration. The ten CHOAM/Richese games at seed 20260928 and ten Ecaz/Moritani games at seed 20260927 completed: 6,523 accepted actions, 167 restores, no rejected actions. Source-bound checkpoint reports are `/tmp/dune-choam-roster-checkpoint-20260928/report.json` and `/tmp/dune-ecaz-roster-checkpoint-20260927/report.json`. A separate CHOAM/Richese seed 20260927 completed nine games but stopped in six-seat Advanced Bidding after 1,728 accepted actions: an exhausted Richese cache reached the existing explicit unresolved normal-auction-count guard. That source question remains open, not papered over by the passing matrix. These samples do not certify full expansion rules, all rosters or public starts.
+10 -8
View File
@@ -97,12 +97,13 @@ 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 and Nexus with two through six base factions, Basic and Advanced.
# Optional-module and expansion rosters, two through six seats, 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-homeworld-nexus --profile homeworld-nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-choam-roster --profile choam-roster
node --import tsx tools/faction-games.ts --out /tmp/dune-new-ecaz-roster --profile ecaz-roster
node --import tsx tools/faction-games.ts --out /tmp/dune-new-ix-roster --profile ix-roster
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-resume --resume /private/failed-combined-advanced.json
```
@@ -111,16 +112,17 @@ 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, Homeworld, Nexus, combined
`homeworld-nexus`, `choam-roster` or `ecaz-roster` 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 they are the only rules-permitted player-count
`homeworld-nexus`, `choam-roster`, `ecaz-roster` or `ix-roster` 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 they are the only rules-permitted player-count
configurations. Each optional module profile runs two through six base factions
under Basic and Advanced using the actual setup seams. The two expansion-roster
under Basic and Advanced using the actual setup seams. The three expansion-roster
profiles also run Basic and Advanced with their respective physical expansion
enabled: CHOAM/Richese start with CHOAM then Richese, Ecaz/Moritani with
Moritani then Ecaz; subsequent seats extend each to six without changing
existing fixed four-seat samples. Their seed offsets are 68–77 and 78–87.
Moritani then Ecaz, Ixian/Tleilaxu with Ixians then Tleilaxu; subsequent seats
extend each to six without changing existing fixed four-seat samples. Their
seed offsets are 68–77, 78–87 and 88–97.
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. The combined module uses
+18 -1
View File
@@ -255,7 +255,7 @@ void test('combined modules reach saved Nexus draws and actual Homeworld shipmen
void test('expansion-roster snapshots resume their own Advanced games rather than the fixed four-seat sample', (t) => {
const area = temporary(t);
for (const [profile, seed] of [['choam-roster', '20260928'], ['ecaz-roster', '20260927']] as const) {
for (const [profile, seed] of [['choam-roster', '20260928'], ['ecaz-roster', '20260927'], ['ix-roster', '20260927']] as const) {
const name = `${profile}-4-advanced`;
const initial = join(area, `${profile}-initial`);
assert.equal(run(initial, '--profile', profile, '--players', '4', '--rules', 'advanced', '--seed', seed, '--max-actions', '1').status, 1);
@@ -274,6 +274,23 @@ void test('expansion-roster snapshots resume their own Advanced games rather tha
}
});
void test('six-seat Advanced Ixian/Tleilaxu game resolves Technology and Face Dancers through JSON restores', (t) => {
const out = join(temporary(t), 'ix-complete');
const runResult = run(out, '--profile', 'ix-roster', '--players', '6', '--rules', 'advanced', '--seed', '20260927');
assert.equal(runResult.status, 0, runResult.stderr);
const evidence = json<{ results: (CliResult & {
used: Record<string, number>;
rejected: Record<string, number>;
restores: number;
})[] }>(join(out, 'results.json')).results[0];
assert.equal(evidence.name, 'ix-roster-6-advanced');
assert.equal(evidence.outcome, 'complete');
assert.ok(evidence.used['decision:ixTechnology'] > 0);
assert.ok(evidence.used['decision:faceDance'] > 0);
assert.deepEqual(evidence.rejected, {});
assert.ok(evidence.restores > 0);
});
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) {
+9 -8
View File
@@ -28,7 +28,7 @@ import { privateOutputDirectory, sourceSnapshot } from './verification';
const DEFAULT_SEED = 20_260_926;
const DEFAULT_MAX_ACTIONS = 3_500;
const DIFFICULTIES = ['Easy', 'Medium', 'Hard', 'Brutal'] as const;
type Profile = 'base' | 'choam' | 'ecaz' | 'combined' | 'homeworld' | 'nexus' | 'homeworld-nexus' | 'choam-roster' | 'ecaz-roster' | 'moritani-skills' | 'tleilaxu-skills' | 'ix-skills' | 'choam-skills';
type Profile = 'base' | 'choam' | 'ecaz' | 'combined' | 'homeworld' | 'nexus' | 'homeworld-nexus' | 'choam-roster' | 'ecaz-roster' | 'ix-roster' | 'moritani-skills' | 'tleilaxu-skills' | 'ix-skills' | 'choam-skills';
type Rules = 'basic' | 'advanced';
type Scenario = {
@@ -134,6 +134,7 @@ const MODULE_SCENARIOS: readonly Scenario[] = [
const EXPANSION_ROSTER_SCENARIOS: readonly Scenario[] = [
{ profile: 'choam-roster' as const, expansions: ['choam'], roster: ['choam', 'richese', 'emperor', 'guild', 'harkonnen', 'fremen'] as FactionId[] },
{ profile: 'ecaz-roster' as const, expansions: ['ecaz'], roster: ['moritani', 'ecaz', 'atreides', 'beneGesserit', 'harkonnen', 'guild'] as FactionId[] },
{ profile: 'ix-roster' as const, expansions: ['ix'], roster: ['ixians', 'tleilaxu', 'emperor', 'guild', 'harkonnen', 'fremen'] as FactionId[] },
].flatMap(({ profile, expansions, roster }, family) =>
[2, 3, 4, 5, 6].flatMap(players =>
(['basic', 'advanced'] as const).map((rules, index) => ({
@@ -214,7 +215,7 @@ type Result = {
function usage() {
return (
'Usage: node --import tsx tools/faction-games.ts --out NEW_PRIVATE_DIR ' +
'[--seed UINT32] [--profile all|base|choam|ecaz|combined|homeworld|nexus|homeworld-nexus|choam-roster|ecaz-roster|moritani-skills|tleilaxu-skills|ix-skills|choam-skills] ' +
'[--seed UINT32] [--profile all|base|choam|ecaz|combined|homeworld|nexus|homeworld-nexus|choam-roster|ecaz-roster|ix-roster|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, Homeworld, Nexus, combined Homeworld-Nexus, expansion roster 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.'
@@ -249,15 +250,15 @@ function positive(value: string | undefined) {
function scenarioName(scenario: Scenario) {
return scenario.profile === 'base' || scenario.profile === 'homeworld' || scenario.profile === 'nexus' || scenario.profile === 'homeworld-nexus' ||
scenario.profile === 'choam-roster' || scenario.profile === 'ecaz-roster' || skillProfile(scenario.profile)
scenario.profile === 'choam-roster' || scenario.profile === 'ecaz-roster' || scenario.profile === 'ix-roster' || skillProfile(scenario.profile)
? `${scenario.profile}-${scenario.roster.length}-${scenario.rules}`
: `${scenario.profile}-${scenario.rules}`;
}
function parseProfile(value: string | undefined) {
const profile = value ?? 'all';
if (!['all', 'base', 'choam', 'ecaz', 'combined', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster', 'moritani-skills', 'tleilaxu-skills', 'ix-skills', 'choam-skills'].includes(profile))
throw new Error('--profile must be all, base, choam, ecaz, combined, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster, moritani-skills, tleilaxu-skills, ix-skills or choam-skills.');
if (!['all', 'base', 'choam', 'ecaz', 'combined', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster', 'ix-roster', 'moritani-skills', 'tleilaxu-skills', 'ix-skills', 'choam-skills'].includes(profile))
throw new Error('--profile must be all, base, choam, ecaz, combined, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster, ix-roster, moritani-skills, tleilaxu-skills, ix-skills or choam-skills.');
return profile as Profile | 'all';
}
@@ -560,14 +561,14 @@ async function main() {
const profile = parseProfile(values.profile);
const rules = parseRules(values.rules);
const players = parsePlayers(values.players);
if (supplied('players') && !['base', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires --profile base, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster or a skill profile.');
if (supplied('players') && !['base', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster', 'ix-roster'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires --profile base, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster, ix-roster 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;
const requestedSamples = profile === 'homeworld' || profile === 'nexus' || profile === 'homeworld-nexus'
? MODULE_SCENARIOS
: profile === 'choam-roster' || profile === 'ecaz-roster' ? EXPANSION_ROSTER_SCENARIOS
: profile === 'choam-roster' || profile === 'ecaz-roster' || profile === 'ix-roster' ? EXPANSION_ROSTER_SCENARIOS
: profile === 'base' ? BASE_SCENARIOS
: profile === 'moritani-skills' ? MORITANI_SKILLS_SCENARIOS
: profile === 'tleilaxu-skills' ? TLEILAXU_SKILLS_SCENARIOS