From c999054ae02d5a03d1005448f9c467bf0c44bed3 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:38:55 +1100 Subject: [PATCH] Counter incoming BG matching before Fremen Ambassador relocation Bind the selected packet to its original allied grant before transfer or collection. Preserve the committed trigger, restore legal replacement choices after prevention and resume the original movement once. --- components/ambassador-movement.tsx | 14 + components/game-table.tsx | 7 + docs/ADVISOR_MATCHING.md | 62 +++++ docs/CURRENT_STATUS.md | 26 ++ docs/IMPLEMENTATION_STATUS.md | 43 +++ docs/RULE_DECISIONS.md | 15 ++ game/advisor-ambassador-matching.ts | 156 +++++++++++ game/bots.ts | 7 + game/engine.ts | 98 ++++++- game/karama-context.ts | 2 + game/karama-movement-cancellation.ts | 1 + game/reference.ts | 22 +- ...advisor-ambassador-matching-engine.test.ts | 101 +++++++ tests/advisor-ambassador-matching.test.ts | 252 ++++++++++++++++++ tests/fixture-advisor-ambassador-matching.ts | 246 +++++++++++++++++ 15 files changed, 1040 insertions(+), 12 deletions(-) create mode 100644 game/advisor-ambassador-matching.ts create mode 100644 tests/advisor-ambassador-matching-engine.test.ts create mode 100644 tests/advisor-ambassador-matching.test.ts create mode 100644 tests/fixture-advisor-ambassador-matching.ts diff --git a/components/ambassador-movement.tsx b/components/ambassador-movement.tsx index 22301804..85942ad7 100644 --- a/components/ambassador-movement.tsx +++ b/components/ambassador-movement.tsx @@ -4,6 +4,7 @@ import { useId, useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { location, splitLocation, territory } from '@/game/board'; import { sandmasterDestinationCollection } from '@/game/sandmaster-movement'; +import { quoteAdvisorAmbassadorMatching, AdvisorAmbassadorMatchingError } from '@/game/advisor-ambassador-matching'; import { Button } from './ui/button'; import { Input } from './ui/input'; @@ -94,8 +95,18 @@ export function AmbassadorMovement({ sandmaster?.piles.some((pile) => pile.key === sandmasterSelection.key) ? sandmasterSelection.key : sandmaster?.key; + let matchingBlock:string|null=null; + if(destination&&total>0){ + try{ + quoteAdvisorAmbassadorMatching({...game,pendingAmbassador:{...entry,turn:game.turn,phase:game.phase, + beneficiary:game.me},advisorAmbassadorMatchingDenied:entry.matchingDenied}, + {parent:entry.event,owner:game.me,origin:source.territory,destination:destination.territory, + sector:destination.sector,advisors:destination.advisors,group:selectedForces}); + }catch(error){if(!(error instanceof AdvisorAmbassadorMatchingError))throw error;matchingBlock=error.message;} + } const reason = destination?.blocked ?? + matchingBlock ?? (!destination ? 'Choose an available destination.' : !total && !includesMarker @@ -121,6 +132,9 @@ export function AmbassadorMovement({ Relocate a group from one territory. This does not spend your ordinary shipment or movement. The Ambassador has already been triggered.

+ {game.advanced&&game.players.find(player=>player.id===game.me)?.faction==='beneGesserit'&&( +

An opposite-type incoming group has a separate matching response before relocation. If matching is prevented, choose another legal packet or destination; the original trigger stays committed.

+ )}