Add three-member short alliance prototype
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 02:31:50 +11:00
1 parent cdc47ed2e1
commit ef92ddf2b9
12 files changed
+343 -12

No files matched your search

+3 -3
View File
@@ -4,9 +4,9 @@ import {
type TwoPlayerControllers,
} from './two-player-controllers';
/** GF9's optional two-member alliance / three-stronghold variant. The
* two-person four-stronghold rule still applies, as do ordinary solo targets.
* Callers add any separately agreed increase after this base target. */
/** GF9's shorter alliance target, also used by the staged three-member and
* unlimited variants. The two-person four-stronghold rule still applies, as
* do ordinary solo targets. Callers add any agreed increase afterward. */
export function allianceVictoryTarget(
seats: readonly string[],
controllers: TwoPlayerControllers | null | undefined,
+4 -2
View File
@@ -3228,9 +3228,11 @@ function policyActions(g: GameView): Action[] {
const card = me.hand?.find((c) => c.effect === 'harvester');
if (card) return [{ type: 'card', card: card.id }];
}
if (g.nexus && !g.spiceWindow && !g.ready.includes(me.id) && group?.variant === 'unlimited' &&
if (g.nexus && !g.spiceWindow && !g.ready.includes(me.id) &&
(group?.variant === 'unlimited' || group?.variant === 'three-short') &&
!group.groups.some(alliance => alliance.members.includes(me.id))) {
const alliance = group.groups.find(candidate => candidate.members.length >= 2 && candidate.members.length < 6 &&
const limit = group.variant === 'unlimited' ? 6 : 3;
const alliance = group.groups.find(candidate => candidate.members.length >= 2 && candidate.members.length < limit &&
!group.blockedInvitations?.some(blocked => blocked.groupId === candidate.id && blocked.entrant === me.id));
if (alliance) return [{type:'coalitionPropose', groupId:alliance.id, entrant:me.id,
expectedRevision:group.revision}];
+6 -4
View File
@@ -28,8 +28,9 @@ export type CoalitionAdmission = {
};
export type CoalitionState = {
version: 1;
/** Absent preserves the existing three-member maximum. */
variant?: 'unlimited';
/** Absent preserves the existing three-member limit and target selection.
* The distinct short variant retains that cap and uses the short target. */
variant?: 'three-short' | 'unlimited';
revision: number;
groups: CoalitionGroup[];
proposal: CoalitionAdmission | null;
@@ -64,7 +65,7 @@ const same = (left: unknown, right: unknown) => JSON.stringify(left) === JSON.st
const copy = <T>(value: T): T => structuredClone(value);
const legacyId = (members: readonly string[]) => `legacy:${JSON.stringify(sorted(members))}`;
export function emptyCoalitionState(variant?: 'unlimited'): CoalitionState {
export function emptyCoalitionState(variant?: CoalitionState['variant']): CoalitionState {
return { version: 1, ...(variant === undefined ? {} : {variant}), revision: 0, groups: [], proposal: null, usedEvents: [] };
}
function requireSeats(seats: readonly CoalitionSeat[]): void {
@@ -95,7 +96,8 @@ function requireGroup(group: CoalitionGroup, seats: readonly CoalitionSeat[], ma
check(same(actual, expected), 'A coalition must retain exactly one ordered edge for each pair.');
}
function requireState(state: CoalitionState): void {
check(record(state) && state.version === 1 && (state.variant === undefined || state.variant === 'unlimited') &&
check(record(state) && state.version === 1 &&
(state.variant === undefined || state.variant === 'three-short' || state.variant === 'unlimited') &&
Number.isSafeInteger(state.revision) && state.revision >= 0 &&
Array.isArray(state.groups) && Array.isArray(state.usedEvents) && state.usedEvents.every(id) &&
new Set(state.usedEvents).size === state.usedEvents.length &&
+1 -1
View File
@@ -4504,7 +4504,7 @@ export const RULE_TOPICS: RuleTopic[] = [
example:
'At the final turn with neither Guild nor Fremen, two players each hold two qualifying strongholds and a third holds one. The two leaders of the count share the win.',
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'A host-selectable shorter two-member target shares victory calculation. Unexposed staged Basic groups connect Nexus, selected funding including purchased Karama shipment prices, Guild transport, Emperor and Fremen aid, Fremen worm protection, Voice, Prescience and Harkonnen traitor support. An explicit staged unlimited variant extends membership to six and uses the three-stronghold target; other group interactions remain open.', evidence: ['game/alliance-victory-option.ts', 'game/victory-progress.ts', 'game/victory-quote.ts', 'game/engine.ts', 'game/coalition-worm-protection.ts', 'game/coalition-battle-preparation.ts', 'game/traitor-declarations.ts', 'game/terminal-cancellation.ts'] },
{ area: 'Implementation', status: 'Partial', detail: 'A host-selectable shorter two-member target shares victory calculation. Unexposed staged Basic groups connect Nexus, selected funding including purchased Karama shipment prices, Guild transport, Emperor and Fremen aid, Fremen worm protection, Voice, Prescience and Harkonnen traitor support. Separate staged variants support up to three members at three strongholds and up to six members at three strongholds; other group interactions remain open.', evidence: ['game/alliance-victory-option.ts', 'game/victory-progress.ts', 'game/victory-quote.ts', 'game/engine.ts', 'game/coalition-worm-protection.ts', 'game/coalition-battle-preparation.ts', 'game/traitor-declarations.ts', 'game/terminal-cancellation.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'The lobby offers the pair option. A staged group Emperor can choose either ally for spice and extra revival; group games cannot yet be started from the lobby.', evidence: ['components/game-table.tsx', 'components/coalition-emperor-aid.tsx', 'tests/short-alliance-victory-controls.test.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Legal alliance and victory paths use the shared target; staged group Emperor bots can offer a targeted revival. Strategic tuning remains deferred.', evidence: ['game/bots.ts', 'game/victory-progress.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'The printed option and provisional two-controller precedence are recorded.', evidence: ['docs/OPTIONAL_ALLIANCE_VICTORY.md'] },
+2 -1
View File
@@ -154,7 +154,8 @@ export function strongholdProgress(
);
const target = allianceVictoryTarget(
players.map((player) => player.id), g.twoPlayerControllers, members.length > 1,
g.coalitionState?.variant === 'unlimited' ? true : members.length >= 3 ? false : g.shortAllianceVictory,
g.coalitionState?.variant === 'unlimited' || g.coalitionState?.variant === 'three-short'
? true : members.length >= 3 ? false : g.shortAllianceVictory,
) + extra;
return {
player: id,