Allow late Sapho priority before final battle settlement
Fresh Richese-roster battles support clean aggressor changes after plans and revelation. Both combatants explicitly finish a public final opportunity regardless of held cards or result; an actual Sapho use clears both marks with a fresh event. Close priority before physical Stone allocation and other outcome settlement. Preserve plans, votes, participants, Habbanya priority and reserved physical cards. Keep old unstamped timing and discard signature bytes. Route all final vote exits through native Homeworld defense first. Add persistent controls and finite minimal bot readiness. Validation:16-file177/177, types/lint/build and eight original-action/rendered-table programmes. Actual cache1 and paid forces use40 Basic/47 Advanced source actions; genuine Stone1 turn1/Sapho1 turn2 uses86. Ordinary and mixed count2 ties change through sealed-plan/final-window Sapho, explicit revised acknowledgements and native losses. Sixteen minimal proposals are legal; controlled reservation and pure window/control cases are separate. Bounded review fixed the Harkonnen Nexus final-vote route; frozen feedback corrected four historical fixture assumptions. All checks then passed together. Final-window protocol is provisional. Direct late Semuta claim/capture, combined World/Nexus/Sapho, wider combinations, full modes, final assurance and browser/deployed acceptance remain open. No saved-room/database/reset/restart/deployment operation.
This commit is contained in:
1 parent
cb13a4d674
commit
84200ebc74
24 files changed
+1023
-40
No files matched your search
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import type { BattleOutcomeWindow } from '@/game/battle-outcome-window';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
export function BattleOutcomeWindowControls({
|
||||
window,
|
||||
me,
|
||||
players,
|
||||
busy,
|
||||
onReady,
|
||||
}: {
|
||||
window: BattleOutcomeWindow;
|
||||
me: string;
|
||||
players: readonly { id: string; name: string }[];
|
||||
busy: boolean;
|
||||
onReady: (event: string) => void;
|
||||
}) {
|
||||
const combatants = [window.attacker, window.defender];
|
||||
const participant = combatants.includes(me);
|
||||
const ownReady = window.ready.includes(me);
|
||||
return (
|
||||
<section className="my-4 flex min-w-0 flex-col gap-3" aria-label="Final battle actions" data-event={window.event}>
|
||||
<h3>Final battle actions</h3>
|
||||
<p>
|
||||
Play any optional final battle card you want to use, then finish.
|
||||
Battle resolution waits for both players.
|
||||
</p>
|
||||
<p className="notice">
|
||||
If Juice of Sapho changes the aggressor, both finished marks are
|
||||
cleared and both players get a fresh final opportunity.
|
||||
</p>
|
||||
<ul aria-label="Final battle action status">
|
||||
{combatants.map(player => (
|
||||
<li key={player}>
|
||||
{players.find(seat => seat.id === player)?.name ?? player}:{' '}
|
||||
{window.ready.includes(player) ? 'Finished' : 'Choosing final actions'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{participant && (
|
||||
<Button
|
||||
className="min-h-11 whitespace-normal motion-reduce:transition-none"
|
||||
disabled={busy || window.closed || ownReady}
|
||||
onClick={() => {
|
||||
if (!participant || busy || window.closed || ownReady) return;
|
||||
onReady(window.event);
|
||||
}}
|
||||
>
|
||||
Finish final battle actions
|
||||
</Button>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -77,6 +77,7 @@ import { RicheseSpecialKarama } from './richese-special-karama';
|
||||
import { RicheseGift } from './richese-gift';
|
||||
import { Distrans } from './distrans';
|
||||
import { JuiceOfSapho } from './juice-of-sapho';
|
||||
import { BattleOutcomeWindowControls } from './battle-outcome-window';
|
||||
import { LeaderSkillsPanel } from './leader-skills';
|
||||
import { LeaderSkillBattleGuide } from './leader-skill-battle-guide';
|
||||
import { smugglerBattlePlanBlock } from '@/game/smuggler-battle';
|
||||
@@ -5155,6 +5156,11 @@ export function GameTable({
|
||||
{g.battle.tieWinner !== g.battle.aggressor ? ' (Habbanya Stronghold advantage)' : ''}.
|
||||
</p>
|
||||
<EcazOccupyBattleSummary game={g} />
|
||||
{g.battle.outcomeWindow && !g.battle.outcomeWindow.closed && (
|
||||
<BattleOutcomeWindowControls window={g.battle.outcomeWindow} me={g.me}
|
||||
players={g.players} busy={busy || !!g.decision || !!g.response || !!g.truthtrance || !!g.phaseOpening}
|
||||
onReady={event => act({type: 'battleOutcomeReady', event})} />
|
||||
)}
|
||||
{!g.battle.revealed && g.battle.leaderless.length > 0 && (
|
||||
<p className="notice block" role="status">
|
||||
{g.battle.leaderless.map(id => g.players.find(p => p.id === id)?.name).join(', ')}
|
||||
|
||||
@@ -23,9 +23,11 @@ function unavailableReason(game: GameView) {
|
||||
return 'Movement order can change only before the current combined shipment and movement turn begins. First requires all remaining turns to be unstarted and the Advanced Guild to have finished or be absent. Completed turns stay completed. Already-held positions and committed cards are unavailable.';
|
||||
if (game.phase === 6)
|
||||
return game.battle
|
||||
? 'Become aggressor only during your open pre-plan opportunity, before declaring ready or committing a plan. Already-held priority and committed cards are unavailable. Finish the current battle and its aftermath before changing remaining battle-choice order.'
|
||||
? game.battle.saphoTimingVersion === 1
|
||||
? 'Become aggressor at a clean boundary before both players finish final battle actions. Already-held priority, a committed card, pending decisions and closed final opportunities are unavailable. Finish the current battle and its aftermath before changing remaining battle-choice order.'
|
||||
: 'This older battle retains its pre-plan opportunity, before declaring ready or committing a plan. Already-held priority and committed cards are unavailable. Finish the current battle and its aftermath before changing remaining battle-choice order.'
|
||||
: 'You need an unresolved battle and an available first or last position. Completed battles stay resolved, and a card committed elsewhere cannot be used.';
|
||||
return 'These controls support Once Around and cyclic bidding, movement order, remaining battle-choice order and pre-plan aggressor. Later battle intervention and other phase modes remain unfinished.';
|
||||
return 'These controls support Once Around and cyclic bidding, movement order, remaining battle-choice order and aggressor priority. Other phase modes remain unfinished.';
|
||||
}
|
||||
|
||||
export function JuiceOfSapho({
|
||||
@@ -68,7 +70,7 @@ export function JuiceOfSapho({
|
||||
>
|
||||
<p>
|
||||
{aggressor
|
||||
? 'Become this battle’s aggressor before plans. You win ordinary ties; a Habbanya Stronghold advantage still takes precedence. The participants and later battle choices stay unchanged.'
|
||||
? 'Become this battle’s aggressor. You win ordinary ties; a Habbanya Stronghold advantage still takes precedence. The participants, committed plans and later battle choices stay unchanged.'
|
||||
: cyclic
|
||||
? `Bid ${option.mode} in the current cyclic lot. Provisional duration: this lot only. Existing bids, funding and passes stay committed. Later circuits use the changed cycle; the next lot returns to its normal opener.`
|
||||
: once
|
||||
@@ -96,7 +98,7 @@ export function JuiceOfSapho({
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm">
|
||||
Later aggressor intervention and other phase modes are still unfinished.
|
||||
Late aggressor timing in new battles uses a provisional shared final opportunity. Older battles retain their pre-plan timing. Other phase modes are still unfinished.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
Reference in new issue
Block a user