From d0e4c177e431b0072e8df3cbb8e51c0d7c0749c2 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:22:37 +1100 Subject: [PATCH] Support mixed No-Field Homeworld transfers --- components/homeworld-shipment.tsx | 26 ++- docs/CURRENT_STATUS.md | 13 ++ docs/RICHESE_NO_FIELD_RULES.md | 32 ++- docs/RULE_DECISIONS.md | 13 ++ game/engine.ts | 23 ++- game/homeworld-shipment-options.ts | 51 +++-- game/reference.ts | 2 +- game/richese-homeworld-marker-shipment.ts | 32 ++- ...e-homeworld-marker-mixed-controls.test.tsx | 108 ++++++++++ ...ichese-homeworld-marker-mixed-http.test.ts | 168 ++++++++++++++++ ...chese-homeworld-marker-mixed-quote.test.ts | 81 ++++++++ ...ese-homeworld-marker-mixed-runtime.test.ts | 184 ++++++++++++++++++ 12 files changed, 695 insertions(+), 38 deletions(-) create mode 100644 tests/richese-homeworld-marker-mixed-controls.test.tsx create mode 100644 tests/richese-homeworld-marker-mixed-http.test.ts create mode 100644 tests/richese-homeworld-marker-mixed-quote.test.ts create mode 100644 tests/richese-homeworld-marker-mixed-runtime.test.ts diff --git a/components/homeworld-shipment.tsx b/components/homeworld-shipment.tsx index 7238edc0..32b4bd4d 100644 --- a/components/homeworld-shipment.tsx +++ b/components/homeworld-shipment.tsx @@ -12,8 +12,7 @@ import { import { Button } from './ui/button'; import { Input } from './ui/input'; -/** This private control is separate from physical-source shipment: the marker - * costs one but does not withdraw a reserve counter until it is revealed. */ +/** The concealed marker does not withdraw a reserve counter until revealed. */ export function HomeworldNoFieldShipment({ game, act, busy }: { game: GameView; act: (action: Action) => void; busy: boolean; }) { @@ -22,6 +21,7 @@ export function HomeworldNoFieldShipment({ game, act, busy }: { const [selectedDestination, setSelectedDestination] = useState(''); const [selectedToken, setSelectedToken] = useState(''); const [allyPayment, setAllyPayment] = useState(0); + const [forces, setForces] = useState({ normal: 0, elite: 0 }); if (!me || me.faction !== 'richese' || info?.owner !== me.id || !info.private) return null; const deployed = info.private.deployed; const source = deployed && 'homeworld' in deployed.location ? deployed.location.homeworld : undefined; @@ -30,13 +30,16 @@ export function HomeworldNoFieldShipment({ game, act, busy }: { const destination = targets.find(world => world.id === selectedDestination) ?? targets[0]; const tokens = info.private.tokens.filter(token => source ? token.id === deployed?.tokenId : token.id !== info.private!.lastShipped); const token = tokens.find(candidate => candidate.id === selectedToken) ?? tokens[0]; - const quote = richeseHomeworldMarkerChoice(game, destination?.id ?? '', token?.id ?? '', allyPayment, source); + const pool = source ? game.homeworlds?.worlds?.find(world => world.id === source)?.forces[me.id] : undefined; + const quote = richeseHomeworldMarkerChoice(game, destination?.id ?? '', token?.id ?? '', allyPayment, source, + source ? forces : undefined); return
Ship a concealed No-Field

Provisional Homeworld rule: ship one concealed marker {source ? `from ${homeworldName(game, source)}` : 'from your supply'} to - another non-allied Homeworld for one spice. Its value stays private and its physical forces - remain in your reserves until the marker is revealed. This uses your normal shipment.

+ another non-allied Homeworld. Its value stays private and its represented forces + remain in your reserves until the marker is revealed. This uses your normal shipment. + {source && ' Physical forces already on the same Homeworld may accompany it.'}

+ {source && pool &&
+ Accompanying forces from {homeworldName(game, source)} + {(['normal', 'elite'] as const).filter(kind => kind === 'normal' || pool.elite > 0).map(kind => + )} +
} {me.ally && } - One marker · one spice total · you pay {quote.ownPayment}, ally pays {quote.allyPayment}. + {quote.amount} total (including one marker) · {quote.cost === 1 ? 'one' : quote.cost} spice total · you pay {quote.ownPayment}, ally pays {quote.allyPayment}. {quote.blocked && ` ${quote.blocked}`}