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
87 lines
2.6 KiB
TypeScript
87 lines
2.6 KiB
TypeScript
'use client';
|
||
import type { GameView } from '@/game/engine';
|
||
import { HelpTip } from './help-tip';
|
||
import { faction } from '@/game/catalog';
|
||
import { leaderStrengthLabel } from '@/game/cards';
|
||
import { BattleComponentInspection } from './battle-component-inspection';
|
||
|
||
export function PrivateBattlePlan({ game }: { game: GameView }) {
|
||
const insight = game.battle?.fullPlanInsight;
|
||
if (
|
||
!insight ||
|
||
game.battle?.revealed ||
|
||
game.battle?.fullPlan?.owner !== game.me
|
||
)
|
||
return null;
|
||
const leader = game.allLeaders.find((l) => l.id === insight.plan.leader);
|
||
const name = (id: string | null) =>
|
||
id
|
||
? (insight.cards.find((c) => c.id === id)?.name ??
|
||
game.allLeaders.find((l) => l.id === id)?.name ??
|
||
id)
|
||
: 'None';
|
||
return (
|
||
<section
|
||
className="private-battle-plan"
|
||
aria-label="Private battle plan inspection"
|
||
>
|
||
<span className="eyebrow">Atreides · private inspection</span>
|
||
<h3>
|
||
{game.players.find((p) => p.id === insight.target)?.name}’s committed
|
||
plan <HelpTip topic="fullPlan" />
|
||
</h3>
|
||
<p className="muted">
|
||
Only you receive this inspection, including when you are helping an
|
||
ally. It stays available until both plans are publicly revealed. The
|
||
other combatant can prepare and seal their plan without an inspection
|
||
confirmation.
|
||
</p>
|
||
<dl>
|
||
<div>
|
||
<dt>Dial</dt>
|
||
<dd>{insight.plan.dial}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Spice support</dt>
|
||
<dd>{insight.plan.support}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Spice Banker commitment</dt>
|
||
<dd>{insight.plan.bankerSpice ?? 0}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Leader</dt>
|
||
<dd>{name(insight.plan.leader)}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Weapon</dt>
|
||
<dd>{name(insight.plan.weapon)}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Defense</dt>
|
||
<dd>{name(insight.plan.defense)}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Kwisatz Haderach</dt>
|
||
<dd>{insight.plan.kwisatz ? 'Included' : 'Not included'}</dd>
|
||
</div>
|
||
</dl>
|
||
<BattleComponentInspection
|
||
playerName={
|
||
game.players.find((p) => p.id === insight.target)?.name ?? 'Player'
|
||
}
|
||
cards={insight.cards}
|
||
leader={
|
||
leader
|
||
? {
|
||
name: leader.name,
|
||
factionName: faction(leader.faction).name,
|
||
strength: leaderStrengthLabel(leader),
|
||
}
|
||
: undefined
|
||
}
|
||
/>
|
||
</section>
|
||
);
|
||
}
|