import { coalitionOf, CoalitionAllianceError, type CoalitionState } from './coalition-alliance'; /** Only public roster and rule-profile data are required to select a side. * Inspection answers and sealed plans are deliberately absent. */ export type CoalitionBattlePreparationContext = { status: string; players: readonly { id: string; faction: string; ally?: string | null; allySinceTurn?: number }[]; coalitionState?: CoalitionState; advanced?: boolean; expansions?: readonly unknown[]; homeworlds?: unknown; nexusCards?: unknown; leaderSkills?: unknown; techTokens?: unknown; strongholdCards?: unknown; ecazTreachery?: unknown; discoveryEnabled?: unknown; }; const classicFactions = new Set(['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen']); /** Select the owner or a single allied combatant for native Voice or * Prescience. The unexposed group extension supports bare Basic and Advanced * classic profiles. Ambiguous group battles fail * closed. `requireMutualPair` preserves the stricter legacy Truthtrance quote; * normal battle preparation historically accepted the owner's scalar ally. */ export function coalitionBattleBeneficiary( game: CoalitionBattlePreparationContext, ownerId: string, attacker: string, defender: string, requireMutualPair = false, ): string | null { if (!Array.isArray(game.players) || !ownerId || !attacker || !defender || attacker === defender) return null; const owner = game.players.find(player => player.id === ownerId); if (!owner || !game.players.some(player => player.id === attacker) || !game.players.some(player => player.id === defender)) return null; const combatants = [attacker, defender]; if (game.coalitionState && game.status === 'playing' && typeof game.advanced === 'boolean' && Array.isArray(game.expansions) && game.expansions.length === 0 && !game.homeworlds && !game.nexusCards && !game.leaderSkills && !game.techTokens && !game.strongholdCards && !game.ecazTreachery && !game.discoveryEnabled && game.players.every(player => classicFactions.has(player.faction))) { try { const seats = game.players.map(player => ({id: player.id, ally: player.ally ?? null, ...(player.allySinceTurn !== undefined ? {allySinceTurn: player.allySinceTurn} : {})})); const group = coalitionOf(seats, game.coalitionState, ownerId); if (group && group.members.length >= 3) { const members = combatants.filter(player => group.members.includes(player)); return members.length === 1 ? members[0] : null; } } catch (error) { if (error instanceof CoalitionAllianceError) return null; throw error; } } if (combatants.includes(owner.id)) return owner.id; if (!owner.ally || !combatants.includes(owner.ally)) return null; if (requireMutualPair && !game.players.some(player => player.id === owner.ally && player.ally === owner.id)) return null; return owner.ally; }