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
@@ -29,6 +29,7 @@ import {
|
||||
import { matchingTraitor } from './traitors';
|
||||
import { auditCount } from './choam-auditor';
|
||||
import { sukGraduateSkill, type SukGraduateSkill } from './suk-graduate';
|
||||
import { validateSpiceBankerSpend } from './spice-banker';
|
||||
import { rihaniVictorySkill, type RihaniSkill } from './rihani-decipherer';
|
||||
import {
|
||||
quoteHomeworldBattleRules,
|
||||
@@ -53,6 +54,7 @@ export class BattleResolutionQuoteError extends Error {
|
||||
}
|
||||
}
|
||||
export type ResolutionPlan = {
|
||||
bankerSpice?: number;
|
||||
dial: number;
|
||||
support: number;
|
||||
allyPayment?: number;
|
||||
@@ -136,6 +138,7 @@ export type BattleResolutionQuote = {
|
||||
effects: ReturnType<typeof strongholdBattleEffects> | null;
|
||||
explosion: boolean;
|
||||
payments: BattleSupportPayment[];
|
||||
spiceBankerPayments?: { player: string; amount: number; waivedByTraitor: boolean }[];
|
||||
choamIncome: { owner: string; amount: number } | null;
|
||||
bounty: { player: string; amount: number } | null;
|
||||
strongholdIncome: { player: string; amount: number }[];
|
||||
@@ -197,6 +200,12 @@ function validateCombatant(
|
||||
whole(p.allyPayment ?? 0),
|
||||
'The revealed battle needs valid force, dial and payment quantities.',
|
||||
);
|
||||
try {
|
||||
validateSpiceBankerSpend(side.leaderSkills ?? [], side.leader && !side.leader.dead ? side.leader.id : undefined,
|
||||
p.bankerSpice, side.spice, strongholdSupportCost(side.stronghold, p.support) - (p.allyPayment ?? 0));
|
||||
} catch (error) {
|
||||
throw new BattleResolutionQuoteError(error instanceof Error ? error.message : 'Invalid Spice Banker commitment.');
|
||||
}
|
||||
const selected = [p.weapon, p.defense, p.leader, side.lateDefense].filter(
|
||||
(value): value is string => value != null,
|
||||
);
|
||||
@@ -406,6 +415,10 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
(v) => v.called && !a.plan.kwisatz && v.beneficiary === d.id,
|
||||
);
|
||||
const payments: BattleSupportPayment[] = [];
|
||||
const spiceBankerPayments = [a, d].filter(side => (side.plan.bankerSpice ?? 0) > 0).map(side => {
|
||||
const waivedByTraitor = side === a ? ac && !dc : dc && !ac;
|
||||
return { player: side.id, amount: waivedByTraitor ? 0 : side.plan.bankerSpice!, waivedByTraitor };
|
||||
});
|
||||
const credits = new Map<string, number>();
|
||||
if (input.advanced)
|
||||
for (const [side, free] of [
|
||||
@@ -524,6 +537,7 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
weapon: aw,
|
||||
defense: ad,
|
||||
skilledLeaderSurvives: !deaths.attacker,
|
||||
bankerSpice: a.plan.bankerSpice,
|
||||
}),
|
||||
defender: leaderSkillBattleBonus({
|
||||
assignments: d.leaderSkills ?? [],
|
||||
@@ -531,6 +545,7 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
weapon: dw,
|
||||
defense: dd,
|
||||
skilledLeaderSurvives: !deaths.defender,
|
||||
bankerSpice: d.plan.bankerSpice,
|
||||
}),
|
||||
};
|
||||
const attackerPenalty = bureaucratBattlePenalty(d.leaderSkills ?? [], d.leader?.id,
|
||||
@@ -759,6 +774,7 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
effects,
|
||||
explosion,
|
||||
payments,
|
||||
...(spiceBankerPayments.length ? { spiceBankerPayments } : {}),
|
||||
choamIncome:
|
||||
choam && choamAmount > 0
|
||||
? { owner: choam.id, amount: choamAmount }
|
||||
|
||||
Reference in new issue
Block a user