Connect Spice Banker sealed battle spending and saved settlement
Prototype one through three own spice as a separate sealed battle commitment. Reserve funds alongside actual force support, add strength only for the surviving skilled leader, and settle once through existing versioned writes. Apply the documented card/core-rule interpretation: payment survives leader death or loss, except for the sole successful traitor caller. Keep printed disc strength and bounty unchanged. Normal income awaits its deferred-custody ruling; combined modules remain gated. Validation: 54 focused tests; typecheck, lint, 4490 offline tests, production build and 43 HTTP tests passed. Independent rules, privacy, persistence and UI review completed. SQLite tests cover reserved-spice diversion, concurrent settlement, traitor waiver and legacy plans. Browser exercised a three-spice commitment, pending refresh, actual +3 strength and payment, and restored private cards and the next decision. Two genuine four-profile Basic/Advanced samples completed without rejected candidates but did not use Banker spending. Following another reported outage, the server remained healthy. Preserved all 461 opening rooms and original seat/recovery rows (495 rooms after QA/HTTP). SQLite integrity and foreign keys passed. A fresh online backup captured all 495 rooms, and browser refresh restored the QA seat. No restart or reset was needed. Verified source tree: 208bf9324ab994b5e59ed50d78998b4dbf8810b3b6799b1a577ef3b84479335f
This commit is contained in:
1 parent
9381ef140d
commit
1380271bb6
22 files changed
+1166
-25
No files matched your search
@@ -147,6 +147,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { BattlePreparation, battlePlanCommitments, bindBattlePlanCommitments, NexusInspectionHistory } from './battle-preparation';
|
||||
import { PrivateBattlePlan } from './private-battle-plan';
|
||||
import { SpiceBankerControl, spiceBankerControlState } from './spice-banker';
|
||||
import { canUsePlanetologistBattleSpecial, validLeaderSkillBattleCardPair } from '../game/leader-skill-combat';
|
||||
import type { Card } from '../game/cards';
|
||||
import { RevealedBattle } from './revealed-battle';
|
||||
@@ -379,6 +380,7 @@ export function GameTable({
|
||||
)
|
||||
: 0;
|
||||
const [support, setSupport] = useState(0);
|
||||
const [bankerSpice, setBankerSpice] = useState(0);
|
||||
const strongholdBankSupport =
|
||||
g.battle?.strongholdEffects[me.id] === 'arrakeen' ? 2 : 0;
|
||||
const battleSupportMaximum = Math.min(
|
||||
@@ -413,6 +415,34 @@ export function GameTable({
|
||||
const [discardCards, setDiscardCards] = useState<Record<string, boolean>>({});
|
||||
const stormLosses = g.decision?.kind === 'stormLosses' ? g.decision : null;
|
||||
const committed = battlePlanCommitments(g);
|
||||
const selectedBattleLeader = committed.leader
|
||||
? String(committed.leader.value ?? '')
|
||||
: leader;
|
||||
const paidBattleSupport = Math.max(0, battleSupport - strongholdBankSupport);
|
||||
const selectedBattleAllyPayment =
|
||||
allyPayment === ''
|
||||
? Math.max(0, paidBattleSupport - (me.spice ?? 0))
|
||||
: Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
Math.trunc(Number(allyPayment)),
|
||||
paidBattleSupport,
|
||||
g.aid.available,
|
||||
),
|
||||
);
|
||||
const ownBattleSupport = Math.max(
|
||||
0,
|
||||
paidBattleSupport - selectedBattleAllyPayment,
|
||||
);
|
||||
const bankerControl = spiceBankerControlState(
|
||||
g,
|
||||
selectedBattleLeader,
|
||||
ownBattleSupport,
|
||||
);
|
||||
const battleBankerSpice = Math.max(
|
||||
0,
|
||||
Math.min(Math.trunc(bankerSpice), bankerControl.maximum),
|
||||
);
|
||||
const planetologistSpecial = (card: Card) => canUsePlanetologistBattleSpecial({
|
||||
assignments: g.leaderSkills?.assignments.filter(a => a.controller === me.id) ?? [],
|
||||
selectedLeader: committed.leader ? String(committed.leader.value ?? '') : leader,
|
||||
@@ -4239,6 +4269,7 @@ export function GameTable({
|
||||
fill={(plan) => {
|
||||
setDial(plan.dial);
|
||||
setSupport(plan.support);
|
||||
setBankerSpice(plan.bankerSpice ?? 0);
|
||||
setAllyPayment(
|
||||
plan.allyPayment === undefined
|
||||
? ''
|
||||
@@ -4450,6 +4481,14 @@ export function GameTable({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<SpiceBankerControl
|
||||
game={g}
|
||||
selectedLeader={selectedBattleLeader}
|
||||
ownSupport={ownBattleSupport}
|
||||
value={battleBankerSpice}
|
||||
onChange={setBankerSpice}
|
||||
disabled={busy}
|
||||
/>
|
||||
{[
|
||||
['Weapon', weapon, setWeapon],
|
||||
['Defense', defense, setDefense],
|
||||
@@ -4556,6 +4595,9 @@ export function GameTable({
|
||||
: 'Seal battle plan',
|
||||
bindBattlePlanCommitments(g, {
|
||||
type: 'battlePlan',
|
||||
...(battleBankerSpice
|
||||
? { bankerSpice: battleBankerSpice }
|
||||
: {}),
|
||||
...(g.aid.available > 0 && allyPayment !== ''
|
||||
? { allyPayment: Number(allyPayment) }
|
||||
: {}),
|
||||
@@ -4580,9 +4622,17 @@ export function GameTable({
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="muted">
|
||||
Your plan is sealed. Waiting for your opponent.
|
||||
</p>
|
||||
<>
|
||||
<p className="muted">
|
||||
Your plan is sealed. Waiting for your opponent.
|
||||
</p>
|
||||
{!!g.battle.plans[me.id]?.bankerSpice && (
|
||||
<p className="fine">
|
||||
Spice Banker: {g.battle.plans[me.id].bankerSpice}{' '}
|
||||
spice committed.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user