import type { RicheseAuction } from './richese-auction'; type DistransAuctionContext = { phase: number; auction: { cards: readonly unknown[]; index: number; bid: number; bidder: string | null; passed: readonly string[]; allyPayment?: number; } | null; richeseAuction?: RicheseAuction | null; richeseFunding?: Record | null; currentAuctionSale?: unknown; }; const amount = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; const ids = (value: unknown): value is string[] => Array.isArray(value) && value.every(id => typeof id === 'string' && id.length > 0) && new Set(value).size === value.length; const record = (value: unknown): value is Record => !!value && typeof value === 'object' && !Array.isArray(value); function validSpecial(s: RicheseAuction, funding: Record | null | undefined): boolean { if (!(s.method === 'onceAround' || s.method === 'silent' || s.method === 'normal') || !(s.source === 'cache' || s.source === 'blackMarket') || (s.source === 'cache' && s.method === 'normal') || typeof s.event !== 'string' || !s.event || typeof s.cardId !== 'string' || !s.cardId || typeof s.owner !== 'string' || !s.owner || s.outcome !== null || !ids(s.eligible) || !s.eligible.length || !ids(s.order) || !ids(s.tieOrder) || s.order.length !== s.eligible.length || s.tieOrder.length !== s.eligible.length || !s.order.every(id => s.eligible.includes(id)) || !s.tieOrder.every(id => s.eligible.includes(id)) || !ids(s.acted) || !ids(s.passed) || !s.acted.every(id => s.eligible.includes(id)) || !s.passed.every(id => s.eligible.includes(id)) || !amount(s.bid) || (s.bid === 0 ? s.bidder !== null : !s.bidder || !s.eligible.includes(s.bidder)) || !record(s.sealed) || (funding != null && !record(funding))) return false; if (funding && !Object.entries(funding).every(([id, entry]) => { if (!s.eligible.includes(id) || !record(entry) || !amount(entry.amount) || !amount(entry.allyPayment) || entry.allyPayment > entry.amount) return false; return entry.donor === null || (typeof entry.donor === 'string' && entry.donor.length > 0); })) return false; if (s.method === 'silent') { const sealed = Object.keys(s.sealed); return s.active === null && s.bid === 0 && s.bidder === null && s.passed.length === 0 && sealed.length === s.acted.length && sealed.every(id => s.acted.includes(id) && amount(s.sealed[id])); } if (Object.keys(s.sealed).length) return false; if (s.method === 'onceAround') { const remaining = s.order.filter(id => !s.acted.includes(id)); return remaining.length > 0 && s.active === remaining[0] && (s.bidder !== null || remaining.some(id => id !== s.owner)) && s.passed.every(id => s.acted.includes(id)) && (!s.bidder || (s.acted.includes(s.bidder) && !s.passed.includes(s.bidder))); } if (s.acted.length || !s.active || !s.eligible.includes(s.active) || s.passed.includes(s.active) || s.active === s.bidder || (s.bidder !== null && s.passed.includes(s.bidder))) return false; const previous = s.passed.at(-1) ?? s.bidder; if (!previous) return s.active === s.order[0]; const start = s.order.indexOf(previous); const next = s.order.slice(start + 1).concat(s.order.slice(0, start + 1)) .find(id => id !== s.bidder && !s.passed.includes(id)); return s.active === next; } /** Provisional interpretation: a completed bid or pass ends the interval * during which Distrans is forbidden. Preserve the existing open lot and all * accepted bid reservations; transfers must still satisfy their normal rules. */ export function distransAuctionBlock(g: DistransAuctionContext): string | null { if (g.currentAuctionSale || (!g.auction && !g.richeseAuction)) return null; if (g.phase === 3 && !g.auction && g.richeseAuction && validSpecial(g.richeseAuction, g.richeseFunding)) return null; const lot = g.auction; if (g.phase === 3 && !g.richeseAuction && lot && Array.isArray(lot.cards) && Number.isSafeInteger(lot.index) && lot.index >= 0 && lot.index < lot.cards.length && !!lot.cards[lot.index] && amount(lot.bid) && (lot.bid === 0 ? lot.bidder === null : typeof lot.bidder === 'string' && !!lot.bidder) && ids(lot.passed) && (!lot.bidder || !lot.passed.includes(lot.bidder)) && amount(lot.allyPayment ?? 0) && (lot.allyPayment ?? 0) <= lot.bid) return null; return 'Distrans is unavailable during this unresolved auction state; finish the current bid or auction first.'; }