Exercise combined expansion Nexus games and saved continuation

This commit is contained in:
Quintine committed 2026-09-28 20:44:25 +10:00
1 parent c6784468a1
commit 068da48734
5 files changed
+81 -22

No files matched your search

+8 -1
View File
@@ -120,7 +120,7 @@ bulk actions and backup/operations tools remain unfinished.
[Participant AI](ADMIN_PARTICIPANT_AI.md) checkpoint `2cddf03` is pushed. Types/lint,
The latest [complete-game integration samples](IMPLEMENTATION_STATUS.md)
Earlier [complete-game integration samples](IMPLEMENTATION_STATUS.md)
finished 62 genuine games: Basic Leader Skills with Tleilaxu, Moritani,
Ixians or CHOAM; Basic/Advanced classic Homeworld + Nexus; all three paired
faction-expansion + Nexus profiles across two through six seats; and two
@@ -128,6 +128,13 @@ six-seat all-expansion games. All 29,333 actions were accepted, with 765
JSON/private-view restorations and unchanged source fingerprints. Rare effects
not encountered naturally and all public release gates remain unverified.
Four further [all-expansion Nexus samples](IMPLEMENTATION_STATUS.md)
completed genuine five/six-seat Basic and Advanced games: 4,098 accepted
actions, 109 JSON/private-view restorations, unchanged source fingerprints
and five Nexus Card draw/keep decisions in the five-seat games. No Nexus
effect was played in those samples; card-specific combinations and public
expansion/Nexus starts remain gated.
The bounded [native Richese Nexus Cunning shipment](NEXUS_RICHESE_RULES.md#bounded-native-cunning-prototype-27-september-2026) now uses two distinct physical No-Field tokens in one priced shipment in genuine Richese/CHOAM paired Nexus games. One token immediately reveals a reserve-capped public force group; the other remains concealed. Owner-only controls, four legal AI profiles, Karama/Guild responses, signed saved receipts and concurrent in-memory SQLite settlement are connected in Basic/Advanced. A read-only local browser fixture displayed the owner selector and no rival private panel. Same-shipment nonrepeat is a documented product interpretation; faceup-component artwork, combined modules, Betrayal and full Nexus acceptance remain gated. This code is local and not in the last verified production image.
The independent [Reinforcements Battle Plan prototype](REINFORCEMENTS_RUNTIME.md)
+16
View File
@@ -1,5 +1,21 @@
# Dune implementation status
## 28 September 2026 — five/six-seat all-expansion Nexus integration samples
The offline-only `combined-nexus` audit setup selects all three physical
expansions and Nexus Cards for five seats (Ixians, Tleilaxu, CHOAM, Ecaz,
Moritani) or six (plus Richese). Basic and Advanced samples for each roster
completed with 4,098 accepted actions, zero rejected bot actions and 109
JSON/private-view restores. The five-seat tables reached five real Nexus Card
draw/keep decisions; the six-seat tables formed alliances but did not draw
cards. The report at `/tmp/dune-all-expansion-nexus-matrix-final/report.json`
records `status: passed` and unchanged source. A separate intentionally
capped ten-action five-seat save resumed to a turn-nine win with 1,380 accepted
actions, 37 more restores and zero rejected actions. The resumed random stream
restarts, so its outcome need not match an uninterrupted game. No card effect
happened in these samples, so they do not establish individual card correctness,
complete cross-expansion behavior or permission to open public starts.
## 28 September 2026 — owner-only operational counts
`/admin/operations` now samples the build revision and live database aggregates:
+15 -9
View File
@@ -114,6 +114,7 @@ node --import tsx tools/faction-games.ts --out /tmp/dune-new-ix-roster --profile
node --import tsx tools/faction-games.ts --out /tmp/dune-new-choam-nexus --profile choam-nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-ecaz-nexus --profile ecaz-nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-ix-nexus --profile ix-nexus
node --import tsx tools/faction-games.ts --out /tmp/dune-new-combined-nexus --profile combined-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-resume --resume /private/failed-combined-advanced.json
```
@@ -122,8 +123,9 @@ 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`, `ecaz-roster`, `ix-roster`, `choam-nexus`,
`ecaz-nexus` or `ix-nexus` profile; omission or `all` retains all five counts. The fixed
`homeworld-nexus`, expansion-roster or paired-Nexus profiles. The
`combined-nexus` profile supports `--players 5` or `6` only. Omission or `all`
retains every roster in a selected profile. The fixed
base roster adds Atreides, Harkonnen, Fremen, Emperor, Guild and Bene Gesserit
in that order. These are sample rosters, not the only rules-permitted
player-count configurations. Each optional module profile runs two through
@@ -134,13 +136,17 @@ Richese, Ecaz/Moritani with 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.
`choam-nexus`, `ecaz-nexus` and `ix-nexus` reuse the respective expansion
rosters with Nexus Cards enabled at seed offsets 98–107, 108–117 and 118–127,
respectively. The original six default samples are unchanged.
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
offsets 58–67. These samples do not open public module or expansion starts or
assert complete interaction coverage.
rosters with Nexus Cards enabled at seed offsets 98–107, 108–117 and 118–127.
`combined-nexus` selects five or six actual expansion factions and all three
expansion decks with Nexus Cards in Basic and Advanced. Five seats leave Richese
absent so a player can remain unallied and draw; six seats contain every expansion
faction. Their seed offsets are 130–131 and 128–129 respectively. The original
six default samples are unchanged. 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 offsets 58–67. These samples
do not open public module or expansion starts or assert complete interaction
coverage.
All-AI Nexus samples now make public reciprocal offers as well as accepting
them, so an actual alliance can form without a human proposer. At the
+16
View File
@@ -6716,6 +6716,22 @@ export function initializePairedNexusGameForAudit(state: Game): Game {
return initializeSetupGameForAudit(state, false, true, false, false, false, false, true);
}
/** Offline-only all-expansion Nexus composition; not a public start gate. */
export function initializeCombinedNexusGameForAudit(state: Game): Game {
requireRule(state.expansions.length === 3 &&
['ix', 'choam', 'ecaz'].every(expansion => state.expansions.includes(expansion)) &&
[5, 6].includes(state.players.length) &&
state.players.every(player => ['ixians', 'tleilaxu', 'choam', 'richese', 'ecaz', 'moritani'].includes(player.faction)) &&
['ixians', 'tleilaxu', 'choam', 'ecaz', 'moritani']
.every(faction => state.players.some(player => player.faction === faction)),
'The combined Nexus sample requires five or six expansion factions and their three decks.');
requireRule(!!state.nexusCards && state.nexusCards.cards === null && state.nexusCards.phase === null &&
!state.homeworlds && !state.leaderSkills && !state.discoveryEnabled &&
!state.ecazTreachery && !state.techTokens && !state.strongholdCards,
'Enable only Nexus Cards in a fresh combined expansion lobby.');
return initializeSetupGameForAudit(state, false, true, false, false, false, false, true);
}
/** Offline prototype entry through real Ix setup, including both expansion factions.
* No player action or room API bypasses the normal release gates. */
export function initializeIxGameForAudit(state: Game): Game {
+26 -12
View File
@@ -14,6 +14,7 @@ import {
initializeHomeworldGameForAudit,
initializeNexusGameForAudit,
initializePairedNexusGameForAudit,
initializeCombinedNexusGameForAudit,
initializeLeaderSkillsGameForAudit,
joinGame,
newPlayer,
@@ -29,7 +30,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' | 'ix-roster' | 'choam-nexus' | 'ecaz-nexus' | 'ix-nexus' | 'moritani-skills' | 'tleilaxu-skills' | 'ix-skills' | 'choam-skills';
type Profile = 'base' | 'choam' | 'ecaz' | 'combined' | 'combined-nexus' | 'homeworld' | 'nexus' | 'homeworld-nexus' | 'choam-roster' | 'ecaz-roster' | 'ix-roster' | 'choam-nexus' | 'ecaz-nexus' | 'ix-nexus' | 'moritani-skills' | 'tleilaxu-skills' | 'ix-skills' | 'choam-skills';
type Rules = 'basic' | 'advanced';
type Scenario = {
@@ -150,7 +151,15 @@ const PAIRED_NEXUS_SCENARIOS: readonly Scenario[] = EXPANSION_ROSTER_SCENARIOS
profile: scenario.profile.replace('-roster', '-nexus') as Profile,
ordinal: scenario.ordinal + 30,
}));
const COMBINED_NEXUS_SCENARIOS: readonly Scenario[] = [
...SCENARIOS.filter(scenario => scenario.profile === 'combined')
.map(scenario => ({ ...scenario, profile: 'combined-nexus' as const, ordinal: scenario.ordinal + 124 })),
...(['basic', 'advanced'] as const).map((rules, index) => ({
ordinal: 130 + index, profile: 'combined-nexus' as const, rules,
expansions: ['ix', 'choam', 'ecaz'],
roster: ['ecaz', 'ixians', 'tleilaxu', 'choam', 'moritani'] as FactionId[],
})),
];
const MORITANI_SKILLS_ROSTER: readonly FactionId[] = [
'moritani', 'emperor', 'guild', 'harkonnen', 'fremen', 'beneGesserit',
@@ -222,10 +231,10 @@ 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|ix-roster|choam-nexus|ecaz-nexus|ix-nexus|moritani-skills|tleilaxu-skills|ix-skills|choam-skills] ' +
'[--seed UINT32] [--profile all|base|choam|ecaz|combined|combined-nexus|homeworld|nexus|homeworld-nexus|choam-roster|ecaz-roster|ix-roster|choam-nexus|ecaz-nexus|ix-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, Homeworld, Nexus, combined Homeworld-Nexus, expansion roster, paired expansion 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.'
'Runs genuine setup and gameplay offline. Default/all keeps the six expansion samples; combined-nexus adds five/six-seat Basic/Advanced all-expansion Nexus samples. Base, Homeworld, Nexus, Homeworld-Nexus, expansion roster, paired expansion Nexus and skill profiles default to their 2–6-player samples. --players requires a supported profile. Output must be a new private directory outside the checkout.'
);
}
@@ -256,7 +265,7 @@ function positive(value: string | undefined) {
}
function scenarioName(scenario: Scenario) {
return scenario.profile === 'base' || scenario.profile === 'homeworld' || scenario.profile === 'nexus' || scenario.profile === 'homeworld-nexus' ||
return scenario.profile === 'base' || scenario.profile === 'homeworld' || scenario.profile === 'nexus' || scenario.profile === 'homeworld-nexus' || scenario.profile === 'combined-nexus' ||
scenario.profile === 'choam-roster' || scenario.profile === 'ecaz-roster' || scenario.profile === 'ix-roster' || scenario.profile === 'choam-nexus' || scenario.profile === 'ecaz-nexus' || scenario.profile === 'ix-nexus' || skillProfile(scenario.profile)
? `${scenario.profile}-${scenario.roster.length}-${scenario.rules}`
: `${scenario.profile}-${scenario.rules}`;
@@ -264,8 +273,8 @@ function scenarioName(scenario: Scenario) {
function parseProfile(value: string | undefined) {
const profile = value ?? 'all';
if (!['all', 'base', 'choam', 'ecaz', 'combined', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster', 'ix-roster', 'choam-nexus', 'ecaz-nexus', 'ix-nexus', '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, choam-nexus, ecaz-nexus, ix-nexus, moritani-skills, tleilaxu-skills, ix-skills or choam-skills.');
if (!['all', 'base', 'choam', 'ecaz', 'combined', 'combined-nexus', 'homeworld', 'nexus', 'homeworld-nexus', 'choam-roster', 'ecaz-roster', 'ix-roster', 'choam-nexus', 'ecaz-nexus', 'ix-nexus', 'moritani-skills', 'tleilaxu-skills', 'ix-skills', 'choam-skills'].includes(profile))
throw new Error('--profile must be all, base, choam, ecaz, combined, combined-nexus, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster, ix-roster, choam-nexus, ecaz-nexus, ix-nexus, moritani-skills, tleilaxu-skills, ix-skills or choam-skills.');
return profile as Profile | 'all';
}
@@ -311,8 +320,10 @@ function freshGame(scenario: Scenario) {
}
if (scenario.profile === 'homeworld' || scenario.profile === 'homeworld-nexus')
game.homeworlds = { custody: null };
if (scenario.profile === 'nexus' || scenario.profile === 'homeworld-nexus' || scenario.profile === 'choam-nexus' || scenario.profile === 'ecaz-nexus' || scenario.profile === 'ix-nexus')
if (scenario.profile === 'nexus' || scenario.profile === 'homeworld-nexus' || scenario.profile === 'choam-nexus' || scenario.profile === 'ecaz-nexus' || scenario.profile === 'ix-nexus' || scenario.profile === 'combined-nexus')
game.nexusCards = { cards: null, phase: null };
if (scenario.profile === 'combined-nexus')
return initializeCombinedNexusGameForAudit(game);
if (scenario.profile === 'choam-nexus' || scenario.profile === 'ecaz-nexus' || scenario.profile === 'ix-nexus')
return initializePairedNexusGameForAudit(game);
return skillProfile(scenario.profile)
@@ -373,10 +384,10 @@ function resumedGame(path: string) {
game.moritaniAssassinateCallEvents
)
throw new Error('--resume sample scenarios exclude unsupported optional modules.');
const scenario = [...SCENARIOS, ...BASE_SCENARIOS, ...MODULE_SCENARIOS, ...EXPANSION_ROSTER_SCENARIOS, ...PAIRED_NEXUS_SCENARIOS, ...MORITANI_SKILLS_SCENARIOS, ...TLEILAXU_SKILLS_SCENARIOS, ...IX_SKILLS_SCENARIOS, ...CHOAM_SKILLS_SCENARIOS].find(
const scenario = [...SCENARIOS, ...COMBINED_NEXUS_SCENARIOS, ...BASE_SCENARIOS, ...MODULE_SCENARIOS, ...EXPANSION_ROSTER_SCENARIOS, ...PAIRED_NEXUS_SCENARIOS, ...MORITANI_SKILLS_SCENARIOS, ...TLEILAXU_SKILLS_SCENARIOS, ...IX_SKILLS_SCENARIOS, ...CHOAM_SKILLS_SCENARIOS].find(
(candidate) =>
(candidate.profile === 'homeworld' || candidate.profile === 'homeworld-nexus') === !!game.homeworlds &&
(candidate.profile === 'nexus' || candidate.profile === 'homeworld-nexus' || candidate.profile === 'choam-nexus' || candidate.profile === 'ecaz-nexus' || candidate.profile === 'ix-nexus') === !!game.nexusCards &&
(candidate.profile === 'nexus' || candidate.profile === 'homeworld-nexus' || candidate.profile === 'choam-nexus' || candidate.profile === 'ecaz-nexus' || candidate.profile === 'ix-nexus' || candidate.profile === 'combined-nexus') === !!game.nexusCards &&
skillProfile(candidate.profile) === !!game.leaderSkills &&
candidate.rules === (game.advanced ? 'advanced' : 'basic') &&
JSON.stringify(candidate.expansions) ===
@@ -570,8 +581,10 @@ 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', 'ix-roster', 'choam-nexus', 'ecaz-nexus', 'ix-nexus'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires --profile base, homeworld, nexus, homeworld-nexus, choam-roster, ecaz-roster, ix-roster, choam-nexus, ecaz-nexus, ix-nexus or a skill profile.');
if (supplied('players') && !['base', 'homeworld', 'nexus', 'homeworld-nexus', 'combined-nexus', 'choam-roster', 'ecaz-roster', 'ix-roster', 'choam-nexus', 'ecaz-nexus', 'ix-nexus'].includes(profile) && !skillProfile(profile))
throw new Error('--players requires a roster or optional-module profile.');
if (profile === 'combined-nexus' && players !== 'all' && ![5, 6].includes(players))
throw new Error('--profile combined-nexus supports only five or six players.');
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;
@@ -579,6 +592,7 @@ async function main() {
? MODULE_SCENARIOS
: profile === 'choam-roster' || profile === 'ecaz-roster' || profile === 'ix-roster' ? EXPANSION_ROSTER_SCENARIOS
: profile === 'choam-nexus' || profile === 'ecaz-nexus' || profile === 'ix-nexus' ? PAIRED_NEXUS_SCENARIOS
: profile === 'combined-nexus' ? COMBINED_NEXUS_SCENARIOS
: profile === 'base' ? BASE_SCENARIOS
: profile === 'moritani-skills' ? MORITANI_SKILLS_SCENARIOS
: profile === 'tleilaxu-skills' ? TLEILAXU_SKILLS_SCENARIOS