# Mission telemetry Mission → Statistics contains the administrator toggle. `telemetryEnabled` is saved in the `mission` section of `$profile:Escapists.json` and defaults to `1`, including when upgrading an older configuration. The page explains the data collected and shows delivery status. Existing administrator authentication and save-before-apply behavior also apply to this setting. Telemetry runs only on the mission authority, never independently on joining clients. Workbench runs do not collect unless explicitly launched with the `errTelemetryTest` diagnostic flag. There is no statistics website or production receiver included in this change. ## Receiving URL and configuration The bundled receiving URL belongs in `addons/core/Configs/ERR/Telemetry.conf`. It is currently empty pending the receiver's actual URL. Until configured, no telemetry is collected or sent, even with the switch enabled. The menu reports this condition rather than claiming that statistics are being delivered. An operator can override the URL and optionally name/authenticate their instance with `$profile:EscapistsTelemetry.json`: ```json { "endpoint": "https://YOUR-HOST/api/mission-events", "serverName": "My Escape server", "token": "receiver-issued-instance-token" } ``` Use the full POST endpoint, with its exact trailing-slash convention. HTTPS is required. URL credentials, fragments and query parameters are rejected. The optional token goes in the HTTPS JSON body, never in a URL, menu snapshot, log, Workshop resource, or persisted event queue. Configuration loads when the mission starts. An empty endpoint overrides the bundled default and prevents sending. `serverId` is a generated UUID persisted in `$profile:EscapistsTelemetryState.json`. Keep this file/profile for continuity; use different profiles for different server instances. Do not clone this state file into a second server. `serverName` is a display label, not an identity key. ## Wire format One asynchronous JSON POST per event, `Content-Type: application/json`. The receiver must accept chunked request bodies (used by the native HTTP client): ```json { "token": "receiver-issued-instance-token", "event": { "schemaVersion": 1, "eventId": "3f16ef25-13f7-46ad-967f-5e84f36cb84b", "missionId": "a82b4f20-8f32-4978-80f6-c54099185c58", "serverId": "74f74822-cf6d-4fc1-856c-1768afe025f4", "serverName": "My Escape server", "type": "mission_finished", "occurredAt": 1792936800, "elapsedSeconds": 3521, "partial": false, "phase": 5, "outcome": "victory", "previousMissionId": "", "previousOutcome": "", "terrain": "{RESOURCE_GUID}Worlds/Example.ent", "scenario": "Escape", "gameVersion": "engine build version", "mods": [{"id": "2528466DE220D91C", "name": "Co10 Escape Reintroduced", "version": ""}], "modsTruncated": false, "playersTruncated": false, "connectedPlayers": 2, "peakPlayers": 4, "players": [ {"id": "game-account-UUID", "name": "Player", "authenticated": true, "connected": true, "participated": true, "escaped": true, "connectedSeconds": 3200} ], "reason": "Escape complete: 2 escaped; 2 did not escape.", "preparationError": "", "settings": {"telemetryEnabled": 1}, "droppedEvents": 0 } } ``` The example abbreviates the players and settings arrays/maps. Actual `settings` contains the saved mission setting values; `-1` retains its meaning of Random, not an observed random roll. `phase` is 0 preparing, 1 prison, 2 radio search, 3 radio defence, 4 extraction, 5 victory, 6 defeat. `mods` describes mounted non-vanilla addons; version is the locally active Workshop revision when exposed by the engine, otherwise empty. Never infer the newest Workshop version from an empty value. Terrain is the actual loaded world resource, including takeover scenarios, rather than the next-round terrain preference. ## Event and outcome semantics | Event | Meaning | | --- | --- | | `mission_started` | This mission instance began collection, usually during preparation. | | `mission_restarted` | A subsequent instance started in the same profile; links the previous mission and its last known outcome. Also followed by `mission_started`. | | `phase_changed` | An actual nonterminal mission transition. Use elapsed times for milestone durations. | | `heartbeat` | A snapshot every five minutes while unfinished. | | `telemetry_resumed` | Re-enabled during the same mission; the mission ID stays the same and `partial` is true. | | `mission_finished` | Exactly one local result snapshot, before normal teardown. | | Outcome | Count as a gameplay result? | | --- | --- | | `victory` | Yes, successful mission. | | `defeat` | Yes, failed mission, including administrator abandonment. | | `preparation_failed` | No, startup error; inspect `preparationError`. | | `restarted_empty` | No, automatic last-disconnect or idle-empty restart. | | `interrupted` | No, session ended without a gameplay result. | | `unfinished` in `previousOutcome` | No confirmed ending survived. May be a crash/forced stop. Do not infer a defeat. | Player names and stable game account UUIDs support history across servers. No IP addresses, chat, inventories, precise player positions or credentials are in an event. Names can change; aggregate by authenticated `id`, not `name` or temporary connection number. Missing backend identities use a mission-local connection key with `authenticated: false`; exclude those keys from global player rankings. `players` retains observed participants and spectators, including disconnected players. `connected` records the end/snapshot connection state, `participated` records whether the mission admitted a life, and `escaped` uses the authoritative extraction roster. A connected spectator or a left-behind participant is not an escapee. Count individual completions using `escaped`, not the mission's victory alone. `connectedSeconds` is sampled connection time (roughly five-second precision), not survival time. Reconnects reuse the authenticated identity. ## Reliable ingestion and aggregation Return any **2xx only after durable storage**, including for an already accepted event. Other responses/timeouts retry the identical event with the same event ID. The receiver must enforce uniqueness on `(serverId, eventId)` and aggregate by `(serverId, missionId)`. Network delivery is at-least-once, not exactly-once. A crash between acceptance and local acknowledgement can produce a duplicate. Suggested tables: - `events`: unique server/event key, mission key, event time, type, JSON payload. - `missions`: unique server/mission key, terrain, start/end, outcome, peak count, partial flag; a repeated start/restart event must not reset a terminal outcome. - `mission_players`: unique server/mission/player key, name, participation, connected-at-end, escaped, sampled connected duration. Useful queries from terminal mission snapshots: - Per-server failures: count distinct missions with `outcome = 'defeat'` grouped by `serverId`; add terrain for per-map difficulty. - Global failures/wins: the same counts across all server IDs, without counting startup errors, empty restarts or unknown/interrupted sessions as defeats. - Player completions: count distinct missions where an authenticated player's final `escaped` flag is true. Also expose attempts, left-behind results, connected-at-end rosters and best successful duration if desired. - Milestone times: phase-event elapsed time differences for prison, radio and extraction. Treat missing phases, dropped events and partial runs as unknown. Validate schema and limits and authenticate operator-issued tokens if statistics must be trusted. The bundled addon contains no shared secret. These are reports from community-hosted missions, not independent anti-cheat attestations. ## Buffering, disabling and limits Requests have a 15-second timeout, with a 30-second callback watchdog. Retries back off from 10 seconds to five minutes. A five-second dispatch tick sends one request at a time. Disk writes occur on events and acknowledgement, not every player sample. Delivery never blocks mission startup or shutdown. The profile queue holds at most 64 events of 64 KiB each. On overflow it discards queued heartbeats first, then the oldest non-in-flight event; `droppedEvents` reports cumulative loss. Players and mods are each capped at 128 with explicit truncation flags. The previous valid queue generation is retained as `.bak` for recovery. An unreadable queue suspends telemetry and preserves evidence for repair instead of silently replacing the server identity. Unsent final results survive normal scenario reloads and are retried in the next enabled instance. Abrupt termination can lose the latest event; the next start's `previousOutcome` remains unfinished if no result was saved. A permanently stopped host cannot send further data. Disabling immediately stops collection/new requests and discards queued events and recovery copies. It cannot recall an HTTP request already submitted or erase records already accepted by the receiver. Re-enabling does not reconstruct the disabled interval, and marks subsequent events partial. Changing the receiver URL discards unsent data instead of forwarding old records to a different recipient. Native API references: [RestContext](https://community.bistudio.com/wikidata/external-data/arma-reforger/EnfusionScriptAPIPublic/interfaceRestContext.html) and [RestCallback](https://community.bistudio.com/wikidata/external-data/arma-reforger/EnfusionScriptAPIPublic/interfaceRestCallback.html). ### Round combat counters Player records additionally include `kills`, `friendlyKills`, `deaths`, `downed` and `recoveries`, using the same server-owned counters as the after-mission screen. These fields are additive to schema version 1. Older queued records may not contain them; treat absent values as unavailable rather than inventing a historical count. Kills exclude friendly kills, suicides, vehicles and editor kills. Downed means an unconscious episode, while a death is a fatal loss. Recoveries include self-recovery and are not healer-attributed revives. Local result tracking continues when telemetry is disabled; sending still follows the existing telemetry setting and endpoint configuration.