From 643add3f8570d39fa789cb4d87b538fee8289a4e Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:07:14 +1100 Subject: [PATCH] Route classic Homeworld Harass returns through native custody --- components/game-table.tsx | 20 +- components/harass-withdraw-choice.tsx | 14 +- docs/CURRENT_STATUS.md | 21 ++ docs/HARASS_WITHDRAW_RUNTIME.md | 83 ++++++- docs/IMPLEMENTATION_STATUS.md | 45 ++++ docs/RULE_DECISIONS.md | 1 + game/battle-resolution-quote.ts | 6 +- game/card-presentation.ts | 2 +- game/engine.ts | 74 +++--- game/homeworld-native-reserves.ts | 62 +++-- game/reference.ts | 14 +- tests/battle-resolution-quote.test.ts | 15 +- tests/fixture-harass-homeworld-arrakis.ts | 254 +++++++++++++++++++++ tests/fixture-harass-homeworld.ts | 264 ++++++++++++++++++++++ tests/harass-homeworld-arrakis.test.ts | 159 +++++++++++++ tests/harass-homeworld.test.ts | 210 +++++++++++++++++ tools/prototype-room.ts | 6 +- tools/start-prototype.ts | 7 +- 18 files changed, 1174 insertions(+), 83 deletions(-) create mode 100644 tests/fixture-harass-homeworld-arrakis.ts create mode 100644 tests/fixture-harass-homeworld.ts create mode 100644 tests/harass-homeworld-arrakis.test.ts create mode 100644 tests/harass-homeworld.test.ts diff --git a/components/game-table.tsx b/components/game-table.tsx index 13c87849..c5fdbd74 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -5129,7 +5129,14 @@ export function GameTable({ {g.players.find(p => p.id === g.battle!.smugglerCollection!.player)?.name}’s Smuggler has {g.battle.smugglerCollection.amount} spice pending from the reveal-time piles. If the leader survives, collection resolves before battle settlement, even after defeat. A partial collection across several piles requires its owner’s sector choice. That spice is not yet spendable.

} - {!g.battle.traitorSubmitted.includes(me.id) && ( + {!g.battle.traitorVoters.includes(me.id) && ( +

+ Only the native faction may declare Traitors here. + Wait for that decision; any legal invader late defense + follows through its own original control. +

+ )} + {g.battle.traitorVoters.includes(me.id) && !g.battle.traitorSubmitted.includes(me.id) && ( <> {actionButton( 'Reveal traitor', @@ -5358,6 +5365,17 @@ export function GameTable({ ))} + {g.homeworlds && g.battle.harassWithdraw && ( +

+ Classic Homeworld timing: actual undialed counters + return to your native reserve Homeworlds, never to + the Tanks. In Advanced, Emperor normal forces return + to Kaitain and Sardaukar to Salusa; Basic retains + its original single primary World. Your original + dial/support stay committed across population + thresholds. Harass cannot be played on any own World. +

+ )} {g.battle.reinforcements && (

Reinforcements may occupy either card slot but is neither a weapon nor a defense. diff --git a/components/harass-withdraw-choice.tsx b/components/harass-withdraw-choice.tsx index 1833a10b..a4cd1580 100644 --- a/components/harass-withdraw-choice.tsx +++ b/components/harass-withdraw-choice.tsx @@ -47,7 +47,9 @@ export function harassWithdrawChoiceState( } } -function locationLabel(key: string) { +function locationLabel(g: GameView, key: string) { + if (key.startsWith('homeworld:')) + return g.combatLocations?.find(place => place.id === key)?.name ?? key; const location = splitLocation(key); return `${territory(location.territory).name} · sector ${location.sector}`; } @@ -118,6 +120,14 @@ export function HarassWithdrawChoice({ Your Battle Plan is revealed. Choose the exact undialed counters that return to reserves before the Traitor decision.

+ {g.homeworlds && ( +

+ These counters return to native Homeworld reserves. Advanced Emperor + ordinary counters go to Kaitain and Sardaukar to Salusa; Basic keeps + both types on its original primary World. Original commitment/support + does not change when that population rises. +

+ )}