From 2183376edc3ec487c7824887e9ca34ee9ac8b55d Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:19:59 +1000 Subject: [PATCH] Sample Ixian and Tleilaxu expansion rosters --- docs/IMPLEMENTATION_STATUS.md | 4 ++++ docs/VERIFICATION_WORKFLOW.md | 18 ++++++++++-------- tests/faction-games-cli.test.ts | 19 ++++++++++++++++++- tools/faction-games.ts | 17 +++++++++-------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 307b2eee..974f9f06 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -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. diff --git a/docs/VERIFICATION_WORKFLOW.md b/docs/VERIFICATION_WORKFLOW.md index f4880e92..3d88e936 100644 --- a/docs/VERIFICATION_WORKFLOW.md +++ b/docs/VERIFICATION_WORKFLOW.md @@ -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 diff --git a/tests/faction-games-cli.test.ts b/tests/faction-games-cli.test.ts index 3624c72d..9a2b95c2 100644 --- a/tests/faction-games-cli.test.ts +++ b/tests/faction-games-cli.test.ts @@ -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; + rejected: Record; + 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) { diff --git a/tools/faction-games.ts b/tools/faction-games.ts index 272d2ce4..cb11f2d0 100644 --- a/tools/faction-games.ts +++ b/tools/faction-games.ts @@ -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