Files
electric-cherry/src/lib/beta-evidence.ts
T

63 lines
3.9 KiB
TypeScript

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<string, unknown>;
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('_', ' ');
}