New beta | Electric Cherry
{#if flash}{/if} {#if $fleetError}{/if} {#if notice}

{notice}

{/if} {#if loading} {:else if availableDraft}

Resume unfinished beta?

Saved {$timeFormat.dateTime(availableDraft.savedAt)} · {availableDraft.picks.filter( (p) => p.scriptId !== null ).length} files · {availableDraft.selectionMode === 'automatic' ? 'Automatic server selection' : `${availableDraft.servers.length} selected servers`}. Versions and eligibility will be rechecked.

{:else if !eligibleFiles.length && !flash} {:else if scripts.length}
    {#each steps as label, index}
  1. {index + 1}{label}
  2. {/each}
{#if storageUnavailable}Draft saving is unavailable in this browser. Keep this page open to preserve your work.{:else}Unfinished changes save in this browser.{/if}{#if dirty}{/if}

{steps[step]}

{#if step === 0}
{#each picks as pick, index (pick.key)}
{#if pick.scriptId !== null}{@const file = detailById[pick.scriptId]?.script}{#if file}

{scopeText(file)} · {scopeCount(file)} eligible servers · Inspect file and versions{#if !loadingVersions.has(pick.key) && !versionsFor(pick).length}
No unpromoted versions. Upload a new version from the file page.{/if}

{/if}{/if}
{/each}

Combine files for different games in one beta. Each file installs only on servers matching its scope, and every file needs at least one test participant.

{:else if step === 1} {#if selectionMode === 'automatic'}

Instances that must pass

At launch, Electric Cherry chooses compatible servers with the fewest current players. Every requested instance must pass the test plan.

{applicableGames .map( (game) => `${gameLabel(game)}: ${countLabel(candidates.filter((server) => server.game === game).length, 'compatible registered server')}` ) .join(' · ')}

Servers are selected when you start the beta, using fresh player counts and availability checks. Restarts wait for confirmed empty lobbies.

{:else}

{pickedServers.size} selected · {candidateServers.length} compatible servers. Restarts wait for confirmed empty lobbies.

{/if}
Selected file scopes
    {#each picks as pick}{#if pick.scriptId !== null}{@const file = detailById[pick.scriptId]?.script}{#if file}
  • {file.name}: {scopeText(file)} · {scopeCount(file)} eligible servers
  • {/if}{/if}{/each}
{#if !candidateServers.length}
No registered servers match these files. Review their scopes above or register a matching server.
{/if}
Advanced options · choose specific servers
Server selection
{#if selectionMode === 'manual' && candidateServers.length}
{visibleServers.length} visible
{#each groups as group}
{fleetIdentity(group.servers[0]).map} · {group.servers[0] .game === 't5' ? 'Black Ops I' : 'Black Ops II'}
{#each group.servers as server}{/each}
{/each} {#if !visibleServers.length}

No servers match these filters.

{/if} {/if}
{:else if step === 2} {:else}

Files and exact versions

    {#each picks as pick}
  • {scripts.find((file) => file.id === pick.scriptId) ?.name} · {artifactVersionLabel( versionsFor(pick).find((v) => v.id === pick.versionId) )} · {versionsFor(pick) .find((v) => v.id === pick.versionId) ?.sha256.slice( 0, 12 )}{#if pick.scriptId !== null && detailById[pick.scriptId]}
    {scopeText(detailById[pick.scriptId].script)}{/if}
  • {/each}

Test instances ({serverCount})

{#if selectionMode === 'automatic'}
    {#each Object.entries(automaticCounts) as [game, count]}
  • {gameLabel(game)}: {countLabel(count, 'server')} must pass
  • {/each}

The least-used available servers are chosen when you start the beta. Selection uses current player counts and ensures every file has a matching participant. If there are not enough available servers, the beta will not start.

{:else}
    {#each [...pickedServers] as name}
  • {/each}
{/if}

Pass criteria

{coverageSummary(testPlan)} · {testPlan.timeoutHours}-hour window, starting when this beta opens.

    {#each testPlan.requirements as requirement}
  • {requirementSummary(requirement)}
  • {/each}

Waves and failure handling

{testPlan.waveSize ? `${testPlan.waveSize} servers per wave.` : 'Deploy to all selected servers.'} {testPlan.failureAction === 'rollback' ? 'Failed checks trigger automatic rollback.' : testPlan.failureAction === 'pause' ? 'Failed checks pause testing.' : 'Failed checks hold the beta for review.'} {testPlan.rollbackRestart === 'when-empty' ? 'Rollback restarts wait for empty servers.' : 'Rollback restores files; restart servers manually.'}

Starting installs each selected version only on matching test servers and restarts them when empty. Waiting, pausing, and retrying use the original testing window. Promotion is a separate reviewed action after testing.

{/if}

{forwardReason}

{#if step < 3}{:else}{/if}
{/if}