Add Basic group alliance preview to lobby
Verify and publish checkpoint container / container (push) Successful in 10m49s
Verify and publish checkpoint container / container (push) Successful in 10m49s
This commit is contained in:
1 parent
44e7cf8001
commit
89386af95b
7 files changed
+273
-10
No files matched your search
+30
-6
@@ -752,7 +752,7 @@ import {
|
||||
} from './advisors';
|
||||
import { isAlliedShipmentVisit, makeBasicAlliedShipment, quoteBasicAlliedShipment, basicAlliedShipmentHasWitness,
|
||||
type BasicAlliedShipment } from './allied-separation';
|
||||
import { answerCoalitionAdmission, beginCoalitionAdmission, coalitionAllies, coalitionGroups, coalitionOf,
|
||||
import { answerCoalitionAdmission, beginCoalitionAdmission, coalitionAllies, coalitionGroups, coalitionOf, emptyCoalitionState,
|
||||
leaveCoalition, CoalitionAllianceError, type CoalitionState, type CoalitionTransition } from './coalition-alliance';
|
||||
import { coalitionBattleBeneficiary } from './coalition-battle-preparation';
|
||||
import { createCoalitionWormProtection, currentCoalitionWormAlly, decideCoalitionWormProtection,
|
||||
@@ -1448,7 +1448,9 @@ type RicheseAllyOffer = {
|
||||
payer: string;
|
||||
};
|
||||
export type Game = {
|
||||
/** Unexposed group-alliance integration state; absent in existing games. */
|
||||
/** Explicit incomplete Basic group preview; absent in legacy games. */
|
||||
coalitionPreview?: 'three' | 'three-short' | 'unlimited';
|
||||
/** Group-alliance integration state; absent in ordinary existing games. */
|
||||
coalitionState?: CoalitionState;
|
||||
coalitionEscrow?: CoalitionEscrowState;
|
||||
coalitionWormProtection?: CoalitionWormProtectionFrame;
|
||||
@@ -5804,6 +5806,8 @@ function twoPlayerRule<T>(operation: () => T): T {
|
||||
function twoPlayerControllersIntegrity(g: Game): void {
|
||||
requireRule(g.shortAllianceVictory === undefined || typeof g.shortAllianceVictory === 'boolean',
|
||||
'The optional alliance victory setting must be true or false.');
|
||||
requireRule(g.coalitionPreview === undefined || ['three', 'three-short', 'unlimited'].includes(g.coalitionPreview),
|
||||
'Invalid group-alliance preview setting.');
|
||||
if (g.twoPlayerControllers === undefined) return;
|
||||
const seats = g.players.map(player => player.id);
|
||||
twoPlayerRule(() => {
|
||||
@@ -10881,6 +10885,12 @@ function startModules(state: Game): Game {
|
||||
}
|
||||
/** Public consistency only; each module's genuine initializer validates its own envelope. */
|
||||
function baseStartProfileBlock(g: Game): string | null {
|
||||
if (g.coalitionPreview && g.status === 'lobby' &&
|
||||
(g.advanced || g.expansions.length > 0 || g.players.length < 3 ||
|
||||
!g.players.every(p => CLASSIC_FACTIONS[p.faction] === true) || g.twoPlayerControllers ||
|
||||
g.shortAllianceVictory || g.techTokens || g.strongholdCards || g.homeworlds ||
|
||||
g.nexusCards || g.discoveryEnabled || g.leaderSkills || g.ecazTreachery || lobbyModulesSelected(g)))
|
||||
return 'The group-alliance preview needs at least three classic factions, Basic rules, and no expansion decks, other optional modules, two-person setup or separate short-alliance setting.';
|
||||
if (g.status === 'lobby' && (g.moritaniAssassinatePreview || g.moritaniAssassinate))
|
||||
return 'Separate development profiles cannot be enabled by a normal start.';
|
||||
if (g.status === 'lobby' && (g.leaderSkills || g.ecazTreachery))
|
||||
@@ -10945,7 +10955,7 @@ function requireFreshBaseRuntime(g: Game) {
|
||||
const configuration = new Set<keyof Game>(['code', 'version', 'host', 'players', 'playerPositions',
|
||||
'advanced', 'expansions', 'log', 'botsPending', 'botNextActionAt', 'techTokens', 'strongholdCards',
|
||||
'homeworlds', 'nexusCards', 'discoveryEnabled', 'leaderSkills', 'ecazTreachery', 'lobbyOptions', 'kullDefaultPolicy',
|
||||
'twoPlayerControllers', 'shortAllianceVictory']);
|
||||
'twoPlayerControllers', 'shortAllianceVictory', 'coalitionPreview']);
|
||||
const keys = [...new Set([...Object.keys(g), ...Object.keys(fresh)])] as (keyof Game)[];
|
||||
requireRule(keys.every(key => configuration.has(key) ||
|
||||
JSON.stringify(g[key]) === JSON.stringify(fresh[key])),
|
||||
@@ -10982,6 +10992,8 @@ function start(g: Game, advancedPreview = false): Game {
|
||||
'Start with unused Stronghold Cards before assigning ownership.');
|
||||
requireFreshBaseRuntime(g);
|
||||
initializeSetup(g);
|
||||
if (g.coalitionPreview) g.coalitionState = emptyCoalitionState(
|
||||
g.coalitionPreview === 'three' ? undefined : g.coalitionPreview);
|
||||
if (advancedPreview) g.advancedPreview = true;
|
||||
return g;
|
||||
}
|
||||
@@ -16278,9 +16290,8 @@ function allianceQuestionWindow(g: Game): boolean {
|
||||
function unsettledPromises(records: readonly {fulfilled?: boolean; released?: boolean}[] | undefined): boolean {
|
||||
return !!records?.some(record => !record.fulfilled && !record.released);
|
||||
}
|
||||
/** Group negotiation is deliberately unexposed until its other alliance
|
||||
* mechanics are integrated. This narrow profile keeps existing pair games
|
||||
* and unrelated commitments on their original paths. */
|
||||
/** The incomplete group preview and staged fixtures retain this narrow profile;
|
||||
* existing pair games and unrelated commitments keep their original paths. */
|
||||
function coalitionNexusAvailable(g: Game): boolean {
|
||||
return g.coalitionState !== undefined && g.status === 'playing' &&
|
||||
g.phase === 1 && !!g.nexus && !g.spiceWindow && !g.spiceResolution &&
|
||||
@@ -44695,6 +44706,18 @@ function applyActionInner(
|
||||
log(g, `${p.name} ${action.enabled ? 'enabled' : 'disabled'} the provisional two-person, four-faction setup. Human readiness was cleared.`);
|
||||
return g;
|
||||
}
|
||||
if (t === 'coalitionPreview') {
|
||||
requireRule(id === g.host && Object.keys(action).every(key => key === 'type' || key === 'variant') &&
|
||||
(action.variant === 'off' || action.variant === 'three' || action.variant === 'three-short' || action.variant === 'unlimited'),
|
||||
'Only the host may choose a group-alliance preview option.');
|
||||
const variant = action.variant === 'off' ? undefined : action.variant;
|
||||
if (variant === g.coalitionPreview) return g;
|
||||
if (variant) g.coalitionPreview = variant;
|
||||
else delete g.coalitionPreview;
|
||||
g.players.forEach(player => (player.ready = !!player.bot));
|
||||
log(g, `${p.name} changed the unfinished group-alliance preview. Human readiness was cleared.`);
|
||||
return g;
|
||||
}
|
||||
if (t === 'shortAllianceVictory') {
|
||||
requireRule(id === g.host && typeof action.enabled === 'boolean' &&
|
||||
Object.keys(action).every(key => key === 'type' || key === 'enabled'),
|
||||
@@ -47056,6 +47079,7 @@ export function viewGame(state: Game, id: string, now?: number) {
|
||||
factionSwap: g.factionSwap ?? null,
|
||||
twoPlayerControllers: g.twoPlayerControllers ?? null,
|
||||
shortAllianceVictory: g.shortAllianceVictory === true,
|
||||
coalitionPreview: g.coalitionPreview ?? 'off',
|
||||
controlledFactionSeats: g.twoPlayerControllers
|
||||
? g.players.filter(player => g.twoPlayerControllers!.controllers.some(controller =>
|
||||
controller.seats.includes(id) && controller.seats.includes(player.id)))
|
||||
|
||||
+3
-2
@@ -2697,11 +2697,12 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The table chronicle records proposals, formation, withdrawals and breaks. Changing an offer clears Nexus readiness so the table can review the updated negotiations.',
|
||||
'Refresh restores saved offers and alliances. If acceptance and withdrawal arrive together, only one action is committed; refresh or reconcile a changed table before choosing again. Pending responses and decisions must finish first.',
|
||||
'Allied forces, cards and spice keep their owners. Alliance funding and individual faction powers use their own controls; special alliances and expansion restrictions remain separate.',
|
||||
'The host may select an unfinished Basic classic group-alliance preview for three to six players. The group panel supports unanimous admission and leaving; powers and interactions remain incomplete. Expansion decks, other modules and the separate short-alliance setting cannot be combined with this preview.',
|
||||
],
|
||||
related: ['spice-blow', 'alliance-funding', 'table-discussion', 'privacy'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Existing ordinary reciprocal offers, withdrawal and break preserve readiness and configured alliance restrictions. Staged Basic and bare-classic Advanced group Nexus actions add unanimous admission and leave; the option is not yet available in the lobby and broader group mechanics remain unfinished.', evidence: ['game/nexus-alliance.ts', 'game/coalition-alliance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Named outgoing offers have live withdrawal. The group panel is connected in staged games only; no lobby enables group play yet.', evidence: ['components/game-table.tsx', 'components/coalition-nexus.tsx'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Ordinary reciprocal offers, withdrawal and break preserve readiness and configured alliance restrictions. The Basic classic group preview adds unanimous admission and leave; broader group mechanics remain unfinished.', evidence: ['game/nexus-alliance.ts', 'game/coalition-alliance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Named outgoing offers have live withdrawal. The host can select an explicitly incomplete Basic group preview; group controls appear during a supported Nexus.', evidence: ['components/game-table.tsx', 'components/coalition-nexus.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four existing profiles can continue legally after withdrawal and answer staged group invitations. Full diplomacy and difficulty tuning remain deferred.' },
|
||||
{ area: 'Documentation', status: 'Implemented', detail: 'Offer versus alliance, incoming invitations, readiness, refresh and concurrent choices are explained.', evidence: ['docs/NEXUS_OFFERS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Full-table rendering, JSON continuation, pending-window rejection and SQLite acceptance/withdrawal races are covered. Staged group cases exercise actual proposals, answers, leaving, table rendering and legal bot responses. All-configuration and full visual acceptance remain open.', evidence: ['tests/coalition-nexus-engine.test.ts', 'tests/nexus-offer-controls.test.tsx', 'tests/nexus-offer-recovery.test.ts', 'tests/nexus-alliance.test.ts'] },
|
||||
|
||||
Reference in new issue
Block a user