import type { BetaRequirement, BetaTestView } from './beta-plan.js'; import { needsGameTelemetry } from './beta-plan-summary.js'; export function betaWaitingCondition(requirement: BetaRequirement, observation?: BetaTestView['servers'][number]['observation']): string { if (requirement.kind === 'manual') return 'Waiting for a tester to record their name and observation.'; if (requirement.kind === 'recovery') return 'The other checks must pass before the next restart when empty; each cycle needs fresh evidence.'; if (requirement.kind === 'healthy-minutes') return observation?.healthy === true ? 'Collecting consecutive fresh healthy observations; an outage or monitoring gap resets this duration.' : 'Waiting for fresh container and RCON health observations. Unhealthy or stale observations reset this duration.'; if (requirement.kind === 'failure-budget') return 'An observed health outage exceeded the budget. Investigate before starting a fresh attempt.'; if (needsGameTelemetry(requirement)) { if (!observation?.lastGameplayAt) return 'No gameplay report received for this attempt. Check the game reporter and Telemetry API requests.'; if (observation.gameEnded) return 'Waiting for the next game and its qualifying reports.'; if (requirement.kind === 'population') return `Waiting for a game report containing at least ${requirement.target} players.`; const players = `at least ${requirement.minPlayers} player${requirement.minPlayers === 1 ? '' : 's'}`; if ((observation.players ?? 0) < requirement.minPlayers) return `The latest report has ${observation.players ?? 0} players; this check needs ${players}.`; if (requirement.kind === 'matches') return requirement.minMinutes && !observation.matchStarted ? 'This match has no game-start report, so its duration cannot qualify. Waiting for a new game start and game end.' : `Waiting for game end${requirement.minRound ? ` after reaching round ${requirement.minRound}` : ''} with ${players}${requirement.minMinutes ? ` and at least ${requirement.minMinutes} minutes since game start` : ''}.`; if (requirement.kind === 'round') return `Waiting for a round change, heartbeat, or game end reporting round ${requirement.target} or higher with ${players}.`; return `Time is credited when the next report arrives, with ${players} at both ends of the interval. It does not advance continuously between reports.`; } return 'Waiting for qualifying evidence.'; } export function betaEvidenceSummary(event: BetaTestView['evidence'][number]): string { let payload: Record; try { payload = JSON.parse(event.payload); } catch { return event.kind.replaceAll('-', ' ').replaceAll('_', ' '); } if (!payload || typeof payload !== 'object') return event.kind; if (['game_start', 'round_change', 'heartbeat', 'game_end'].includes(event.kind)) { const label = event.kind === 'game_start' ? 'Game started' : event.kind === 'game_end' ? 'Game ended' : event.kind === 'heartbeat' ? 'Heartbeat' : 'Round changed'; return `${label}${typeof payload.round === 'number' ? ` at round ${payload.round}` : ''}${Array.isArray(payload.players) ? ` with ${payload.players.length} players` : ''}${typeof payload.map === 'string' ? ` on ${payload.map}` : ''}.`; } if (event.kind === 'manual-sign-off') return `${payload.requirement ?? 'Checklist'}: ${payload.note ?? 'Signed off'}`; if (event.kind === 'health' || event.kind === 'status-sample') return `${payload.healthy ? 'Healthy observation' : 'Unhealthy observation'}${payload.reason ? `: ${payload.reason}` : '.'}`; return event.kind.replaceAll('-', ' ').replaceAll('_', ' '); }