From 624f95dde402118d76dde66188534e7d163d970b Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:04:40 +1100 Subject: [PATCH] Connect staged group Nexus negotiations --- components/game-table.tsx | 15 ++- docs/CURRENT_STATUS.md | 16 ++- docs/OPTIONAL_ALLIANCE_VICTORY.md | 19 +++ docs/RULE_DECISIONS.md | 6 + game/bots.ts | 15 ++- game/engine.ts | 101 +++++++++++++++- game/reference.ts | 8 +- tests/coalition-nexus-engine.test.ts | 165 +++++++++++++++++++++++++++ tests/nexus-offer-controls.test.tsx | 17 +++ 9 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 tests/coalition-nexus-engine.test.ts diff --git a/components/game-table.tsx b/components/game-table.tsx index bb176e49..046b956f 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -284,6 +284,7 @@ import { MoritaniRetention } from './moritani-retention'; import { MoritaniAssassinate, MoritaniAssassinateHistory } from './moritani-assassinate'; import { MoritaniExtortion, MoritaniExtortionNotice } from './moritani-extortion'; import { AdvancedAllySeparation } from './advanced-ally-separation'; +import { CoalitionNexus } from './coalition-nexus'; import { NexusEmperorPayment } from './nexus-emperor-payment'; import { NexusEmperorSellerPurchase } from './nexus-emperor-seller-purchase'; import { NexusEmperorAmbassadorPurchase } from './nexus-emperor-ambassador-purchase'; @@ -301,6 +302,8 @@ export function GameTable({ exitDisabled?: boolean; }) { const me = g.players.find((p) => p.id === g.me)!; + const groupNexus = g.coalitionNexus; + const inGroup = groupNexus?.groups.some(group => group.members.length === 3 && group.members.includes(me.id)); const outgoingAlliance = g.allianceOffers[me.id]; const outgoingAllianceName = g.players.find((p) => p.id === outgoingAlliance)?.name; const shipmentAvailable = nexusGuildShipmentAvailable(g); @@ -4350,6 +4353,14 @@ export function GameTable({

A sandworm has opened a Nexus. Alliances may change now.

+ {groupNexus && { + if (intent.kind === 'propose') act({type: 'coalitionPropose', groupId: intent.groupId, + entrant: intent.entrant, expectedRevision: intent.expectedRevision}); + else if (intent.kind === 'answer') act({type: 'coalitionAnswer', event: intent.event, + nexus: intent.nexus, turn: intent.turn, accept: intent.accept, expectedRevision: intent.expectedRevision}); + else act({type: 'coalitionLeave', expectedRevision: intent.expectedRevision}); + }} />} + {!groupNexus?.proposal && !inGroup && <> {outgoingAlliance && !me.ally && (

@@ -4367,7 +4378,8 @@ export function GameTable({ > {g.players - .filter((p) => p.id !== me.id) + .filter((p) => p.id !== me.id && !groupNexus?.groups.some(group => + group.members.length === 3 && group.members.includes(p.id))) .map((p) => (