Fix keyed telemetry match attribution (Refs #144)
CI / checks (push) Successful in 5m26s

This commit is contained in:
2026-10-12 00:58:05 -04:00
parent 4ea291d305
commit 785cc4616b
24 changed files with 1115 additions and 112 deletions
+43 -9
View File
@@ -73,6 +73,22 @@ without trimming or truncation. Keep GUIDs as strings to preserve their precisio
and leading zeros. Round and stat values are nonnegative JSON integers.
Each GUID must appear only once within a report's player array.
Every event also accepts these optional top-level fields:
| Field | Meaning |
| ------- | --------------------------------------------------------------------------------------------------- |
| `match` | Reporter-generated game identity: a nonblank string of at most 64 UTF-16 code units. |
| `seq` | Positive JSON safe integer, at most 9,007,199,254,740,991, identifying this report within the game. |
Send both fields for reliable match boundaries, ordering, and retry handling.
Choose a fresh `match` value for every game, including restarts on the same map,
and keep it unchanged throughout that game. Match keys are scoped to the exact
`server` name. Start `seq` at 1 and increment it once per event, including
heartbeats. Retry a report with its original `match`, `seq`, and payload; do not
allocate a new sequence number for a retry. Either field may be omitted for
compatibility, but sequence-based deduplication requires both fields. Existing
reporters without these fields remain valid.
Every event also accepts an optional per-player `ping`: the game server's current
latency measurement in milliseconds, as a nonnegative JSON integer (for example,
`"ping": 42`). Send `null` or omit the field when unavailable; do not substitute
@@ -95,6 +111,8 @@ Send when the game starts. Round and stats are not required.
{
"event": "game_start",
"server": "t6_zombie_town_1",
"match": "town-20261012-001",
"seq": 1,
"map": "zm_transit",
"players": [
{ "name": "Josh", "guid": "12345678901234567" },
@@ -111,6 +129,8 @@ Send the new round and each player's current stats.
{
"event": "round_change",
"server": "t6_zombie_town_1",
"match": "town-20261012-001",
"seq": 2,
"map": "zm_transit",
"round": 25,
"players": [
@@ -144,6 +164,8 @@ Send the final round and each player's final stats when the game ends.
{
"event": "game_end",
"server": "t6_zombie_town_1",
"match": "town-20261012-001",
"seq": 3,
"map": "zm_transit",
"round": 26,
"players": [
@@ -179,16 +201,28 @@ validation. Authenticated bodies over 1 MiB return 413 before JSON parsing.
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.
Send events in gameplay order directly to Electric Cherry. There is no discovery
request, beta token, 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
With `match` and `seq`, repeated sequences are ignored even after an application
restart. A lower sequence that arrives late can fill a missing-report gap without
rolling back the current round, roster, or counters. A delayed `game_start` can
record the starting roster without resetting the game's observed progression.
A known older match key cannot replace a newer game's live state. When an unseen
key starts a new game before the previous keyed game's `game_end` arrives,
Matches labels the previous game **End report missing**. A later `game_end`
completes its own match without changing the newer game. Missing sequence counts
are diagnostic: they do not invent absent rounds, rosters, or an ending.
Duplicate and older reports do not advance beta requirements or replace the
current scoreboard. A successful response acknowledges the submitted report;
it does not mean that report became the current game state.
Electric Cherry saves the latest accepted 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.
an end closes the match. Without a match key, legacy receipt-order and
map/round heuristics remain in effect. Those reports cannot reliably distinguish
delayed events from earlier games; send them once in order.
Match timing is independent of player count: an empty `game_start` still records
the start, and empty rosters or RCON polls do not erase it. Fleet can display a
@@ -198,7 +232,7 @@ Fleet clears the previous match's timing rather than carrying it into the new on
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.
and match identity and ordering behavior.
### Reporter deployment