Support cohort-eligible Ambassador leader and KH repeats

Replace lifetime first-death caps with the selected provisional composition: first returns remain immediate, while later native leaders and KH must satisfy their existing ordinary death/cohort conditions. Extract one phase/slot/price-independent cohort quote for ordinary Revival and the independent Ambassador. Preserve ordinary timing, payment, prevention, native early service and one-leader controls.

Snapshot an opened cohort before a free return makes a leader alive, preserving remaining ordinary returns. Keep Ecaz identity tracking and the shared Duke permission separate. KH retains activation/loss history and five native discs; update its marker above the actual opened owner cycle and old marker. Independent grants retain ordinary allowance and income policies. Explain eligibility in persistent controls and source-labelled reference.

Verify related rules/controls, types/lint/build, controlled native/copied free-return-to-next-ordinary-Revival programmes layered on original public Basic paid-entry sources, controlled Advanced KH actual entry/return, income allow/cancel and twelve minimal legal policy continuations. Assigned repeat/cohort facts are explicitly controlled, not claimed original battle histories. Full modes, wider custody/module combinations, comprehensive assurance and deployed acceptance remain unfinished. No strategic calibration, saved-room operation, reset, restart or deployment.
This commit is contained in:
Quintine committed 2026-10-24 13:43:11 +11:00
1 parent 16f654071a
commit 152039be5b
12 files changed
+719 -51

No files matched your search

+44 -20
View File
@@ -5,7 +5,7 @@ import { isAuditorLeader } from './choam-auditor';
import { homeworldLowBonus } from './homeworld-benefits';
import { recruitsRevivalAllowance } from './recruits';
import { DUKE_VIDAL_ID } from './duke-vidal';
import { ecazDukeRevivalBlock, quoteEcazDukeRevival } from './ecaz-duke-revival';
import { ecazDukeRevivalBlock, quoteEcazDukeRevival, type EcazDukeRevivalContext } from './ecaz-duke-revival';
import {
quoteEcazRevivalCohort, completeEcazRevivalCohortUse,
recordEcazRevivalOpening, recordEcazFirstExecution,
@@ -164,8 +164,45 @@ export function normalRevivalCycle(
return cycle;
}
/** Ordinary Ecaz cohorts keep native identity separate from independent Duke deaths.
* Only the absent-Tleilaxu card quote may waive the unopened first cohort. */
export type LeaderRevivalCohortContext = EcazDukeRevivalContext;
export type LeaderRevivalCohortPlayer = Pick<
Player, 'id' | 'faction' | 'leaders' | 'revivalCycle' | 'ecazRevivalCohort'
>;
export type LeaderRevivalCohortQuote = {
cycle: number;
leaders: readonly string[];
dukeAvailable: boolean;
dukeBlocked: string | null;
};
/** Ordinary death/cohort permission only, independent of phase, allowance,
* payment and prevention. The native Tleilaxu early service is separate.
* Ecaz keeps native identity separate from independent Duke deaths; only the
* absent-Tleilaxu card quote may waive the unopened first cohort. */
export function quoteLeaderRevivalCohort(
g: LeaderRevivalCohortContext,
p: LeaderRevivalCohortPlayer,
waiveOpening = false,
): LeaderRevivalCohortQuote {
const shared = p.faction === 'ecaz' && g.dukeVidal;
const dukeContext = shared && g.status === 'playing' ? g : null;
const dukeBlocked = dukeContext ? ecazDukeRevivalBlock(dukeContext, p.id) : null;
const dukeAvailable = !!dukeContext && !dukeBlocked;
const cohort = shared ? quoteEcazRevivalCohort(p, dukeAvailable, waiveOpening) : null;
let cycle = cohort?.cycle ?? normalRevivalCycle(p);
if (waiveOpening && !cohort) cycle = Math.max(1, cycle);
const leaders = p.leaders.flatMap((leader) => {
if (!leader.dead || leader.capturedBy || leader.gholaBy) return [];
// Supplied Advanced p29 E permits Auditor revival each turn regardless
// of the ordinary five-disc cohort, including its repeated deaths.
const auditorReturn = g.advanced && p.faction === 'choam' && isAuditorLeader(leader);
return auditorReturn || (cycle > 0 && (cohort
? !cohort.returned.includes(leader.id) : leader.deaths === cycle))
? [leader.id] : [];
});
return { cycle, leaders, dukeAvailable, dukeBlocked };
}
export function leaderRevivalOptions(
g: Pick<
Game,
@@ -192,27 +229,14 @@ export function leaderRevivalOptions(
waiveOpening = false,
) {
const shared = p.faction === 'ecaz' && g.dukeVidal;
const dukeContext = shared && g.status === 'playing' ? g : null;
const dukeBlocked = dukeContext ? ecazDukeRevivalBlock(dukeContext, p.id) : null;
const dukeAvailable = !!dukeContext && !dukeBlocked;
const cohort = shared ? quoteEcazRevivalCohort(p, dukeAvailable, waiveOpening) : null;
let cycle = cohort?.cycle ?? normalRevivalCycle(p);
if (waiveOpening && !cohort) cycle = Math.max(1, cycle);
const { cycle, leaders: cohortLeaders, dukeAvailable, dukeBlocked } =
quoteLeaderRevivalCohort(g, p, waiveOpening);
const enabled = g.phase === 4 && !revivalPrevented(g, p.id);
const discount = revivalDiscount(g, p);
const leaders = p.leaders.flatMap((leader) => {
if (!enabled || !leader.dead || leader.capturedBy || leader.gholaBy)
return [];
// Supplied Advanced p29 E permits Auditor revival each turn regardless
// of the other leaders in Tanks; retain the ordinary one-leader allowance.
const auditorReturn =
g.advanced &&
p.faction === 'choam' &&
isAuditorLeader(leader);
const normal =
!p.leaderRevived &&
(auditorReturn || (cycle > 0 && (cohort
? !cohort.returned.includes(leader.id) : leader.deaths === cycle)));
const normal = !p.leaderRevived && cohortLeaders.includes(leader.id);
if (
!normal &&
(p.faction !== 'tleilaxu' ||
@@ -232,7 +256,7 @@ export function leaderRevivalOptions(
];
});
if (dukeAvailable && enabled && !p.leaderRevived) {
const quote = quoteEcazDukeRevival(dukeContext!, p.id, !!discount);
const quote = quoteEcazDukeRevival(g, p.id, !!discount);
leaders.push({
id: DUKE_VIDAL_ID,
name: quote.duke.leader.name,