171 lines
7.4 KiB
Markdown
171 lines
7.4 KiB
Markdown
# Beta test plans
|
|
|
|
Create a plan in Betas, choose its servers, and deploy. The server's game script
|
|
reports gameplay to Electric Cherry. The same reports update Fleet and any active
|
|
beta on that server.
|
|
|
|
## Game reporter protocol
|
|
|
|
Send JSON to `POST /api/telemetry` with `Content-Type: application/json`.
|
|
There are exactly three events. Send them whether or not a beta is active.
|
|
|
|
| Event | Fields |
|
|
| --- | --- |
|
|
| `game_start` | `server`, `map`, `players` with each player's `name` and `guid` |
|
|
| `round_change` | `server`, `map`, `round`, `players` with each player's `name`, `guid`, `kills`, `headshots`, `downs`, `revives`, and `score` |
|
|
| `game_end` | `server`, `map`, `round`, `players` with each player's `name`, `guid`, `kills`, `headshots`, `downs`, `revives`, and `score` |
|
|
|
|
`server` is the exact registered Electric Cherry server name. `map` is the
|
|
current map. `players` is the full player array, including an empty array when
|
|
there are no players. Names and GUIDs are strings; keep GUIDs as strings to
|
|
preserve their precision. Round and stat values are nonnegative JSON integers.
|
|
Each GUID must appear only once within a report's player array.
|
|
|
|
### Game start
|
|
|
|
Send when the game starts. Round and stats are not required.
|
|
|
|
```json
|
|
{
|
|
"event": "game_start",
|
|
"server": "t6_zombie_town_1",
|
|
"map": "zm_transit",
|
|
"players": [
|
|
{ "name": "Josh", "guid": "12345678901234567" },
|
|
{ "name": "Alex", "guid": "23456789012345678" }
|
|
]
|
|
}
|
|
```
|
|
|
|
### Round change
|
|
|
|
Send the new round and each player's current stats.
|
|
|
|
```json
|
|
{
|
|
"event": "round_change",
|
|
"server": "t6_zombie_town_1",
|
|
"map": "zm_transit",
|
|
"round": 25,
|
|
"players": [
|
|
{ "name": "Josh", "guid": "12345678901234567", "kills": 420, "headshots": 160, "downs": 1, "revives": 3, "score": 32000 },
|
|
{ "name": "Alex", "guid": "23456789012345678", "kills": 380, "headshots": 140, "downs": 3, "revives": 1, "score": 28000 }
|
|
]
|
|
}
|
|
```
|
|
|
|
### Game end
|
|
|
|
Send the final round and each player's final stats when the game ends.
|
|
|
|
```json
|
|
{
|
|
"event": "game_end",
|
|
"server": "t6_zombie_town_1",
|
|
"map": "zm_transit",
|
|
"round": 26,
|
|
"players": [
|
|
{ "name": "Josh", "guid": "12345678901234567", "kills": 445, "headshots": 170, "downs": 2, "revives": 3, "score": 33500 },
|
|
{ "name": "Alex", "guid": "23456789012345678", "kills": 401, "headshots": 146, "downs": 4, "revives": 1, "score": 29750 }
|
|
]
|
|
}
|
|
```
|
|
|
|
Success returns HTTP 200 with `{ "ok": true, "telemetry": { ... } }`.
|
|
The returned telemetry contains the event and an Electric Cherry `receivedAt`
|
|
timestamp. Invalid JSON or fields return 400, an unknown/destroyed server 404,
|
|
and a non-JSON content type 415.
|
|
|
|
The script sends each event once, in gameplay order, directly to Electric Cherry.
|
|
There is no discovery request, beta token, boot/session ID, sequence number,
|
|
custom feature event, external helper, or separate beta acknowledgement.
|
|
Basic payload checks happen in the receiver.
|
|
|
|
Electric Cherry saves the latest complete report, including player stats, and
|
|
streams it to connected clients. A new start clears the previous round display;
|
|
an end closes the match. Receipt order determines the latest report. This small
|
|
protocol does not attempt to identify delayed events from earlier games or
|
|
provide exactly-once delivery across games.
|
|
|
|
Reports also feed the persistent [Leaderboard](leaderboard.md), independently
|
|
of beta activity. That page documents cumulative stat accounting, round credit,
|
|
and the limits of identifying matches from this protocol.
|
|
|
|
The game scripts are uploaded artifacts, not source maintained in this
|
|
repository. Their reporter should emit the three payloads above.
|
|
|
|
## Requirements
|
|
|
|
Requirements on a qualifying server are ANDed:
|
|
|
|
| Requirement | Evidence |
|
|
| --- | --- |
|
|
| Reach round X | A round change or game end reports round >= X with the required player count |
|
|
| Complete qualifying matches | Game end, optionally qualified by round, player count, and duration |
|
|
| Populated minutes | Time between consecutive game reports with enough players at both ends |
|
|
| Player-hours | Populated minutes multiplied by the lower reported player count, divided by 60 |
|
|
| Reach player count | Player array length in a game report |
|
|
| Sustained population | Consecutive report intervals above the player threshold within one game |
|
|
| Continuous healthy minutes | Consecutive fresh container/RCON observations |
|
|
| Maximum health outages | Transitions from observed healthy to unhealthy |
|
|
| Successful recovery cycles | Repeat the other requirements after each additional empty-server restart |
|
|
| Tester checklist | Tester name and observation entered on the beta detail page |
|
|
|
|
All gameplay requirements use the script reports. Match counts do not read game
|
|
logs. The reported round and required player count must occur together in the
|
|
same game; observations from different games cannot combine to qualify a match.
|
|
The final game-end report can supply that qualifying round. Consecutive duplicate
|
|
game ends do not add another completion.
|
|
|
|
Match duration requires a game start and uses receipt times. Population time
|
|
advances only when another report arrives in the same game; it uses the lower
|
|
player count at the interval's endpoints. There is no heartbeat requirement or
|
|
short interval limit between rounds. These are estimates between reported
|
|
snapshots, not continuous measurements of player presence.
|
|
|
|
Health checks and safe restart gates use the existing container/RCON monitoring.
|
|
Health intervals spanning more than 60 seconds earn no healthy time.
|
|
|
|
## 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.
|
|
|
|
Set wave size to 1 for a canary, a larger number for batches, or 0 for all
|
|
selected servers. Later waves receive files after the previous waves pass.
|
|
Health-outage budget violations on any tested server block promotion.
|
|
|
|
The test window starts when the beta opens, including time waiting for empty
|
|
servers. Missing evidence at the deadline produces **inconclusive**.
|
|
Health monitoring continues after a pass until promotion.
|
|
|
|
For failures, select hold as failed, pause for investigation, or automatic
|
|
rollback. Rollback restores only files touched by the beta. Restarts can wait
|
|
for empty servers or remain manual. Promotion is always manual.
|
|
|
|
Pause holds evaluation and rollout. Resume retains completed progress and the
|
|
original deadline, but starts new timing intervals. A fresh attempt resets
|
|
progress and restarts deployed servers when empty. Recovery cycles repeat
|
|
gameplay milestones and checklist sign-offs while carrying health outages forward.
|
|
|
|
Plans, progress, and evidence persist in SQLite. The detail page shows the most
|
|
recent 200 evidence entries. On upgrading from the old reporter protocol,
|
|
active progress resets. Unsupported checks in existing plans and saved presets
|
|
become manual review items; they cannot silently pass.
|
|
|
|
## API and storage
|
|
|
|
`POST /api/betas` accepts `testPlan`.
|
|
`GET /api/betas/:id` includes the plan, outcome, per-server progress, and
|
|
recent evidence under `test`.
|
|
|
|
`POST /api/betas/:id/test` accepts `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 edits do not change an active plan.
|
|
|
|
Internal progress records support attempts and recovery cycles. The reporter
|
|
does not know about or supply these records. Legacy betas without a test plan
|
|
also use `game_end` for their match-completion gate.
|