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
27 lines
1.4 KiB
TypeScript
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 };
|
|
}
|