From 0d8481e8216c20cfe100d903fe4c7d41f9136dc7 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:16:02 +1100 Subject: [PATCH] Counter each changing origin in a resident BG gather Hold both physical groups until all required matching responses allow the original movement. Share normal packet denials across gather and ordinary moves, then resume one original movement suffix. --- components/game-table.tsx | 8 + components/planetologist-movement.tsx | 11 + docs/ADVISOR_MATCHING.md | 68 +++++ docs/CURRENT_STATUS.md | 31 +++ docs/IMPLEMENTATION_STATUS.md | 43 ++++ docs/PLANETOLOGIST_RULES.md | 11 +- docs/RULE_DECISIONS.md | 14 ++ game/advisor-gather-matching.ts | 94 +++++++ game/bots.ts | 9 +- game/engine.ts | 95 ++++++- game/karama-context.ts | 3 + game/karama-movement-cancellation.ts | 1 + game/reference.ts | 31 ++- tests/advisor-gather-matching-engine.test.ts | 102 ++++++++ tests/advisor-gather-matching.test.ts | 245 +++++++++++++++++++ tests/fixture-advisor-gather-matching.ts | 231 +++++++++++++++++ 16 files changed, 987 insertions(+), 10 deletions(-) create mode 100644 game/advisor-gather-matching.ts create mode 100644 tests/advisor-gather-matching-engine.test.ts create mode 100644 tests/advisor-gather-matching.test.ts create mode 100644 tests/fixture-advisor-gather-matching.ts diff --git a/components/game-table.tsx b/components/game-table.tsx index 357bed5a..049036b8 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -2503,6 +2503,14 @@ export function GameTable({ {' '}Any already-played movement or declaration card keeps its original use. Choose a legal replacement if matching is prevented.

)} + {g.response.kind==='advisorFlip'&&g.response.advisorGatherMatchingSource&&( +

+ Gathered group {g.response.advisorGatherMatchingPosition?.current} of {g.response.advisorGatherMatchingPosition?.total}: + {' '}the incoming forces from {territory(g.response.advisorGatherMatchingSource.origin).name} must match your residents. + {' '}Neither origin has moved, changed type or collected spice. Every required group response must allow the same original gather before it moves once. + {' '}Preventing one group leaves both origins and the movement unused, while this prevented packet here remains unavailable. Choose a legal replacement. +

+ )} {g.response.kind==='advisorFlip'&&g.response.advisorTransportMatchingSource&&(

The incoming transport group must match your residents before payment or force transfer. diff --git a/components/planetologist-movement.tsx b/components/planetologist-movement.tsx index 904a89d4..735482f0 100644 --- a/components/planetologist-movement.tsx +++ b/components/planetologist-movement.tsx @@ -10,6 +10,8 @@ import { type PlanetologistMoveMode, } from '@/game/planetologist-movement'; import { botGroundMoveAllowed } from '@/game/bot-mobility'; +import { isAdvisor } from '@/game/advisors'; +import { quoteAdvisorGatherMatching, AdvisorGatherMatchingError } from '@/game/advisor-gather-matching'; import { gameTerritories, location, @@ -92,6 +94,12 @@ export function planetologistMoveDraft( ? 'The selected route is beyond the boosted range or is blocked.' : 'Each selected source must reach the destination using its normal range.', }; + if(mode==='gather'){ + try{ + quoteAdvisorGatherMatching(game,{owner:me.id,move:me.moved??0,destination,sector, + advisors:isAdvisor(me,destination),group:Object.fromEntries(selected)}); + }catch(error){if(!(error instanceof AdvisorGatherMatchingError))throw error;return {action:null,blocked:error.message};} + } return { action: { type: 'move', @@ -168,6 +176,9 @@ export function PlanetologistMovement({ gather forces from exactly two territories into the selected destination.

+ {game.advanced&&me.faction==='beneGesserit'&&mode==='gather'&&( +

When joining your residents, each incoming origin that changes type has its own matching response. Both groups wait for every required allowance. Prevention leaves the movement unused and keeps both groups in place.

+ )}