Introduce an outbound Python host agent, Debian installer, durable remote operations, immutable game-file libraries, host-aware deployments and ports, and occupancy-safe campaigns and draining. Native hosts do not require Komodo. Add administrator, operator and viewer accounts, protected management routes, secure sessions, password recovery, audit history and separate telemetry tokens. Document first-admin setup, reporter migration and live-host acceptance checks. Validation: 1,343 TypeScript tests and 39 agent tests passed, along with type checking, the production build, compiled SSR/API smoke tests and administrator recovery. Real Debian/Docker T5/T6 acceptance remains unrun.
234 lines
12 KiB
Markdown
234 lines
12 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
|
|
|
|
Set `EC_TELEMETRY_TOKEN` on the Electric Cherry controller to a dedicated random
|
|
secret of at least 32 characters (for example, generate 64 hexadecimal characters
|
|
with `openssl rand -hex 32`). Configure the same secret in each game reporter or
|
|
its trusted local relay. Use a production HTTPS URL or a reverse proxy that
|
|
terminates TLS; browser login cookies do not authorize game telemetry.
|
|
|
|
Send JSON to `POST /api/telemetry` with `Content-Type: application/json` and either
|
|
`Authorization: Bearer <EC_TELEMETRY_TOKEN>` or
|
|
`X-EC-Telemetry-Token: <EC_TELEMETRY_TOKEN>`. If both headers are present, they must
|
|
contain the same secret. Keep the secret out of URLs, payloads and browser code,
|
|
and use a different secret from host-agent credentials or account passwords.
|
|
The JSON event format is unchanged.
|
|
There are four 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` |
|
|
| `heartbeat` | Same fields as `round_change`; sent periodically during a round to refresh the current round and player stats |
|
|
| `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. A missing/invalid machine token returns 401; an unset or invalid
|
|
controller `EC_TELEMETRY_TOKEN` returns 503. Authentication happens before payload
|
|
validation. Authenticated 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 four payloads above and add the
|
|
authentication header. Existing anonymous reporters must be updated before
|
|
telemetry is accepted; if a reporter cannot set HTTP headers, configure a trusted
|
|
host-local relay to add the header. This repository does not bundle that relay or
|
|
assume a particular GSC HTTP implementation. Rotating the shared secret requires
|
|
updating the controller environment and all reporters/relays together.
|
|
|
|
### Telemetry diagnostics
|
|
|
|
Open a server's **Diagnostics → Telemetry API requests** to inspect incoming
|
|
`POST /api/telemetry` calls whose top-level `server` exactly matches its registered
|
|
name. Each entry includes the Electric Cherry receipt time, HTTP result, error
|
|
reason, event, map, round, player count, content type, and submitted JSON. Rejected
|
|
reports are included, even when the content type is wrong but the body is readable
|
|
JSON after authentication. Unauthorized requests are rejected before their bodies
|
|
are read and are not retained in per-server diagnostics. Authentication headers
|
|
and their secrets are never stored. Processing failures return 500 and record the internal error when diagnostic
|
|
storage is available; failed database transactions do not update the scoreboard.
|
|
|
|
The panel refreshes every five seconds while visible and has a manual refresh
|
|
button. History is stored in SQLite starting with this version, with up to 200
|
|
requests per server from the last seven days and a fleet-wide cap of 10,000
|
|
requests. Old entries are pruned when new requests arrive, and expired entries
|
|
are excluded from reads. Payload previews are limited to 16,384 characters and
|
|
marked when truncated. Request headers other than content type are not stored.
|
|
`GET /api/servers/{name}/telemetry-requests` returns the same recent history,
|
|
newest first, with caching disabled.
|
|
|
|
- **No entries:** no attributable requests are retained for this exact name.
|
|
Check the reporter's destination and server field. Calls that never reach
|
|
Electric Cherry, malformed JSON, and bodies without a usable server name
|
|
cannot appear in this server's log. Earlier requests are not backfilled.
|
|
Missing/invalid telemetry credentials also produce no retained request; inspect
|
|
the reporter's HTTP result for 401 or an unconfigured-controller 503.
|
|
- **400 / 415:** inspect the error and payload. One invalid player's stats or a
|
|
duplicate GUID rejects the entire report. These requests do not update the
|
|
scoreboard.
|
|
- **404:** the reported server was not registered or had been destroyed at the
|
|
time of the request.
|
|
- **200:** Electric Cherry processed the report. An empty `players` array
|
|
produces an empty scoreboard, including when sent with `game_end`. If a
|
|
populated accepted report is missing from the scoreboard, investigate the
|
|
live update/browser path.
|
|
- **500:** Electric Cherry failed to process the request. Inspect the recorded
|
|
error and the application log.
|
|
|
|
RCON's online status and player count are separate from gameplay telemetry, so
|
|
an occupied server can still have no scoreboard report.
|
|
|
|
## Requirements
|
|
|
|
Requirements on a qualifying server are ANDed:
|
|
|
|
| Requirement | Evidence |
|
|
| --- | --- |
|
|
| Reach round X | A round change, heartbeat, or game end reports round >= X with the required player count |
|
|
| Complete qualifying matches | Game end at round 2 or later, optionally qualified by a higher 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, waves 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 queues restoration only for files touched by the beta.
|
|
Restoration and restart both wait for an empty server and restart capacity.
|
|
The manual option holds those changes in a rollback campaign until you select
|
|
**Resume when empty**. Promotion is always manual.
|
|
|
|
Pause holds evaluation and wave advancement. 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` at round 2 or later for their match-completion gate.
|