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