7.8 KiB
Beta test plans
New betas use a saved plan rather than the two legacy wait flags. Existing
betas and API callers without testPlan retain their original workflow.
Schema additions are additive and run at startup; no active beta needs to be
closed to install this feature.
Requirements
Use Add requirement in Betas. Requirements on a qualifying server are ANDed:
| Requirement | Evidence |
|---|---|
| Reach round X | Session telemetry reports round >= X with the required player count |
| Complete qualifying matches | Normal completed matches, optionally qualified by round, players and duration |
| Populated minutes | Fresh RCON samples at both ends of a populated interval |
| Player-hours | Populated minutes × the lower sampled player count / 60 |
| Reach player count | Fresh healthy RCON sample |
| Sustained population | Consecutive fresh healthy samples above the player threshold |
| Continuous healthy minutes | Consecutive fresh container/RCON observations |
| Maximum health outages | Transitions from observed healthy to unhealthy |
| Maximum unexpected restarts | Distinct reported process boots within the same test run |
| Maximum script errors | New console lines matching script errors/exceptions, unhandled exceptions or fatal errors |
| Successful recovery cycles | Repeat the other requirements after each additional empty-server restart |
| Feature event count | Named, session-scoped game events |
| Tester checklist | Tester name and observation, entered on the beta detail page |
There is one round requirement: reach round X. For repeated high-round runs, use a qualifying-match count with a minimum round. The minimum player count must be present at the qualifying round observation in the same match. A match with many players and a different solo high-round match cannot combine to pass.
Unqualified match counts use incremental game logs (InitGame, a player join,
then ExitLevel: executed). Round-qualified or multi-player-qualified matches
use session telemetry. Each requirement uses one source, preventing duplicate
counts. Match duration is measured from the observed join/session start. Log
polling can undercount duration; it does not infer unobserved elapsed time.
Round, feature and unexpected-restart checks require the reporting protocol below. Legacy round reports still update Fleet but cannot prove beta progress. The game scripts themselves are uploaded artifacts, not source maintained in this repository; update their reporter to emit the protocol before selecting these requirements in production.
Coverage, rollout and outcomes
Coverage can require all selected servers, a quorum, one per selected map, one per selected game, or one per selected game/map combination. Coverage metadata is pinned when the beta opens. Register game/map metadata first.
Set wave size to 1 for a canary, a larger number for batches, or 0 for all selected servers. Later waves reserve their artifact targets but receive no files until every server in the prior waves has passed. All waves must start testing before the final coverage rule can pass. Error-budget violations on any tested server block promotion, including with quorum coverage.
The test window starts when the beta opens, including time waiting for empty servers. Missing evidence at the deadline produces inconclusive, not a failure or an automatic rollback. Health and log monitoring continue after a pass until promotion; a deteriorating result revokes readiness.
For failures, select hold as failed, pause for investigation, or automatic rollback. Rollback restores only files touched by the beta. It can queue rolling restarts when servers empty, or leave restarts manual. Manual rollback uses the same selected restart policy. No beta auto-promotes.
Pause holds evidence evaluation and rollout. Resume retains evidence and the original deadline. A fresh attempt retires old evidence and restarts deployed servers when empty; it does not extend the deadline. Expired or incomplete-file deployments should be rolled back and reopened. Recovery cycles similarly require fresh milestones/checklists, while carrying error budgets forward.
Time accounting excludes intervals spanning monitoring gaps longer than 60 seconds. A missing first sample earns no time. A log that cannot be read cannot pass a clean-log requirement. Log backlog above 2 MB makes the plan inconclusive instead of silently dropping evidence. Partial lines, rotations, byte cursors, attempts and accepted events persist in SQLite. The detail page shows the most recent 200 evidence entries; older entries remain in the database.
Game reporter protocol
On game-process startup, fetch:
GET /api/telemetry?server=t5_zombie_theater_1
The response contains runs: [{ runId, betaId }]. Cache that list for the process
lifetime, generate a unique bootId, and send each event once to every returned
run. A run ID is an evidence correlation token, not an authentication credential.
Do not refresh it during a match or relabel queued events with a newer run ID.
Each beta-controlled restart creates a new run ID before requesting the restart.
An unexpected process restart fetches the current run ID and uses a new boot ID.
POST JSON to /api/telemetry:
{
"event": "round_change",
"server": "t5_zombie_theater_1",
"runId": "the-run-id-from-startup",
"eventId": "boot-uuid:session-uuid:12",
"bootId": "boot-uuid",
"sessionId": "session-uuid",
"sequence": 12,
"round": 25,
"players": 4
}
Send events in order:
boot: once per process, before all session events.sessionIdcan be omitted. Sendsequence,roundandplayersas 0.session_start: a new unique session ID, sequence 0, current players/round. Send when actual gameplay begins, not when an idle map loads.round_change: strictly increasing sequence within that session, reported round and player count. Emit on entry to each round.feature: same sequence ordering, current players/round, plusnamesuch asperk.purchased. Report one event per occurrence.match_end: normal completion only, next sequence, final players/round. Crashes, forced shutdowns and idle map rotations must not emit this event.
IDs accept letters, numbers, _, ., :, and - (up to 128 characters).
Feature names are limited to 120 characters. Numbers must be nonnegative safe
integers. Retries reuse the same event ID and payload. Send sequentially: an
older sequence arriving late is rejected, not retroactively credited.
Responses include test: { accepted, reason? }. Duplicates return HTTP 200 with
accepted: false; malformed events return 400. Events for retired runs, unknown
boots/sessions, already-ended sessions, or paused/closed tests are not credited.
The existing unauthenticated/trusted-network deployment model is unchanged.
Legacy payloads without runId continue to support round_change for Fleet
display. They do not alter RCON restart safety data or test evidence.
API and storage
POST /api/betas accepts testPlan, validated by parseBetaPlan in
src/lib/beta-plan.ts. New plans always verify restart recovery before testing.
GET /api/betas/:id includes the plan, outcome, per-server requirement progress
and recent evidence under test.
POST /api/betas/:id/test accepts actions pause, resume, retry, or
sign-off (with server, requirementId, and note).
GET/POST /api/betas/presets lists/saves reusable plans. Saved presets are copied
into a beta; later preset edits never change an active test.
beta_test_plans, beta_test_coverage, beta_test_runs, beta_test_evidence,
and beta_test_presets hold configuration and results. beta_file_changes
records which files were touched, including partially completed deployments.
Legacy beta lifecycle states remain compatible with artifact holds and history;
test outcomes are stored separately. Holds last until promotion or rollback.