From 9381ef140df430fe213738c62bdbbd2902f43e32 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:19:33 +1000 Subject: [PATCH] Connect optional Smuggler reserve shipment and saved interception Prototype one free accompanying force into an empty territory with explicit opt-out, shared human/AI pricing, physical elite custody and Guild recipient accounting. Bind pending quotes to their unique saved Guild decision; preserve legacy unstamped full-price declarations. Smuggler battle collection and combined modules remain unfinished and gated. Repair a randomly dealt-out Karama fixture by relocating its actual physical card without changing inventory. Validation: 70 focused tests; typecheck, lint, 4477 offline tests, production build and 43 HTTP tests passed. Independent rules/persistence review completed, including interruption and cancellation. Browser checked the price toggle, three arrivals for two spice, and refresh with unchanged private cards. Two genuine four-profile Basic/Advanced samples completed without rejected candidates but did not use Smuggler. Preserved all 427 opening rooms and original seat/recovery rows (461 rooms after QA/HTTP). SQLite integrity and foreign keys passed. Server remained healthy after the reported power outage; no restart or reset. Verified source tree: bf6e02bf6873ca6d6b01c83481489f55b2c7db0184a7056364343440dae88803 --- components/game-table.tsx | 12 +- components/leader-skills.tsx | 2 +- components/smuggler-shipment.tsx | 35 +++ docs/COMPONENT_INVENTORY.md | 3 +- docs/CURRENT_STATUS.md | 8 + docs/IMPLEMENTATION_STATUS.md | 25 ++ docs/LEADER_SKILLS_RUNTIME.md | 7 +- docs/RULE_DECISIONS.md | 3 +- docs/SMUGGLER_SHIPMENT.md | 78 ++++++ game/bots.ts | 9 +- game/engine.ts | 38 ++- game/reference.ts | 15 +- game/smuggler-shipment.ts | 39 +++ tests/discovery-entry-review.test.ts | 30 ++- tests/smuggler-shipment-controls.test.tsx | 36 +++ tests/smuggler-shipment-fixture.ts | 119 +++++++++ tests/smuggler-shipment-recovery.test.ts | 307 ++++++++++++++++++++++ tests/smuggler-shipment.test.ts | 274 +++++++++++++++++++ 18 files changed, 1015 insertions(+), 25 deletions(-) create mode 100644 components/smuggler-shipment.tsx create mode 100644 docs/SMUGGLER_SHIPMENT.md create mode 100644 game/smuggler-shipment.ts create mode 100644 tests/smuggler-shipment-controls.test.tsx create mode 100644 tests/smuggler-shipment-fixture.ts create mode 100644 tests/smuggler-shipment-recovery.test.ts create mode 100644 tests/smuggler-shipment.test.ts diff --git a/components/game-table.tsx b/components/game-table.tsx index 2df6e29b..dcf0de25 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -124,6 +124,8 @@ import { import { LeaderPortrait } from './leader-portrait'; import { ShipmentQuote } from './shipment-quote'; import { reserveShipmentCost } from '@/game/shipment-price'; +import { quoteSmugglerShipment } from '@/game/smuggler-shipment'; +import { SmugglerShipmentChoice } from './smuggler-shipment'; import { nexusGuildSecretAllyAction, nexusGuildSecretAllyQuote } from '@/game/nexus-guild-secret-ally-options'; import { nexusRicheseAction, nexusRicheseQuote } from '@/game/nexus-richese-options'; import { guildTransportQuote } from '@/game/transport-quote'; @@ -304,6 +306,7 @@ export function GameTable({ const [amount, setAmount] = useState(1); const [richeseShipmentEvent, setRicheseShipmentEvent] = useState(''); const useRicheseShipment = !!g.nexusRichese && richeseShipmentEvent === g.nexusRichese.event; + const [useSmuggler, setUseSmuggler] = useState(true); const [bidDraft, setBidDraft] = useState({ auction: '', value: 1 }); const auctionKey = `${g.code}/${g.turn}/${g.auction?.remaining ?? 0}`; const minimumBid = (g.auction?.bid ?? 0) + 1; @@ -507,10 +510,13 @@ export function GameTable({ g.players.some((p) => p.faction === 'guild' && p.id === me.ally) || g.karamaShipping?.player === me.id, }; + const smugglerOffer = validShipmentAmount && !useGuildSecret && !useRicheseShipment + ? quoteSmugglerShipment(g, me.id, selected, amount) : null; + const smugglerShipment = useSmuggler ? smugglerOffer : null; const shipmentCost = validShipmentAmount ? useGuildSecret ? (nexusGuildSecretAllyQuote(g, selected, amount)?.cost ?? null) : useRicheseShipment ? (nexusRicheseQuote(g, selected, amount)?.cost ?? null) - : reserveShipmentCost(shipmentRate, territory(selected).type, amount) + : reserveShipmentCost(shipmentRate, territory(selected).type, amount - (smugglerShipment ? 1 : 0)) : null; if (useGuildSecret && shipmentCost === null && !g.nexusGuildSecretAlly?.blocked) shipmentProblems.push('Choose a current Guild Secret Ally shipment.'); if (useRicheseShipment && shipmentCost === null && !g.nexusRichese?.blocked) @@ -3808,7 +3814,7 @@ export function GameTable({ /> +