Connect fresh Basic Semuta and Ambassador discards

Initialize the existing neutral Semuta protocol during genuine fresh public bare Basic Richese setup with selected native families. Retain original roster/deck/freshness checks, separate optional/Advanced profiles and unchanged existing saves.

Retain the original Ambassador arrival cause and admit clean ordinary shipment/movement CHOAM and Ixian private discard batches, including native BG copies. Keep CHOAM credit committed once; pause before exactly one Ix replacement. Existing owner-only commitment/selection, physical Semuta/full-hand exchange and original entrant continuation remain.

Verify original public setup, actual one-spice Semuta auction, ordinary purchase, paid placement, paid shipment and actual movement. Exercise claim/pass controls, busy protection and all four existing legal profiles without strategic tuning. Repair nearby stale tests to acknowledge actual battle outcomes and retain the existing independent-card/Discovery combination. Full modes, wider nested/optional interactions, comprehensive assurance and deployed acceptance remain open. No saved-room/reset/restart/deployment operation.
This commit is contained in:
Quintine committed 2026-10-24 14:08:45 +11:00
1 parent 1f3e71fe03
commit b4a556d5d3
12 files changed
+673 -34

No files matched your search

+12 -4
View File
@@ -6,6 +6,7 @@ import {
} from '../game/engine';
import { FACTIONS, type FactionId } from '../game/catalog';
import { baseDeck } from '../game/cards';
import { ecazTreacheryCards } from '../game/ecaz-cards';
import { botActions } from '../game/bots';
import { DIFFICULTIES, type Difficulty } from '../game/bot-profiles';
import { createLeaderSkills } from '../game/leader-skills';
@@ -54,7 +55,7 @@ function completedSetup(initial: Game, difficulty: Difficulty = 'Easy') {
assert.equal(game.setupStage, undefined);
assert.deepEqual(normalizeAutomaticGame(reload(game)), game);
const cards = [...game.deck, ...game.discard, ...game.players.flatMap(p => p.hand)].map(card => card.id).sort();
assert.deepEqual(cards, baseDeck().map(card => card.id).sort());
assert.deepEqual(cards, [...baseDeck(), ...(initial.ecazTreachery ? ecazTreacheryCards() : [])].map(card => card.id).sort());
const traitors = [...game.traitorReserve!, ...game.players.flatMap(p => [...p.traitors, ...p.traitorChoices])];
assert.equal(new Set(traitors).size, traitors.length);
assert.deepEqual(traitors.sort(), game.players.flatMap(p => p.leaders.map(leader => leader.id)).sort());
@@ -121,13 +122,20 @@ void test('only the host can select a boolean rules mode, changes clear human re
completedSetup(act(ready(game), game.host, { type: 'start' }));
});
void test('unsupported independent cards and Discovery reject the whole start without silently dropping a module', () => {
void test('supported independent cards and Discovery preserve both selected modules through public Basic setup', () => {
let game = act(lobby(['atreides', 'emperor']), 'atreides',
{ type: 'moduleOption', module: 'ecazTreachery', enabled: true });
game = act(game, game.host, { type: 'moduleOption', module: 'discoveries', enabled: true });
game = ready(game);
reject(game, game.host, { type: 'start' });
assert.ok(game.players.every(p => !p.hand.length && !p.traitors.length));
const before = structuredClone(game), started = act(game, game.host, { type: 'start' });
assert.deepEqual(game, before); assert.equal(started.status, 'setup');
assert.equal(started.advanced, false); assert.equal(started.ecazTreachery, true);
assert.equal(started.discoveryEnabled, true); assert.ok(started.discoveries);
assert.ok(started.spiceDeck.some(card => 'discovery' in card && card.discovery));
const cards = [...started.deck, ...started.players.flatMap(p => p.hand)];
for (const card of ecazTreacheryCards()) assert.ok(cards.some(held => held.id === card.id));
assert.equal(started.semutaPreview, undefined);
completedSetup(started);
});
void test('Advanced starts require the exact host opt-in and cannot dispatch audit initializers or hidden bypass options', () => {