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
This commit is contained in:
1 parent
af360c836b
commit
06f46c6f79
26 files changed
+1230
-122
No files matched your search
@@ -24,7 +24,7 @@ export type BattleLeaderSkill = {
|
||||
};
|
||||
|
||||
export type AppliedLeaderSkill = {
|
||||
skill: DirectLeaderSkillId | 'planetologist';
|
||||
skill: DirectLeaderSkillId | 'planetologist' | 'mentat';
|
||||
amount: 1 | 2 | 3;
|
||||
mode: 'normal' | 'skilled';
|
||||
};
|
||||
@@ -114,6 +114,30 @@ function isDirectSkill(skill: string): skill is DirectLeaderSkillId {
|
||||
return directSkills.has(skill as LeaderSkillId);
|
||||
}
|
||||
|
||||
/** A captive grants only this actual disc's lower effect, never a normal effect. */
|
||||
export function usesSurvivingSkilledLeader(
|
||||
assignments: readonly BattleLeaderSkill[],
|
||||
skill: LeaderSkillId,
|
||||
leader: string | undefined | null,
|
||||
survives: boolean,
|
||||
): boolean {
|
||||
return survives && !!leader && assignments.some((a) =>
|
||||
a.skill === skill && a.leader === leader && (!a.faceUp || a.captured));
|
||||
}
|
||||
|
||||
/** Bureaucrat changes the opponent's total, not either leader disc or bounty. */
|
||||
export function bureaucratBattlePenalty(
|
||||
assignments: readonly BattleLeaderSkill[],
|
||||
leader: string | undefined | null,
|
||||
survives: boolean,
|
||||
opponentStrongholds: number | undefined,
|
||||
): number {
|
||||
if (!usesSurvivingSkilledLeader(assignments, 'bureaucrat', leader, survives)) return 0;
|
||||
if (opponentStrongholds === undefined || !Number.isSafeInteger(opponentStrongholds) || opponentStrongholds < 0)
|
||||
throw new Error('Bureaucrat needs a valid occupied stronghold count.');
|
||||
return opponentStrongholds;
|
||||
}
|
||||
|
||||
function qualifies(
|
||||
skill: DirectLeaderSkillId,
|
||||
weapon: Card | undefined,
|
||||
@@ -144,6 +168,9 @@ export function leaderSkillBattleBonus(input: {
|
||||
}): LeaderSkillBattleBonus {
|
||||
if (!input.selectedLeader) return { bonus: 0, applied: [] };
|
||||
const applied: AppliedLeaderSkill[] = [];
|
||||
if (input.selectedLeader.kind === 'disc' && usesSurvivingSkilledLeader(
|
||||
input.assignments, 'mentat', input.selectedLeader.id, input.skilledLeaderSurvives,
|
||||
)) applied.push({ skill: 'mentat', amount: 2, mode: 'skilled' });
|
||||
if (
|
||||
input.selectedLeader.kind === 'disc' &&
|
||||
input.skilledLeaderSurvives &&
|
||||
|
||||
Reference in new issue
Block a user