Resolve ambiguous Stone battles with sealed physical totals
Add a provisional post-reveal physical-count commitment for new outcome-changing Basic/Advanced Stone and copied Mirror battles. Keep all original sides sealed until complete and retain matching later typed losses. Preserve legacy, ordinary and invariant-winner timing. Carry the same constraint through card cleanup and native casualty consumers. Validation: frozen 16 files146/146, types/lint/build and14 original-action/rendered-control programmes; four minimal policies make both owned choices legally. Source timing is explicitly provisional. Wider combinations, assurance and deployed acceptance remain open. No saved-game, reset, restart or deployment operation.
This commit is contained in:
1 parent
677ad7aa3c
commit
6ccfaa1b4d
22 files changed
+1247
-83
No files matched your search
@@ -168,6 +168,7 @@ import {
|
||||
} from '@/game/battle-cards';
|
||||
import { validBattleSlotPair, usesNativeBattleSpecial } from '@/game/battle-card-slots';
|
||||
import { stoneBurnerPlanBlock, stoneBurnerWithdrawalContext } from '@/game/stone-burner';
|
||||
import { StoneBurnerAllocation } from './stone-burner-allocation';
|
||||
import { mirrorWeaponModeBlock } from '@/game/mirror-weapon-mode';
|
||||
import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens';
|
||||
import {
|
||||
@@ -629,6 +630,7 @@ export function GameTable({
|
||||
? 'attacker'
|
||||
: 'defender',
|
||||
stoneCandidateContext,
|
||||
stoneContext?.allowAllocations,
|
||||
),
|
||||
)
|
||||
.find((reason) => reason !== null) ?? null)));
|
||||
@@ -3737,6 +3739,11 @@ export function GameTable({
|
||||
accept: false,
|
||||
})}
|
||||
</>
|
||||
) : g.decision.kind === 'stoneAllocation' ? (
|
||||
g.battle?.stoneAllocation && <StoneBurnerAllocation event={g.decision.event}
|
||||
totals={g.battle.stoneAllocation.totals}
|
||||
onCommit={undialed => act({type:'decision',event:g.battle!.stoneAllocation!.event,undialed})}
|
||||
busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
|
||||
) : g.decision.kind === 'discoveryEntry' ? (
|
||||
<DiscoveryEntryDecision game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
|
||||
) : g.decision.kind === 'ecologicalStorm' ? (
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from './ui/button';
|
||||
|
||||
export function StoneBurnerAllocation({
|
||||
event,
|
||||
totals,
|
||||
onCommit,
|
||||
busy,
|
||||
}: {
|
||||
event: string;
|
||||
totals: readonly number[];
|
||||
onCommit: (undialed: number) => void;
|
||||
busy: boolean;
|
||||
}) {
|
||||
const choices = [...new Set(totals)].filter(total => Number.isSafeInteger(total) && total >= 0)
|
||||
.sort((a, b) => a - b);
|
||||
if (!event || !choices.length) return null;
|
||||
return (
|
||||
<section aria-label="Stone Burner undialed force commitment" className="notice space-y-3" data-event={event}>
|
||||
<h4>Commit undialed physical forces</h4>
|
||||
<p>
|
||||
Provisional timing: choose the number of your undialed physical counters
|
||||
for this Stone Burner comparison. Each ordinary or elite counter counts
|
||||
as one. Choices stay sealed until everyone has committed.
|
||||
</p>
|
||||
<p>
|
||||
The winner chooses their ordinary and elite losses afterward, consistent
|
||||
with the committed total. This choice does not change your dial or spice.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{choices.map(total => (
|
||||
<Button key={`${event}:${total}`} type="button" className="min-h-11 whitespace-normal text-base" disabled={busy}
|
||||
onClick={() => { if (!busy) onCommit(total); }}>
|
||||
Commit {total} undialed physical forces
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user