Files
Dune/game/leader-skill-battle-board.ts
T
Quintine 06f46c6f79 Connect Leader Skill battle effects and private Rihani exchanges
Prototype Mentat/Bureaucrat score effects, automatic Sandmaster board spice, and Rihani inspection plus committed exchange. Preserve private views, physical custody, saved winner cleanup and captive/Suk sequencing. Remaining normal bands, missing skill families and combined modules remain gated.

Validation: 45 focused tests; typecheck, lint, 4465 offline tests, production build and 43 HTTP tests passed. Independent rules/privacy/persistence review completed. New isolated browser setup exercised inspection, draw, pending refresh, exchange and completed refresh. Two genuine four-profile Basic/Advanced samples completed without rejected candidates but did not trigger Rihani.

Preserved all 393 opening rooms and original seat/recovery rows (427 rooms after QA/HTTP). SQLite integrity and foreign keys passed. Server remained healthy after the reported outage; no restart or reset.

Verified source tree: c64ce9537269de94c0d785bc08b17346e826c5797352a636771e1dc6ce5e4859
2026-09-13 19:45:29 +10:00

27 lines
1.4 KiB
TypeScript

import { gameTerritories, splitLocation, type MobileBoard } from './board';
import { fighterCount } from './advisors';
import { isDiscoveryLocationId, JACURUTU_SIETCH } from './discoveries';
import type { ForcePresence } from './force-presence';
import type { FactionId } from './catalog';
/** Occupation is physical public presence, independent of victory control or storm. */
export function leaderSkillStrongholdCount(
board: MobileBoard,
player: ForcePresence & { faction: FactionId; advisors?: Record<string, { lockedTurn?: number }> },
): number {
return gameTerritories(board).filter((t) => t.type === 'stronghold' &&
(!isDiscoveryLocationId(t.id) || t.id === JACURUTU_SIETCH) &&
fighterCount(player, t.id) > 0).length;
}
/** The card adds board spice to an existing pile; it grants no direct faction income. */
export function sandmasterVictorySpice(territory: string, spice: Readonly<Record<string, number>>) {
const piles = Object.entries(spice).filter(([key, amount]) => amount > 0 && splitLocation(key).territory === territory);
if (piles.length > 1) throw new Error('Sandmaster placement among multiple spice piles awaits its sector ruling.');
if (!piles.length) return null;
const [key, before] = piles[0];
if (!Number.isSafeInteger(before) || before < 1 || !Number.isSafeInteger(before + 3))
throw new Error('Sandmaster requires a valid existing spice pile.');
return { key, before, after: before + 3 };
}