Preserve Emperor gift opportunity before bot Extortion
Verify and publish checkpoint container / container (push) Successful in 10m54s

This commit is contained in:
Quintine committed 2026-10-30 17:44:00 +11:00
1 parent a6be17fc5d
commit 7c1f4b4e09
13 files changed
+370 -13

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
'use client';
import type { Action } from '@/game/engine';
import { Button } from './ui/button';
/** The same public opportunity is shown regardless of the Emperor's spice. */
export type EmperorExtortionPriorityView = {
event: string;
recipient: string;
canContinue: boolean;
};
export type EmperorExtortionPriorityProps = {
game: {
emperorExtortionPriority: EmperorExtortionPriorityView | null;
players: readonly { id: string; name: string }[];
};
act: (action: Action) => void;
busy: boolean;
};
/** The payer's normal Extortion choice remains pending until the Emperor
* completes this optional alliance opportunity. */
export function EmperorExtortionPriority({ game, act, busy }: EmperorExtortionPriorityProps) {
const priority = game.emperorExtortionPriority;
if (!priority) return null;
const event = priority.event;
const recipient = game.players.find(player => player.id === priority.recipient)?.name ?? 'the ally';
const canAct = () => !busy && game.emperorExtortionPriority === priority &&
priority.event === event && typeof event === 'string' && event.length > 0 && priority.canContinue;
return <section className="notice space-y-3" aria-label="Emperor Extortion opportunity" aria-live="polite">
<h3>Emperor alliance opportunity</h3>
<p>Before {recipient} makes the Extortion choice, the Emperor may share spice with their ally. The existing gift control shows any available amount.</p>
{priority.canContinue ? <Button className="game-action min-h-11 whitespace-normal"
data-testid="emperor-extortion-continue" disabled={!canAct()}
onClick={() => { if (canAct()) act({type:'emperorExtortionContinue', event}); }}>
Continue to Extortion
</Button> : <p>Waiting for the Emperor to continue.</p>}
</section>;
}
+2
View File
@@ -1,6 +1,7 @@
'use client';
import { SaphoArrivalOrder } from './sapho-arrival-order';
import { SaphoStationPriority } from './sapho-station-priority';
import { EmperorExtortionPriority } from './emperor-extortion-priority';
import { RicheseBidPromisePanel } from './richese-bid-promise-panel';
import { EarlyRicheseBidPromises } from './early-richese-bid-promises';
import { TravelPromises } from './travel-promises';
@@ -1536,6 +1537,7 @@ export function GameTable({
<EcazLoyaltyCard loyalty={g.ecazLoyalty} />
<MoritaniTerrorSupply game={g} />
<MoritaniExtortionNotice game={g} />
{g.emperorExtortionPriority && <EmperorExtortionPriority game={g} act={act} busy={reactionBusy} />}
{g.saphoStationPriority && <SaphoStationPriority game={g} act={act}
busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />}
{g.saphoArrivalOrder && <SaphoArrivalOrder view={g.saphoArrivalOrder} me={g.me}