Support multi-game betas with automatic server selection

This commit is contained in:
2026-10-07 09:41:17 -04:00
parent a6dee8c5e2
commit c6352d86b4
17 changed files with 881 additions and 173 deletions
+7 -2
View File
@@ -2,7 +2,7 @@
import { onMount, tick } from 'svelte';
import { builtInPlans, requirement, requirementKinds, requirementLabels, parseBetaPlan, betaPlanIssues, type BetaPlan, type RequirementKind } from '$lib/beta-plan.js';
import { coverageSummary, needsGameTelemetry, presetDescriptions, requirementCategory, requirementSummary } from '$lib/beta-plan-summary.js';
let { plan = $bindable<BetaPlan>(), serverCount }: { plan: BetaPlan; serverCount?: number } = $props();
let { plan = $bindable<BetaPlan>(), serverCount, allServersRequired = false }: { plan: BetaPlan; serverCount?: number; allServersRequired?: boolean } = $props();
let validationRequested = $state(false), coverageOpen = $state(false), deadlineOpen = $state(false);
const issues = $derived(validationRequested ? betaPlanIssues(plan, serverCount) : []);
const fieldError = (field: string) => issues.find(issue => issue.field === field)?.message;
@@ -47,6 +47,7 @@
try {
// Validation returns an independent plain object, even when the source is a Svelte proxy.
plan = parseBetaPlan(preset.plan);
if (allServersRequired) plan.scope = 'all';
appliedPresetName = name;
choosingPreset = false;
editingId = ''; error = '';
@@ -178,15 +179,19 @@
<summary><span>Coverage &amp; waves<small>{coverageSummary(plan)} · {waveSummary}</small></span><span class="edit-hint">Configure</span></summary>
<div class="policy-body">
<div class="fields">
{#if allServersRequired}
<p>Every requested instance must pass. Change the instance counts in the Servers step.</p>
{:else}
<label>Qualifying servers<select bind:value={plan.scope}>
<option value="all">Every selected server</option><option value="quorum">At least N selected servers</option>
<option value="map">At least one per selected map</option><option value="game">At least one per selected game</option>
<option value="map-game">At least one per selected game/map combination</option>
</select></label>
{#if plan.scope === 'quorum'}<label>Required servers<input id="plan-quorum" aria-invalid={!!fieldError('quorum')} aria-describedby="error-quorum" type="number" min="1" max={serverCount ?? 1000} step="1" bind:value={plan.quorum} required /><span class="error" id="error-quorum">{fieldError('quorum') ?? `${serverCount ?? 0} servers selected`}</span></label>{/if}
{/if}
<label>Servers per wave (0 = all at once)<input id="plan-waveSize" aria-invalid={!!fieldError('waveSize')} aria-describedby="error-waveSize" type="number" min="0" step="1" bind:value={plan.waveSize} required /><span class="error" id="error-waveSize">{fieldError('waveSize') ?? ''}</span></label>
</div>
<p>Each wave must fully pass before files reach the next wave. Coverage rules apply after all selected servers have started testing. Restarts always wait for empty servers.</p>
<p>Each wave must fully pass before files reach the next wave. Coverage rules apply after all selected servers have started testing, and every file must pass on at least one matching instance. Restarts always wait for empty servers.</p>
</div>
</details>
<details class="policy" bind:open={deadlineOpen}>