Fix telemetry delivery and report loaded island names; record Workshop 0.0.130
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -147,18 +147,19 @@ Never distribute the administrator or read credential to game servers.
|
||||
|
||||
Opening `/`, `/api/summary`, or `/api/mission-events` in a browser returns public
|
||||
JSON with collected win/failure totals. No token is required for these summaries.
|
||||
Use `?terrain=URL_ENCODED_WORLD_RESOURCE` to restrict totals to one map, and add
|
||||
Use `?terrain=URL_ENCODED_ISLAND_NAME` to restrict totals to one map, and add
|
||||
`&serverId=SERVER_UUID` to include that installation's record for the same map.
|
||||
Without a terrain filter, the response includes overall totals and up to 100 maps
|
||||
with separate win/failure counts. The response contains counts and map resources,
|
||||
with separate win/failure counts. The response contains counts and reported island names,
|
||||
not player/server names, account IDs, raw events or credentials. Full records
|
||||
remain protected by admin/read credentials.
|
||||
|
||||
Each summary has `schemaVersion: 1`, `terrain`, `allServers` and optional `server`.
|
||||
Each record contains `completed`, `wins`, `losses`, `partialMissions`, and
|
||||
`winRate` (percentage; null when there are no completed rounds). Map identity is
|
||||
the actual loaded world resource, compared case-insensitively. Different worlds
|
||||
are kept separate even when their generic scenario title is the same.
|
||||
`winRate` (percentage; null when there are no completed rounds). The game server resolves the island name from its loaded root scene, independently
|
||||
of the scenario title. Names are compared case-insensitively. Older reports
|
||||
through 0.0.129 retain their world-resource identifiers; query those with the
|
||||
original resource path. The receiver does not infer or rewrite historical islands.
|
||||
|
||||
The acknowledgement for an accepted victory/defeat event also includes a
|
||||
`community` summary for that event's map, server, mission and event ID. It is
|
||||
|
||||
@@ -50,7 +50,12 @@ export function validateEvent(event) {
|
||||
for (const key of ['partial', 'modsTruncated', 'playersTruncated']) {
|
||||
requireValue(typeof event[key] === 'boolean', `Invalid ${key}`);
|
||||
}
|
||||
for (const key of ['elapsedSeconds', 'connectedPlayers', 'peakPlayers', 'droppedEvents']) {
|
||||
// Published Enfusion clients serialize elapsed time as fractional seconds.
|
||||
// Normalize before hashing/storage so their durable queues can retry unchanged.
|
||||
requireValue(typeof event.elapsedSeconds === 'number' && Number.isFinite(event.elapsedSeconds)
|
||||
&& event.elapsedSeconds >= 0 && event.elapsedSeconds <= 2147483647, 'Invalid elapsedSeconds');
|
||||
event.elapsedSeconds = Math.floor(event.elapsedSeconds);
|
||||
for (const key of ['connectedPlayers', 'peakPlayers', 'droppedEvents']) {
|
||||
requireValue(integer(event[key]), `Invalid ${key}`);
|
||||
}
|
||||
requireValue(integer(event.occurredAt, 253402300799), 'Invalid occurredAt');
|
||||
|
||||
@@ -92,6 +92,24 @@ test('concurrent retries store once; reordered JSON is equivalent; conflicting c
|
||||
assert.equal((await ingest({...e, serverId: otherServer}, otherToken)).status, 201);
|
||||
});
|
||||
|
||||
test('published fractional-duration defeats are accepted, counted and retry idempotently', async () => {
|
||||
const terrain = '{1234567890ABCDEF}Worlds/FractionalDuration.ent';
|
||||
const e = event({terrain, outcome: 'defeat', phase: 6, players: [], elapsedSeconds: 261.593994140625});
|
||||
const first = await ingest(e);
|
||||
assert.equal(first.status, 201, await first.clone().text());
|
||||
assert.equal((await first.json()).accepted, true);
|
||||
assert.equal((await detail(e)).elapsedSeconds, 261);
|
||||
assert.equal((await ingest(e)).status, 200);
|
||||
assert.equal((await ingest({...e, elapsedSeconds: 261})).status, 200);
|
||||
const summary = await (await call(`/api/summary?terrain=${encodeURIComponent(terrain)}`)).json();
|
||||
assert.equal(summary.allServers.losses, 1);
|
||||
assert.equal(summary.allServers.completed, 1);
|
||||
for (const elapsedSeconds of [-0.1, 2147483647.1, '261.594', null]) {
|
||||
assert.equal((await ingest(event({elapsedSeconds}))).status, 400);
|
||||
}
|
||||
assert.equal((await ingest(event({connectedPlayers: 0.5}))).status, 400);
|
||||
});
|
||||
|
||||
test('out-of-order snapshots and second terminal reports cannot reopen or double count a mission', async () => {
|
||||
const e = event({players: []});
|
||||
const heartbeat = {...e, type: 'heartbeat', phase: 4, outcome: '', elapsedSeconds: 100};
|
||||
|
||||
Reference in New Issue
Block a user