Announce eligible Basic leaderless battles before plans
Verify and publish checkpoint container / container (push) Successful in 10m34s

This commit is contained in:
Quintine committed 2026-10-29 06:59:11 +11:00
1 parent 2bac45b294
commit 685bd8bdfc
8 files changed
+417 -11

No files matched your search

+32 -1
View File
@@ -985,6 +985,9 @@ export type Battle = {
plans: Record<string, Plan>;
/** Basic declarations recorded only when a new leaderless plan is sealed. */
basicLeaderlessAnnouncements?: string[];
/** Fresh clean Basic battles announce a bounded no-revival case before plans. */
basicEarlyLeaderlessVersion?: 1;
basicEarlyLeaderless?: string[];
revealed: boolean;
traitorCalls: Record<string, boolean>;
traitorDecisionPromises?: TraitorDecisionPromise[];
@@ -42679,6 +42682,25 @@ function applyActionWithInitiator(state: Game, id: string, action: Action, initi
charityOrderIntegrity(g);
return g;
}
/** The published inability is safe before a plan only when no leader can be
* revived and this clean Basic roster cannot privately transfer a Cheap Hero.
* Other cases retain the original seal-time announcement. */
function announceEarlyBasicLeaderless(g: Game) {
const b = g.battle;
if (!b || b.basicEarlyLeaderlessVersion !== 1 || g.status !== 'playing' || g.advanced ||
g.phase !== 6 || b.revealed || !b.prepared || b.preparation || b.preLeader && !b.preLeader.closed ||
g.response || g.decision || g.truthtrance || g.phaseOpening || g.pendingKarama ||
g.pendingKull || g.pendingTreacheryDiscard || g.pendingNullentropy) return;
for (const id of [b.attacker, b.defender]) {
if (b.plans[id] || b.basicEarlyLeaderless?.includes(id)) continue;
const p = getPlayer(g, id), leaders = controlledLeaders(g, p);
if (!leaders.length || leaders.some(l => l.dead || l.capturedBy || !l.usedAt || l.usedAt === b.territory) ||
p.hand.some(c => c.kind === 'hero' && !(b.voice?.target === id && !b.voice.must &&
playedVoiceMatch(c, 'leader', b.voice.kind)))) continue;
(b.basicEarlyLeaderless ??= []).push(id);
log(g, `${p.name} has no available leader or Cheap Hero for this battle.`);
}
}
function finishActionContinuations(g: Game) {
if (g.victoryContinuation) return;
if (g.pendingNexusHarkonnenBetrayal) return;
@@ -42751,6 +42773,7 @@ function finishActionContinuations(g: Game) {
!g.decision && !g.response && !g.truthtrance && !g.phaseOpening &&
!g.pendingKarama && !g.pendingTerrorEntry && !g.pendingAmbassador)
finishMoritaniPlacement(g);
announceEarlyBasicLeaderless(g);
}
function settleAutomaticContinuations(g: Game) {
if (arrivalOrderWaiting(g)) return;
@@ -46984,6 +47007,11 @@ function applyActionInner(
...choice,
chooserEvent: battleChooserEvent(g.turn, battleOrderAfterBattle(g), g.battleOrder?.uses.length ?? 0),
event: battleEvent,
...(!g.advanced && g.expansions.length === 0 && !g.ecazTreachery && !g.techTokens &&
!g.strongholdCards && !g.leaderSkills && !g.homeworlds && !g.nexusCards && !g.discoveryEnabled &&
!g.sandtrout && !g.coalitionState && g.players.every(seat =>
['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'].includes(seat.faction))
? { basicEarlyLeaderlessVersion: 1 as const } : {}),
stoneAllocationVersion: 1,
...(g.leaderSkills ? { smugglerCollectionVersion: 1 as const } : {}),
...(g.leaderSkills && mentatQuestionModeSupported(g) ? { mentatQuestionVersion: 1 as const } : {}),
@@ -47151,7 +47179,8 @@ function applyActionInner(
b.plans[id] = validatePlan(g, p, action);
if (b.plans[id].leader === null) {
if (!g.advanced) (b.basicLeaderlessAnnouncements ??= []).push(id);
log(g, `${p.name} announced a Battle Plan without a leader or Cheap Hero; no battle cards can be played.`);
if (!b.basicEarlyLeaderless?.includes(id))
log(g, `${p.name} announced a Battle Plan without a leader or Cheap Hero; no battle cards can be played.`);
}
if (b.fullPlan?.target === id) {
recordFullPlanInspection(g, b.fullPlan.owner);
@@ -49031,6 +49060,8 @@ export function viewGame(state: Game, id: string, now?: number) {
})
: [],
submitted: Object.keys(b.plans),
...(b.basicEarlyLeaderless?.some(playerId => !b.plans[playerId])
? { earlyLeaderless: b.basicEarlyLeaderless.filter(playerId => !b.plans[playerId]) } : {}),
leaderless: g.advanced
? Object.keys(b.plans).filter(playerId => b.plans[playerId].leader === null)
: (b.basicLeaderlessAnnouncements ?? []).filter(playerId => b.plans[playerId]?.leader === null),