Open expansion-faction starts from the normal lobby
This commit is contained in:
1 parent
5a32c1ac85
commit
5961c75a29
8 files changed
+36
-48
No files matched your search
+2
-5
@@ -1085,10 +1085,7 @@ export default function Home() {
|
||||
disabled={busy || claimUncertain}
|
||||
onChange={(value) => {
|
||||
setAdvanced(value);
|
||||
if (value) {
|
||||
setExpansions([]);
|
||||
if (f.expansion !== 'base') setSelected('atreides');
|
||||
}
|
||||
if (value && f.expansion !== 'base' && !expansions.includes(f.expansion)) setSelected('atreides');
|
||||
}}
|
||||
/>
|
||||
<p className="fine">2–6 players · up to 10 turns</p>
|
||||
@@ -1101,7 +1098,7 @@ export default function Home() {
|
||||
<label className="expansion" key={x.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={busy || claimUncertain || advanced}
|
||||
disabled={busy || claimUncertain}
|
||||
checked={expansions.includes(x.id)}
|
||||
onChange={(e) => {
|
||||
setExpansions(
|
||||
|
||||
@@ -2060,8 +2060,8 @@ export function GameTable({
|
||||
!g.players.every((p) => p.ready),
|
||||
)}
|
||||
<p className="development-note">
|
||||
Playable core under development. Advanced is available as an
|
||||
unfinished preview. Expansion games are not yet available.
|
||||
Playable core under development. Advanced and expansion factions
|
||||
are unfinished previews; some interactions may still need fixes.
|
||||
</p>
|
||||
</>
|
||||
) : g.status === 'setup' && !g.decision ? (
|
||||
|
||||
@@ -29,6 +29,18 @@ optional skill bands, combinations and public expansion starts remain open.
|
||||
Rules implementation continues; comprehensive assurance and administration
|
||||
remain deferred until all rules are implemented.
|
||||
|
||||
**Expansion-faction lobby starts open — 17 October (user-directed speed pass).**
|
||||
Hosts may now start Basic or Advanced-preview games with Ix, CHOAM and/or Ecaz
|
||||
factions from the normal lobby; the start routes through the existing native
|
||||
faction setup. Optional Tech Tokens follow the native profile; Stronghold Cards,
|
||||
Homeworlds, Nexus, Discovery, Skills and independent cards keep their dev entries.
|
||||
Eight all-AI 10-turn games (Basic/Advanced × Ix, CHOAM, Ecaz, all-three six-seat)
|
||||
finished with no stuck or rejected actions. The lobby note warns of unfinished
|
||||
interactions. **Known regression (fix later):** the full suite shows133/9868
|
||||
failures, mostly Nexus inspection/Cunning/Sardaukar and Bureaucrat suites; they
|
||||
began between `bc09ac3` (14 Oct, passing) and `82f1aae` (15 Oct) and predate
|
||||
this change. Not yet triaged.
|
||||
|
||||
**Independent36/all14 Skills composition — 17 October.** Fresh ready classic
|
||||
Basic/Advanced all14 now admits the printed independent three-card variant
|
||||
(original36, no other modules, no played-game conversion). Real Recruits after a
|
||||
|
||||
+15
-6
@@ -8404,9 +8404,12 @@ function baseStartProfileBlock(g: Game): string | null {
|
||||
if (g.ecazTreachery) return 'Ecaz Treachery Cards are still being implemented.';
|
||||
if (g.moritaniAssassinatePreview || g.moritaniAssassinate)
|
||||
return 'Separate development profiles cannot be enabled by a normal start.';
|
||||
if (!g.players.every(p => FACTIONS.some(entry => entry.id === p.faction && entry.expansion === 'base')))
|
||||
return 'Expansion factions are still being implemented.';
|
||||
if (g.expansions.length !== 0) return 'Expansion rules are still being implemented.';
|
||||
const expansionFactions = g.expansions.length > 0 ||
|
||||
!g.players.every(p => FACTIONS.some(entry => entry.id === p.faction && entry.expansion === 'base'));
|
||||
if (expansionFactions && g.strongholdCards)
|
||||
return 'Stronghold Cards with expansion factions still use a development profile.';
|
||||
if (expansionFactions && g.techTokens && !nativeFactionTechProfile(g))
|
||||
return 'Tech Tokens with expansion factions need a supported three-through-six-seat native roster.';
|
||||
return null;
|
||||
}
|
||||
function advancedPreviewIntegrity(g: Game) {
|
||||
@@ -8465,7 +8468,7 @@ function requireFreshBaseRuntime(g: Game) {
|
||||
JSON.stringify(g[key]) === JSON.stringify(fresh[key])),
|
||||
'Starting a base table cannot retain existing game resources, histories or continuations.');
|
||||
}
|
||||
function start(g: Game, advancedPreview = false) {
|
||||
function start(g: Game, advancedPreview = false): Game {
|
||||
requireRule(g.players.length >= 2, 'At least two players are needed.');
|
||||
requireRule(
|
||||
g.players.every((p) => p.ready),
|
||||
@@ -8476,6 +8479,12 @@ function start(g: Game, advancedPreview = false) {
|
||||
requireRule(!advancedPreview || g.advanced, 'Choose Advanced preview rules before starting that preview.');
|
||||
const blocked = baseStartProfileBlock(g);
|
||||
requireRule(!blocked, blocked ?? 'This rules profile is unavailable.');
|
||||
if (g.expansions.length) {
|
||||
// Expansion factions use the genuine native faction setup (elites, decks, cache).
|
||||
const initialized = initializeFactionExpansionsGameForAudit(g);
|
||||
if (advancedPreview) initialized.advancedPreview = true;
|
||||
return initialized;
|
||||
}
|
||||
requireFreshSetup(g);
|
||||
requireRule(!g.techTokens || JSON.stringify(g.techTokens) === JSON.stringify(createTechTokens()),
|
||||
'Start with unused Tech Tokens before assigning their initial owners.');
|
||||
@@ -8484,6 +8493,7 @@ function start(g: Game, advancedPreview = false) {
|
||||
requireFreshBaseRuntime(g);
|
||||
initializeSetup(g);
|
||||
if (advancedPreview) g.advancedPreview = true;
|
||||
return g;
|
||||
}
|
||||
function skillRule<T>(operation: () => T): T {
|
||||
try {
|
||||
@@ -32324,8 +32334,7 @@ function applyActionInner(
|
||||
requireRule(Object.keys(action).every(key => key === 'type' || key === 'advancedPreview') &&
|
||||
(action.advancedPreview === undefined || action.advancedPreview === true),
|
||||
'Start this table, or explicitly choose Advanced preview.');
|
||||
start(g, action.advancedPreview === true);
|
||||
return g;
|
||||
return start(g, action.advancedPreview === true);
|
||||
}
|
||||
if (t === 'faction') {
|
||||
const f = action.faction as FactionId;
|
||||
|
||||
@@ -532,7 +532,8 @@ export function classicIndependentLeaderSkillsProfile(game: LeaderSkillProfile):
|
||||
/** Shared by rule quotes, controls and minimal legal bot participation. */
|
||||
export function ordinaryLeaderSkillModeSupported(game: LeaderSkillProfile): boolean {
|
||||
return classicIndependentLeaderSkillsProfile(game) || (noOtherLeaderSkillModules(game) &&
|
||||
(!game.expansions.length || nativeExpansionLeaderSkillsProfile(game))) ||
|
||||
(!game.expansions.length || nativeExpansionLeaderSkillsProfile(game) ||
|
||||
(game.expansions.length === 1 && game.expansions[0] === 'choam'))) ||
|
||||
classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) ||
|
||||
strongholdLeaderSkillsProfile(game) || classicNexusLeaderSkillsProfile(game) ||
|
||||
guildBetrayalNexusLeaderSkillsProfile(game) ||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
newPlayer,
|
||||
joinGame,
|
||||
viewGame,
|
||||
initializeBaseGameForAudit,
|
||||
type Game,
|
||||
} from '../game/engine';
|
||||
import { baseDeck, createAuditorLeader, CHOAM_AUDITOR_ID } from '../game/cards';
|
||||
@@ -97,7 +96,7 @@ function setupModule() {
|
||||
return exports.initializeSetup!;
|
||||
}
|
||||
|
||||
void test('real isolated initializer adds the Advanced Auditor before prediction and traitor dealing; public gates stay closed', () => {
|
||||
void test('real isolated initializer adds the Advanced Auditor before prediction and traitor dealing', () => {
|
||||
const initialize = setupModule();
|
||||
for (const advanced of [false, true]) {
|
||||
let g = createGame(
|
||||
@@ -107,18 +106,6 @@ void test('real isolated initializer adds the Advanced Auditor before prediction
|
||||
);
|
||||
joinGame(g, newPlayer('b', 'Bene Gesserit', 'beneGesserit'));
|
||||
for (const id of ['c', 'b']) g = applyAction(g, id, { type: 'ready' });
|
||||
const before = structuredClone(g);
|
||||
assert.throws(
|
||||
() => applyAction(g, 'c', { type: 'start' }),
|
||||
advanced ? /Advanced preview/ : /still being implemented/,
|
||||
);
|
||||
if (advanced)
|
||||
assert.throws(
|
||||
() => applyAction(g, 'c', { type: 'start', advancedPreview: true }),
|
||||
/Expansion factions are still being implemented/,
|
||||
);
|
||||
assert.throws(() => initializeBaseGameForAudit(g), /base factions/);
|
||||
assert.deepEqual(g, before);
|
||||
initialize(g);
|
||||
assert.equal(g.setupStage, 'prediction');
|
||||
assert.equal(
|
||||
|
||||
@@ -299,18 +299,7 @@ for (const [advanced, seed] of [
|
||||
);
|
||||
}));
|
||||
|
||||
void test('the Ix prototype keeps public gates and rejects unsupported or nonempty lobbies without mutation', () => {
|
||||
const publicLobby = lobby(false, [
|
||||
['a', 'atreides'],
|
||||
['h', 'harkonnen'],
|
||||
]);
|
||||
const publicBefore = structuredClone(publicLobby);
|
||||
assert.throws(
|
||||
() => applyAction(publicLobby, publicLobby.host, { type: 'start' }),
|
||||
/Expansion rules are still being implemented/,
|
||||
);
|
||||
assert.deepEqual(publicLobby, publicBefore);
|
||||
|
||||
void test('the Ix prototype rejects unsupported or nonempty lobbies without mutation', () => {
|
||||
const absent = createGame('NOIXPROTO', newPlayer('a', 'a', 'atreides'));
|
||||
absent.players.push(newPlayer('h', 'h', 'harkonnen'));
|
||||
absent.players.forEach((p) => (p.ready = true));
|
||||
|
||||
@@ -272,7 +272,7 @@ void test('Atreides inspection grants only its entitled view and survives JSON b
|
||||
assert.equal(viewGame(g, 'e').richeseAuction!.cardId, null);
|
||||
assert.equal(g.players[0].hand[0].id, card.id);
|
||||
});
|
||||
void test('connected cache-cancel restores the ordinary pool while seller self-bid and lobby gates still reject atomically', () => {
|
||||
void test('connected cache-cancel restores the ordinary pool while seller self-bid rejects atomically', () => {
|
||||
const before = fixture();
|
||||
const k = before.deck.find((c) => c.effect === 'karama')!;
|
||||
before.players[2].hand = [k];
|
||||
@@ -295,13 +295,6 @@ void test('connected cache-cancel restores the ordinary pool while seller self-b
|
||||
const exact = structuredClone(g);
|
||||
assert.throws(() => bid(g, 'r', 1), /self-bid/);
|
||||
assert.deepEqual(g, exact);
|
||||
const lobby = createGame('RICHESE1', newPlayer('r', 'Richese', 'richese'));
|
||||
lobby.players.push(newPlayer('e', 'Emperor', 'emperor'));
|
||||
lobby.players.forEach((p) => (p.ready = true));
|
||||
assert.throws(
|
||||
() => applyAction(lobby, 'r', { type: 'start' }),
|
||||
/still being implemented/,
|
||||
);
|
||||
});
|
||||
|
||||
void test('an outbid ally-funded player whose hand fills is automatically skipped without reserving losing credit', () => {
|
||||
|
||||
Reference in new issue
Block a user