Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+27 -12
View File
@@ -10,7 +10,7 @@ does not require a Docker socket or direct access to host filesystems.
## Run locally
Use Node.js 22.12+ (the container uses Node.js 22), npm and Python 3.
Use Node.js 22.13+ (the container uses Node.js 22), npm and Python 3.
```sh
npm ci
@@ -76,12 +76,27 @@ remove the generated fixture directory.
## Checks and builds
```sh
npm run lint
npm run check
npm test
npm run test:agent
npm run build
```
Run `npm run format` to apply Prettier formatting before committing. It uses
four-space indentation and a 100-column target for supported source, config and
documentation files, including Svelte components. Python, shell and game scripts
are outside Prettier's supported languages. Generated agent downloads and the npm
lockfile keep their generator's formatting.
`npm run lint` checks formatting without changing files, then runs the recommended
JavaScript, TypeScript and Svelte ESLint rules. Generated build output and local
runtime data are excluded. Type checking remains part of `npm run check`.
The ESLint config documents exceptions for existing Svelte navigation, list and
reactivity patterns. Individual suppressions explain intentional validation and
test fixtures. Node.js 22.13 is the minimum supported 22.x version for the lint
toolchain.
The `package.json` overrides keep SvelteKit 2 on patched `devalue` 5.x (5.9.4 or
newer) and `cookie` 0.7.x releases until its dependency ranges include those fixes.
These dependencies ship in the server bundle even though SvelteKit is a
@@ -103,17 +118,17 @@ T5/T6 server validation use the [host acceptance checklist](../guides/native-hos
Paths below are relative to the repository root.
| Path | Responsibility |
| --- | --- |
| `src/routes` | Pages and API endpoints |
| `src/lib/ui` | Shared components, stores and [UI patterns](ui-patterns.md) |
| `src/lib/server/hosts` | Enrollment, readiness and command transport |
| `src/lib/server/backends` | Instance identity and remote file access |
| `src/lib/server/provisioning/native.ts` | Lifecycle and campaign queues connected to agents |
| `src/lib/server/game-files` | Immutable uploads and streaming distribution |
| `src/lib/server/db` | SQLite schema, migrations and shared artifact storage |
| `src/lib/server/reports` | Metrics, leaderboard and capacity workers |
| `agent` | Python service, installer, packaging and tests |
| Path | Responsibility |
| --------------------------------------- | ----------------------------------------------------------- |
| `src/routes` | Pages and API endpoints |
| `src/lib/ui` | Shared components, stores and [UI patterns](ui-patterns.md) |
| `src/lib/server/hosts` | Enrollment, readiness and command transport |
| `src/lib/server/backends` | Instance identity and remote file access |
| `src/lib/server/provisioning/native.ts` | Lifecycle and campaign queues connected to agents |
| `src/lib/server/game-files` | Immutable uploads and streaming distribution |
| `src/lib/server/db` | SQLite schema, migrations and shared artifact storage |
| `src/lib/server/reports` | Metrics, leaderboard and capacity workers |
| `agent` | Python service, installer, packaging and tests |
## Performance diagnostics
+10 -10
View File
@@ -74,16 +74,16 @@ logs. Static values use text or state colors; the accent marks links and control
### Product language
| Concept | Visible label |
| --- | --- |
| A managed game instance | Server; use the friendly map and lobby name |
| Creating a server | Create server |
| Removing a server | Remove server |
| Favourite server | Pin |
| Game does not answer | Not responding |
| Games | Black Ops I / Black Ops II; BO1 / BO2 in compact labels |
| Releasing file changes | Campaigns |
| Player completion evidence | Present in final report / Not present in final report |
| Concept | Visible label |
| -------------------------- | ------------------------------------------------------- |
| A managed game instance | Server; use the friendly map and lobby name |
| Creating a server | Create server |
| Removing a server | Remove server |
| Favourite server | Pin |
| Game does not answer | Not responding |
| Games | Black Ops I / Black Ops II; BO1 / BO2 in compact labels |
| Releasing file changes | Campaigns |
| Player completion evidence | Present in final report / Not present in final report |
Keep internal values such as `t5`, `t6`, `destroy`, instance IDs, and filenames in
API fields and technical details. User-facing actions use the labels above.
+10 -10
View File
@@ -31,16 +31,16 @@ guessable token. An unset setup token holds setup closed.
## Roles
| Capability | Viewer | Operator | Administrator |
| --- | --- | --- | --- |
| Fleet, metrics, matches and operation summaries | Yes | Yes | Yes |
| Raw configs, file diffs, logs, RCON and telemetry diagnostics | No | Yes | Yes |
| Deploy/start/stop/restart/rebuild/remove servers | No | Yes | Yes |
| Scripts, plugins, configs, campaigns, betas and collections | Read summaries | Manage | Manage |
| Host setup, deletion, enrollment, credentials, runtime preparation and game-file upload/cache | Read status | Read status | Manage |
| Server keys and global settings/maintenance policies | No | No | Manage |
| Create users, change roles, disable accounts and reset passwords | No | No | Yes |
| Own password and sign out | Yes | Yes | Yes |
| Capability | Viewer | Operator | Administrator |
| --------------------------------------------------------------------------------------------- | -------------- | ----------- | ------------- |
| Fleet, metrics, matches and operation summaries | Yes | Yes | Yes |
| Raw configs, file diffs, logs, RCON and telemetry diagnostics | No | Yes | Yes |
| Deploy/start/stop/restart/rebuild/remove servers | No | Yes | Yes |
| Scripts, plugins, configs, campaigns, betas and collections | Read summaries | Manage | Manage |
| Host setup, deletion, enrollment, credentials, runtime preparation and game-file upload/cache | Read status | Read status | Manage |
| Server keys and global settings/maintenance policies | No | No | Manage |
| Create users, change roles, disable accounts and reset passwords | No | No | Yes |
| Own password and sign out | Yes | Yes | Yes |
Operators can launch using available game-pool keys already saved by an administrator. They
cannot save keys indirectly through a launch request. An operator's content
+4 -4
View File
@@ -12,10 +12,10 @@ Choose **Add policy**, select the game and scope, and enter an explicit maximum.
Choose **Save policy** to apply changes directly. Saving checks the current fleet, policy revision, ownership conflicts, and host readiness. If saving is blocked, the form keeps your settings and shows the reason. Saved policies show lobby counts, key availability, and the current status. An active job appears once with its latest stage and a **View job** link. Use **Lobbies** for per-map spare coverage and occupancy evidence, **Activity** for decision changes grouped by job, and **Metrics** for observed capacity and provisioning measurements.
| Autoscaling | Behavior |
| --- | --- |
| Off | No new actions. Unsubmitted work is withdrawn and delivered operations remain tracked until settled. |
| On | Adds lobbies and removes surplus lobbies after continuous empty evidence and all removal checks pass. |
| Autoscaling | Behavior |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| Off | No new actions. Unsubmitted work is withdrawn and delivered operations remain tracked until settled. |
| On | Adds lobbies and removes surplus lobbies after continuous empty evidence and all removal checks pass. |
Plutonium keys belong to a game pool shared by every map, manual launch, collection, beta, and autoscaling policy for that game. An eligible creation uses the lowest available lobby number from 1–20 and reserves any free game key atomically. Key labels do not select a map or lobby. The policy shows saved and available game keys; policy maxima can exceed pool size, and a policy can be saved before any keys are added. An empty or exhausted pool holds new growth with a clear reason; it does not prevent surplus removal. Queued launches reserve their key, stopped instances retain it, and confirmed cleanup returns it to the pool. Failed or uncertain launches keep their reservation until safely reconciled. Existing lobbies and unfinished operations must be healthy and settled before activation. Automatic removal also requires agent **1.7.0 or later** advertising `instance.autoscaling-remove` on each affected host.
+71 -39
View File
@@ -56,12 +56,12 @@ There are four events. Send them whether or not a beta is active.
### Event fields
| Event | Fields |
| --- | --- |
| `game_start` | `server`, `map`, `players` with each player's `name` and `guid` |
| 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` |
| `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
@@ -89,13 +89,13 @@ 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" }
]
"event": "game_start",
"server": "t6_zombie_town_1",
"map": "zm_transit",
"players": [
{ "name": "Josh", "guid": "12345678901234567" },
{ "name": "Alex", "guid": "23456789012345678" }
]
}
```
@@ -105,14 +105,30 @@ 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 }
]
"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
}
]
}
```
@@ -122,14 +138,30 @@ 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 }
]
"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
}
]
}
```
@@ -213,18 +245,18 @@ an occupied server can still have no scoreboard report.
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 |
| 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 |
| 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
+5 -5
View File
@@ -26,11 +26,11 @@ openssl rand -hex 32
Set these values in `.env`:
| Variable | Purpose |
| --- | --- |
| `EC_PUBLIC_URL` | The external HTTPS address of the dashboard. |
| `EC_SETUP_TOKEN` | The generated secret, used to create the first administrator. |
| `EC_DATA_DIR` | A persistent directory for the database, game files, and beta archives. |
| Variable | Purpose |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `EC_PUBLIC_URL` | The external HTTPS address of the dashboard. |
| `EC_SETUP_TOKEN` | The generated secret, used to create the first administrator. |
| `EC_DATA_DIR` | A persistent directory for the database, game files, and beta archives. |
| `EC_TELEMETRY_TOKEN` | A separate secret of at least 32 characters for game reporters. Generate it independently with the same command. |
For an existing installation, retain its original data directory. Compose
+59 -59
View File
@@ -87,15 +87,15 @@ of inferring one. GUIDs stay strings.
## Recorded statistics
| Statistic | Meaning |
| --- | --- |
| Highest round | Highest round reported with that player present, including players who joined mid-game. |
| Total kills, headshots, revives, downs | Sum of each player's highest reported counter in each game. Round snapshots are cumulative, so they are never added together within a game. |
| Total rounds | Round progression during observed participation, added across games. A player observed at game start and then round 8 receives seven rounds. A player first seen at round 8 gets a highest-round record of 8, but zero total rounds; reaching round 9 then adds one. |
| Games played | Completed games in which a stats report at round 2 or later contains the player, excluding players who left in the same round they joined. Staying through game end counts even without a round change. Legacy participation with an unknown joining round still counts. Start-only and round 1 participation do not count. |
| Games completed | Games whose `game_end` roster contains the player. Disconnected players retain earlier stats without receiving completion credit. |
| Best game kills | Highest kill count recorded in any one game. |
| Highest reported score | Highest observed score balance. Spending points can lower the score, so this is not cumulative points earned. |
| Statistic | Meaning |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Highest round | Highest round reported with that player present, including players who joined mid-game. |
| Total kills, headshots, revives, downs | Sum of each player's highest reported counter in each game. Round snapshots are cumulative, so they are never added together within a game. |
| Total rounds | Round progression during observed participation, added across games. A player observed at game start and then round 8 receives seven rounds. A player first seen at round 8 gets a highest-round record of 8, but zero total rounds; reaching round 9 then adds one. |
| Games played | Completed games in which a stats report at round 2 or later contains the player, excluding players who left in the same round they joined. Staying through game end counts even without a round change. Legacy participation with an unknown joining round still counts. Start-only and round 1 participation do not count. |
| Games completed | Games whose `game_end` roster contains the player. Disconnected players retain earlier stats without receiving completion credit. |
| Best game kills | Highest kill count recorded in any one game. |
| Highest reported score | Highest observed score balance. Spending points can lower the score, so this is not cumulative points earned. |
Same-round departures retain their recorded stats, personal bests, and match
history; only the games-played count excludes them. This also changes the game
@@ -211,15 +211,15 @@ example above works for command-line or server-side clients. Successful response
All parameters are optional. Names and enumerated values are case-sensitive.
Use URL encoding for map keys and search text.
| Parameter | Default | Accepted values and behavior |
| --- | --- | --- |
| `game` | Both games | Exactly `t5` or `t6`. An empty value is invalid. |
| `map` | All maps | Opaque `maps[].key`, at most 1024 characters. Empty means no map filter. An unknown key or a game/map mismatch returns an empty selection, not an error. |
| `players` | All squad sizes | Exactly `1`, `2`, `3`, or `4`, matching the recorded starting player count. Unknown counts contribute only when this filter is absent. |
| `metric` | `highestRound` | One of the metric keys below. An empty or unknown value is invalid. |
| `q` | No search | At most 100 characters before trimming. Matches a literal substring of the current name or GUID, ignoring ASCII letter case. Surrounding whitespace is trimmed; whitespace-only input means no search. `%` and `_` are literal characters, not wildcards. |
| `limit` | `50` | Integer from 1 to 100. |
| `offset` | `0` | Integer from 0 to 1,000,000; counts matching entries to skip, not ranks or pages. |
| Parameter | Default | Accepted values and behavior |
| --------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `game` | Both games | Exactly `t5` or `t6`. An empty value is invalid. |
| `map` | All maps | Opaque `maps[].key`, at most 1024 characters. Empty means no map filter. An unknown key or a game/map mismatch returns an empty selection, not an error. |
| `players` | All squad sizes | Exactly `1`, `2`, `3`, or `4`, matching the recorded starting player count. Unknown counts contribute only when this filter is absent. |
| `metric` | `highestRound` | One of the metric keys below. An empty or unknown value is invalid. |
| `q` | No search | At most 100 characters before trimming. Matches a literal substring of the current name or GUID, ignoring ASCII letter case. Surrounding whitespace is trimmed; whitespace-only input means no search. `%` and `_` are literal characters, not wildcards. |
| `limit` | `50` | Integer from 1 to 100. |
| `offset` | `0` | Integer from 0 to 1,000,000; counts matching entries to skip, not ranks or pages. |
`limit` and `offset` accept decimal digits only. Empty strings, signs, spaces,
fractions and exponent notation are rejected rather than clamped. String
@@ -235,40 +235,40 @@ Metric keys are `highestRound`, `kills`, `roundsSurvived`, `headshots`, `revives
HTTP `200` returns the following top-level fields directly, with no `ok` or
`data` wrapper:
| Field | Type | Meaning |
| --- | --- | --- |
| `entries` | Entry array | Requested page, ordered by the selected metric descending. |
| `maps` | Map array | Available completed-match map options, filtered only by `game`, independent of `map`, `q`, metric qualification and pagination. |
| `total` | number | Number of matching GUIDs after metric qualification and search, before pagination. |
| `limit`, `offset` | number | Accepted pagination values. |
| `metric` | string | Selected metric key, including the default when omitted. |
| `summary` | object | Totals for all qualifying completed participation in the game/map scope, independent of `q`, metric qualification and pagination. |
| Field | Type | Meaning |
| ----------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `entries` | Entry array | Requested page, ordered by the selected metric descending. |
| `maps` | Map array | Available completed-match map options, filtered only by `game`, independent of `map`, `q`, metric qualification and pagination. |
| `total` | number | Number of matching GUIDs after metric qualification and search, before pagination. |
| `limit`, `offset` | number | Accepted pagination values. |
| `metric` | string | Selected metric key, including the default when omitted. |
| `summary` | object | Totals for all qualifying completed participation in the game/map scope, independent of `q`, metric qualification and pagination. |
Each entry combines one GUID's qualifying participation across the selected
games/maps. It contains all of these fields regardless of the selected metric:
| Entry field | Type | Meaning |
| --- | --- | --- |
| `guid` | string | Player identity. Preserve it as a string, including leading zeros. |
| `name` | string | Latest reported name across both games, even if that report is outside the selected scope or from an unfinished match. |
| `rank` | number | Rank within the scope after metric qualification, before search/pagination. Ties share rank and leave gaps: 1, 1, 3. |
| `highestRound`, `kills`, `roundsSurvived`, `headshots`, `revives`, `downs`, `gamesPlayed`, `gamesCompleted`, `bestKills`, `bestScore` | number | The ten counters defined under [Recorded statistics](#recorded-statistics). |
| `headshotRate`, `killsPerDown`, `averageKills`, `averageRounds`, `completionRate` | number or null | Ratios defined below; `null` if the denominator is zero. |
| `lastSeen` | string | Latest player observation among the qualifying participation in this scope, not the latest global identity update. |
| `records` | object | Personal-best evidence keyed by `highestRound`, `bestKills` and `bestScore`. |
| Entry field | Type | Meaning |
| ------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `guid` | string | Player identity. Preserve it as a string, including leading zeros. |
| `name` | string | Latest reported name across both games, even if that report is outside the selected scope or from an unfinished match. |
| `rank` | number | Rank within the scope after metric qualification, before search/pagination. Ties share rank and leave gaps: 1, 1, 3. |
| `highestRound`, `kills`, `roundsSurvived`, `headshots`, `revives`, `downs`, `gamesPlayed`, `gamesCompleted`, `bestKills`, `bestScore` | number | The ten counters defined under [Recorded statistics](#recorded-statistics). |
| `headshotRate`, `killsPerDown`, `averageKills`, `averageRounds`, `completionRate` | number or null | Ratios defined below; `null` if the denominator is zero. |
| `lastSeen` | string | Latest player observation among the qualifying participation in this scope, not the latest global identity update. |
| `records` | object | Personal-best evidence keyed by `highestRound`, `bestKills` and `bestScore`. |
Entries have no `game` field. Equal metric values are displayed in name order
(ASCII case-insensitive, then exact name), then GUID order. `q` is a substring
search, not an exact-GUID lookup; a tool looking up one GUID must check equality
on returned `guid` values and account for pagination.
| Ratio | Calculation | Requirement to rank by this metric |
| --- | --- | --- |
| `headshotRate` | `headshots / kills` | `gamesPlayed >= 5` and `kills >= 100` |
| `killsPerDown` | `kills / downs` | `gamesPlayed >= 5` and `downs >= 5` |
| `averageKills` | `kills / gamesPlayed` | `gamesPlayed >= 5` |
| `averageRounds` | `roundsSurvived / gamesPlayed` | `gamesPlayed >= 5` |
| `completionRate` | `gamesCompleted / gamesPlayed` | `gamesPlayed >= 5` |
| Ratio | Calculation | Requirement to rank by this metric |
| ---------------- | ------------------------------ | ------------------------------------- |
| `headshotRate` | `headshots / kills` | `gamesPlayed >= 5` and `kills >= 100` |
| `killsPerDown` | `kills / downs` | `gamesPlayed >= 5` and `downs >= 5` |
| `averageKills` | `kills / gamesPlayed` | `gamesPlayed >= 5` |
| `averageRounds` | `roundsSurvived / gamesPlayed` | `gamesPlayed >= 5` |
| `completionRate` | `gamesCompleted / gamesPlayed` | `gamesPlayed >= 5` |
These are unrounded ratios, not formatted strings. For `headshotRate` and
`completionRate`, `0.5` means 50%. Requirements use the selected game/map scope
@@ -283,14 +283,14 @@ engine map identifier; `name` is the display name. Pass `key` as the request's
completed matches reaching round 2, so an option can exist without any qualifying
players. Removed servers' maps remain available.
| Summary field | Type | Meaning |
| --- | --- | --- |
| `players` | number | Unique qualifying GUIDs, combined across games. |
| `matches` | number | Distinct completed matches with qualifying player participation; not the sum of `gamesPlayed`. |
| `kills`, `roundsSurvived` | number | Sums across qualifying players; rounds are player-rounds. |
| `highestRound` | number | Highest qualifying player's recorded round. |
| `firstTrackedAt` | string or null | Earliest match `startedAt` among those matches. |
| `updatedAt` | string or null | Latest match report receipt time among those matches; not a response-generation time or a complete data-change token. |
| Summary field | Type | Meaning |
| ------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `players` | number | Unique qualifying GUIDs, combined across games. |
| `matches` | number | Distinct completed matches with qualifying player participation; not the sum of `gamesPlayed`. |
| `kills`, `roundsSurvived` | number | Sums across qualifying players; rounds are player-rounds. |
| `highestRound` | number | Highest qualifying player's recorded round. |
| `firstTrackedAt` | string or null | Earliest match `startedAt` among those matches. |
| `updatedAt` | string or null | Latest match report receipt time among those matches; not a response-generation time or a complete data-change token. |
For an empty scope, numeric summary fields are zero and both timestamps are
`null`. A search with no results or an offset beyond `total` returns
@@ -301,15 +301,15 @@ Each `records` value has the following fields. Evidence uses the same game/map
scope as the entry, and ties choose the earliest completion, then lowest match
ID. The three records can refer to different matches.
| Evidence field | Type | Meaning |
| --- | --- | --- |
| `matchId`, `value` | number | Completed match ID and that personal-best value. Link to `/matches/{matchId}` for its results page. |
| `game`, `mapKey`, `mapName`, `server` | string | Recorded game, opaque map key, map display name and server name. |
| `startedAt` | string | First recorded match observation; only the game start when `startObserved` is true. |
| `endedAt` | string or null | Recorded completion time; can be unknown in older stored records. |
| `startObserved` | boolean | Whether a `game_start` was received for the match. |
| `presentAtStart` | boolean or null | Whether the player was first observed at the recorded start; `null` for unknown older participation. |
| `firstObservedRound` | number or null | Round of the player's first observation, or unknown. This does not prove continuous survival. |
| Evidence field | Type | Meaning |
| ------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------- |
| `matchId`, `value` | number | Completed match ID and that personal-best value. Link to `/matches/{matchId}` for its results page. |
| `game`, `mapKey`, `mapName`, `server` | string | Recorded game, opaque map key, map display name and server name. |
| `startedAt` | string | First recorded match observation; only the game start when `startObserved` is true. |
| `endedAt` | string or null | Recorded completion time; can be unknown in older stored records. |
| `startObserved` | boolean | Whether a `game_start` was received for the match. |
| `presentAtStart` | boolean or null | Whether the player was first observed at the recorded start; `null` for unknown older participation. |
| `firstObservedRound` | number or null | Round of the player's first observation, or unknown. This does not prove continuous survival. |
Timestamps reflect controller receipt/observation times, normally ISO 8601 UTC
strings such as `2026-10-05T12:00:00.000Z`, not reporter gameplay-clock times.
+6 -6
View File
@@ -63,13 +63,13 @@ represents the current registration rather than every instance in the history.
Hosting Rules v1 deliberately offer experiments even with small samples:
| Recorded condition | Suggested experiment |
| --- | --- |
| At least 20% of known capacity time had every lobby full | Trial one additional lobby during busy hours. |
| Capacity unavailable and players present for at least 80% of known server time | Tentatively trial an extra lobby using occupancy as a proxy. |
| Recorded condition | Suggested experiment |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| At least 20% of known capacity time had every lobby full | Trial one additional lobby during busy hours. |
| Capacity unavailable and players present for at least 80% of known server time | Tentatively trial an extra lobby using occupancy as a proxy. |
| Players present for at most 10% of known time, with under 1% measured pressure or unavailable capacity | Review one idle lobby or a shorter hosting window while preserving access to the map. |
| Occupancy unavailable | Check telemetry/RCON reporting and watch for known player counts. |
| Other observations | Check missing capacity or keep the lineup and watch peaks. |
| Occupancy unavailable | Check telemetry/RCON reporting and watch for known player counts. |
| Other observations | Check missing capacity or keep the lineup and watch peaks. |
Each suggestion includes evidence and an outcome to watch. Unknown capacity,
less than one hour of relevant known evidence, or substantial unknown captured
+1 -1
View File
@@ -52,7 +52,7 @@ GitHub release is imported from its detail page.
- `GET /api/plugins/github/release?url=...` previews a release and its DLL assets.
- `POST /api/plugins/github/import` accepts JSON `{ url, assetId, name,
scopeType?, scopeValue? }` for a new plugin, or `{ url, assetId, pluginId }`
scopeType?, scopeValue? }` for a new plugin, or `{ url, assetId, pluginId }`
for an existing plugin. Use the preview's exact release URL, not a mutable
`latest` URL. Returns plugin/version IDs, checksum and the beta builder URL.
- `GET /api/plugins/updates` checks all plugins; `?pluginId=123` checks one.
+7 -7
View File
@@ -34,14 +34,14 @@ of its explicit removal list, which resets whenever the preview is refreshed.
See [Collections](collections.md) for the review details and safe submission
retries when a response is lost.
| Action | Behavior |
| --- | --- |
| Create | Create a new instance using the latest promoted files and verify container health, game status, the selected map, and the Plutonium revision. |
| Start | Start the existing container and verify container health and RCON. |
| Restart | Wait until empty, restart the existing container, and verify container health and RCON. |
| Action | Behavior |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Create | Create a new instance using the latest promoted files and verify container health, game status, the selected map, and the Plutonium revision. |
| Start | Start the existing container and verify container health and RCON. |
| Restart | Wait until empty, restart the existing container, and verify container health and RCON. |
| Rebuild | Wait until empty, replace the instance through the host agent, preserving its server identity and port while advancing its generation. Verify container health, game status, map, and Plutonium revision. |
| Stop | Wait until empty, stop the container, and verify that the host agent reports it stopped. Keep the instance for a later Start. |
| Remove | Wait until empty, remove the container and its files through the host agent, then retire the instance and registration. |
| Stop | Wait until empty, stop the container, and verify that the host agent reports it stopped. Keep the instance for a later Start. |
| Remove | Wait until empty, remove the container and its files through the host agent, then retire the instance and registration. |
## Safety and verification
+22 -22
View File
@@ -44,28 +44,28 @@ expire unused artifact versions when their age policy is disabled. Game-file gra
is a delay: 0 adds no delay. A positive age and version count together preserve the
minimum count as well as versions younger than the age threshold.
| History or storage | Default |
| --- | --- |
| Campaigns | 7 days |
| Successful routine operations | 1 day |
| Provisioning workflows | Indefinite |
| Agent actions | Indefinite |
| Explicitly resolved failures | Indefinite |
| Non-update maintenance jobs | 30 days |
| Resolved recovery incidents | Indefinite |
| Host resource history | 7 days |
| Instance resource samples | 1 day |
| Fleet occupancy metrics | 31 days |
| Telemetry diagnostics | 7 days; 200 per server; 10,000 overall |
| Completed/interrupted matches | Indefinite |
| Closed betas and test evidence | Indefinite |
| Campaign file bodies and closed beta snapshots | 30 days |
| Completed launch and command file bodies | 30 days |
| Unused artifact versions | No automatic expiry; no minimum count |
| Unused game files | No grace period; keep latest 1 version per game |
| Incomplete browser uploads | Indefinite |
| Authentication audit events | Indefinite |
| Minimum history count | 0 additional records |
| History or storage | Default |
| ---------------------------------------------- | ----------------------------------------------- |
| Campaigns | 7 days |
| Successful routine operations | 1 day |
| Provisioning workflows | Indefinite |
| Agent actions | Indefinite |
| Explicitly resolved failures | Indefinite |
| Non-update maintenance jobs | 30 days |
| Resolved recovery incidents | Indefinite |
| Host resource history | 7 days |
| Instance resource samples | 1 day |
| Fleet occupancy metrics | 31 days |
| Telemetry diagnostics | 7 days; 200 per server; 10,000 overall |
| Completed/interrupted matches | Indefinite |
| Closed betas and test evidence | Indefinite |
| Campaign file bodies and closed beta snapshots | 30 days |
| Completed launch and command file bodies | 30 days |
| Unused artifact versions | No automatic expiry; no minimum count |
| Unused game files | No grace period; keep latest 1 version per game |
| Incomplete browser uploads | Indefinite |
| Authentication audit events | Indefinite |
| Minimum history count | 0 additional records |
Telemetry age and count policies are independent; 0 disables each individual cap.
Diagnostic responses remain bounded to 10,000 rows when the per-server retention
+32 -32
View File
@@ -14,16 +14,16 @@ Update October 7, 2026: Dry run and Create only have been removed. Current polic
## Existing foundations and required extensions
| Foundation | Reuse and required work |
| --- | --- |
| Background worker | Add a requester to [pipeline/worker.ts](../../src/lib/server/pipeline/worker.ts), which already reconciles background work every 15 seconds independently of Provisioning. Keep host execution inside Provisioning. |
| Occupancy evidence | Reuse [status evidence](../../src/lib/server/fleet/status-evidence.ts) and shared freshness rules. Monitoring runs on a 30-second cadence; accepted evidence currently expires after 90 seconds. Positive occupancy vetoes an empty claim. Unknown is never zero. |
| Safe creation | Use the durable creation intent and placement path in [provisioning/native.ts](../../src/lib/server/provisioning/native.ts). Preserve CPU, memory, port, host eligibility, library, and startup concurrency checks. A blocked create remains queued. |
| Safe removal | Reuse the controller occupancy checks, instance generation checks, host-local check, and cleanup checkpoints. Add autoscaling-specific admission and cancellation behavior before enabling automatic destruction. |
| Foundation | Reuse and required work |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Background worker | Add a requester to [pipeline/worker.ts](../../src/lib/server/pipeline/worker.ts), which already reconciles background work every 15 seconds independently of Provisioning. Keep host execution inside Provisioning. |
| Occupancy evidence | Reuse [status evidence](../../src/lib/server/fleet/status-evidence.ts) and shared freshness rules. Monitoring runs on a 30-second cadence; accepted evidence currently expires after 90 seconds. Positive occupancy vetoes an empty claim. Unknown is never zero. |
| Safe creation | Use the durable creation intent and placement path in [provisioning/native.ts](../../src/lib/server/provisioning/native.ts). Preserve CPU, memory, port, host eligibility, library, and startup concurrency checks. A blocked create remains queued. |
| Safe removal | Reuse the controller occupancy checks, instance generation checks, host-local check, and cleanup checkpoints. Add autoscaling-specific admission and cancellation behavior before enabling automatic destruction. |
| Requester eligibility | [provisioning/worker.ts](../../src/lib/server/provisioning/worker.ts) currently gives Create and Destroy separate paths that bypass the runtime requester eligibility check. Autoscaling ownership and policy checks must cover both paths, including delayed execution. A source label alone is insufficient. |
| Saved keys | [server-keys.ts](../../src/lib/server/fleet/server-keys.ts) stores keys in game pools. Each live or pending launch reserves a key until cleanup confirms it is safe to reuse. Allocate lobby identities numbered 1–20 independently of the key label. |
| Collections | [collections.ts](../../src/lib/server/fleet/collections.ts) already counts queued creations and launches promoted recipes. Exact matching can remove lobbies across the fleet, including maps omitted from the collection. Add ownership checks to preview and submission. |
| Hosting insights | [metrics-insights.ts](../../src/lib/metrics-insights.ts) provides historical suggestions for hosting trials. Use these to help choose policies; the live evaluator needs current empty-lobby evidence and must not execute historical suggestions directly. |
| Saved keys | [server-keys.ts](../../src/lib/server/fleet/server-keys.ts) stores keys in game pools. Each live or pending launch reserves a key until cleanup confirms it is safe to reuse. Allocate lobby identities numbered 1–20 independently of the key label. |
| Collections | [collections.ts](../../src/lib/server/fleet/collections.ts) already counts queued creations and launches promoted recipes. Exact matching can remove lobbies across the fleet, including maps omitted from the collection. Add ownership checks to preview and submission. |
| Hosting insights | [metrics-insights.ts](../../src/lib/metrics-insights.ts) provides historical suggestions for hosting trials. Use these to help choose policies; the live evaluator needs current empty-lobby evidence and must not execute historical suggestions directly. |
Create already uses the promoted launch recipe and capacity-aware placement. Start retains existing files and has recovery concurrency controls, but would need equivalent CPU and memory admission before becoming an autoscaling strategy. The proposed scope follows option 4 in [the idle CPU research](../research/t5-t6-idle-cpu-research.md).
@@ -31,15 +31,15 @@ Create already uses the promoted launch recipe and capacity-aware placement. Sta
Use one policy per `(game, mapSlug)`, preserving configured variants such as T6 start locations. Do not group ambiguous legacy records by engine map alone. Resolve or exclude ambiguous identities before activation.
| Setting | Proposed initial behavior |
| --- | --- |
| Mode | Off, Dry run, Create only, or Create and destroy. Default Off. |
| Minimum running lobbies | Default 1; a floor for desired ready capacity, subject to available keys and host capacity. |
| Maximum lobbies | Required explicit limit, at most 20. Count occupied lobbies, registered identities, reservations, and unresolved creations toward admission. |
| Empty spare target | Default 1. Count empty ready lobbies, not unfilled player slots. |
| Empty duration before removal | Default 15 minutes; adjustable after the pilot. |
| Action cooldown | Default 5 minutes between new policy actions, measured from durable admission. Cancellation and safety checks do not wait for cooldown. |
| Launch settings | Reuse normal map recipes and automatic placement on eligible active hosts. Use current promoted files when provisioning begins. |
| Setting | Proposed initial behavior |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Mode | Off, Dry run, Create only, or Create and destroy. Default Off. |
| Minimum running lobbies | Default 1; a floor for desired ready capacity, subject to available keys and host capacity. |
| Maximum lobbies | Required explicit limit, at most 20. Count occupied lobbies, registered identities, reservations, and unresolved creations toward admission. |
| Empty spare target | Default 1. Count empty ready lobbies, not unfilled player slots. |
| Empty duration before removal | Default 15 minutes; adjustable after the pilot. |
| Action cooldown | Default 5 minutes between new policy actions, measured from durable admission. Cancellation and safety checks do not wait for cooldown. |
| Launch settings | Reuse normal map recipes and automatic placement on eligible active hosts. Use current promoted files when provisioning begins. |
Validate integer counts with `1 <= minimum <= maximum <= 20` and `1 <= spare target <= maximum`. Show the game pool’s saved and available counts independently of each map’s maximum. Policies can be saved with fewer keys than the maximum, or with no keys yet. Exhaustion holds growth across every map sharing that game, while eligible surplus removal can still return capacity. Game-wide targets are per map, but game keys are shared across all maps and launch paths.
@@ -121,20 +121,20 @@ Complete and validate each phase in a separate commit before starting the next.
Required behavioral scenarios across these phases:
| Scenario | Expected result |
| --- | --- |
| Last spare fills, including a high-round partially filled session | One create when eligible; free player slots do not count as spares. |
| Create is queued, starting, or awaiting verification | Reservation counts once; it is not advertised as ready and no duplicate is queued. |
| Every eligible host is full or unavailable | Existing job waits; other policies and worker tasks continue. |
| Game key pool is empty or exhausted | Hold growth with a clear reason; other games remain independent and eligible surplus removal can proceed. |
| A launch is queued, stopped, failed, or uncertain | Its key stays reserved until confirmed cleanup; no other launch can use that key concurrently. |
| Occupancy evidence conflicts, expires, or arrives from an old lifecycle | Positive current occupancy wins; invalid evidence cannot establish emptiness. |
| Players join before claim, after dispatch, or while the agent holds removal | Queued work is withdrawn where possible; agent safety prevents occupied destruction; held autoscaling intent cannot execute later without a fresh decision. |
| Process restarts before or after intent submission | Existing operation is recovered by durable identity without replay or duplicate admission. |
| Collection exact match omits a managed map | Preview and submission report the ownership conflict, including stale-preview races. |
| Beta, campaign, maintenance, host drain, or recovery begins after evaluation | Admission rechecks block scaling and existing work retains its lifecycle ownership. |
| Operator pauses, changes limits, or cancels a policy job | Unstarted intent is invalidated; unsettled commands stay visible and keep reservations until reconciled. |
| A name is reused after confirmed deletion | Old observations, cooldown decisions, and commands cannot apply to the replacement generation. |
| Scenario | Expected result |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Last spare fills, including a high-round partially filled session | One create when eligible; free player slots do not count as spares. |
| Create is queued, starting, or awaiting verification | Reservation counts once; it is not advertised as ready and no duplicate is queued. |
| Every eligible host is full or unavailable | Existing job waits; other policies and worker tasks continue. |
| Game key pool is empty or exhausted | Hold growth with a clear reason; other games remain independent and eligible surplus removal can proceed. |
| A launch is queued, stopped, failed, or uncertain | Its key stays reserved until confirmed cleanup; no other launch can use that key concurrently. |
| Occupancy evidence conflicts, expires, or arrives from an old lifecycle | Positive current occupancy wins; invalid evidence cannot establish emptiness. |
| Players join before claim, after dispatch, or while the agent holds removal | Queued work is withdrawn where possible; agent safety prevents occupied destruction; held autoscaling intent cannot execute later without a fresh decision. |
| Process restarts before or after intent submission | Existing operation is recovered by durable identity without replay or duplicate admission. |
| Collection exact match omits a managed map | Preview and submission report the ownership conflict, including stale-preview races. |
| Beta, campaign, maintenance, host drain, or recovery begins after evaluation | Admission rechecks block scaling and existing work retains its lifecycle ownership. |
| Operator pauses, changes limits, or cancels a policy job | Unstarted intent is invalidated; unsettled commands stay visible and keep reservations until reconciled. |
| A name is reused after confirmed deletion | Old observations, cooldown decisions, and commands cannot apply to the replacement generation. |
Run targeted Vitest coverage for planner, collections, provisioning, cancellation, telemetry, and routes, plus Python agent tests for the removal contract. Before each implementation phase is committed, run the relevant checks; before live release, run `npm run check`, `npm test`, `npm run test:agent`, and `npm run build`. Verify the UI with keyboard navigation and narrow layouts, including stale evidence, blocked capacity, and partial cancellation states.
@@ -21,14 +21,14 @@ its own.
## Scope and defaults
| Area | First release |
| --- | --- |
| Leaderboard periods | All time, This week, and This month, with previous/next period navigation. Keep All time as the default. |
| Calendar | Weeks start Monday at 00:00 UTC; months start on the first at 00:00 UTC. Show the boundaries explicitly. Personal timezone preferences format timestamps without changing competition membership. |
| Personal bests | Highest round, best game kills, and highest reported score, compared within the same game and map variant. |
| Sharing | A preview and PNG download for a match summary or one player's result, plus an authenticated match link. Native device sharing is an optional enhancement with download/copy fallbacks. |
| Visual direction | Existing dark surfaces, electric-blue accent, typography, and brand mark, extended with original SVG map emblems and consistent game/status treatments. |
| Initial visual coverage | Fleet, server detail, Autoscaling, provisioning and host operation history, player highlights, and match/share cards. |
| Area | First release |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Leaderboard periods | All time, This week, and This month, with previous/next period navigation. Keep All time as the default. |
| Calendar | Weeks start Monday at 00:00 UTC; months start on the first at 00:00 UTC. Show the boundaries explicitly. Personal timezone preferences format timestamps without changing competition membership. |
| Personal bests | Highest round, best game kills, and highest reported score, compared within the same game and map variant. |
| Sharing | A preview and PNG download for a match summary or one player's result, plus an authenticated match link. Native device sharing is an optional enhancement with download/copy fallbacks. |
| Visual direction | Existing dark surfaces, electric-blue accent, typography, and brand mark, extended with original SVG map emblems and consistent game/status treatments. |
| Initial visual coverage | Fleet, server detail, Autoscaling, provisioning and host operation history, player highlights, and match/share cards. |
Season rewards, player-account linking, public profile/match pages, notification
subscriptions, rank-change tracking, custom themes, and photographic map artwork
@@ -212,13 +212,13 @@ styling, and document their use.
### Information order and status consistency
| Surface | Presentation changes |
| --- | --- |
| Fleet cards | Map/lobby/game first; health, player count, and round next; roster and match duration below; lower-priority timing and technical identity in supporting details. Preserve compact empty cards, search, pins, table mode, and the match spotlight. |
| Server detail | Add map/game identity to the shared header with health. Keep player counts, round, and timing in the Live content so Configuration and Diagnostics remain focused on their task. |
| Autoscaling | Use shared identity and mode badges, a compact count strip, and a prominent current decision/blocker. Keep per-map targets, Off/On setting, ownership, global pause, and unsettled operations visible. Reuse existing evidence/history disclosures. |
| Provisioning and host operations | Use consistent labels and status components while preserving action, target, result, elapsed time, and specific wait reasons. Put long IDs, source paths, and low-level metadata behind explicit details/copy controls. |
| Player and match results | Reuse map identity and achievement treatment across profile highlights, match results, and downloaded cards. Distinguish a current record from a record earned at that time. |
| Surface | Presentation changes |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fleet cards | Map/lobby/game first; health, player count, and round next; roster and match duration below; lower-priority timing and technical identity in supporting details. Preserve compact empty cards, search, pins, table mode, and the match spotlight. |
| Server detail | Add map/game identity to the shared header with health. Keep player counts, round, and timing in the Live content so Configuration and Diagnostics remain focused on their task. |
| Autoscaling | Use shared identity and mode badges, a compact count strip, and a prominent current decision/blocker. Keep per-map targets, Off/On setting, ownership, global pause, and unsettled operations visible. Reuse existing evidence/history disclosures. |
| Provisioning and host operations | Use consistent labels and status components while preserving action, target, result, elapsed time, and specific wait reasons. Put long IDs, source paths, and low-level metadata behind explicit details/copy controls. |
| Player and match results | Reuse map identity and achievement treatment across profile highlights, match results, and downloaded cards. Distinguish a current record from a record earned at that time. |
Align status presentation by meaning, while preserving domain-specific labels.
For example, `StatusPip` currently colors starting/provisioning amber while shared
@@ -238,15 +238,15 @@ Complete, validate, and commit each phase before starting the next. Finish the
player-achievement workstream before starting the visual refresh. Commit any
unrelated fix separately.
| Phase | Reviewable result | Acceptance gate |
| --- | --- | --- |
| A1 Completion time and period queries | Stable completion field/migration, period parsing, scoped ranking/evidence/profile queries, and cache keys. | Boundary, correction, qualification, and all-time compatibility tests pass; query plans and representative timing are recorded. |
| A2 Period navigation | Week/month controls, history navigation, period metadata, profile context, and stable share URLs. | Direct links, back/forward, rollover, empty periods, and inspection-preserving refresh work on desktop and mobile. |
| A3 Personal best highlights | Derived milestones, profile section, and match annotations. | Strict improvements, ties, first results, variants, corrections, and undated history behave as specified; long player histories remain practical. |
| A4 Result sharing | Accessible preview, PNG download, authenticated copy-link action, and supported native sharing. | Export matches the preview, contains only selected result data, handles long/incomplete data, and works after live-server deletion. Update leaderboard documentation and commit the completed workstream. |
| B1 Shared visual identity | Original emblems, resolver, game labels, achievement tokens, and usage guidance. | Every map selection resolves correctly or falls back; no misleading variant, color-only meaning, or missing-asset layout shift. |
| B2 Fleet and operational presentation | Apply identity/hierarchy/status rules to Fleet, server detail, Autoscaling, provisioning, and host operation history. | Existing actions and critical states remain visible and usable; no new polling or behavior changes. |
| B3 Player presentation and review | Apply the shared visual identity to player highlights, match pages, and export layouts; refresh documentation images. | Complete the visual/accessibility review and update UI guidance, then commit the completed workstream. |
| Phase | Reviewable result | Acceptance gate |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A1 Completion time and period queries | Stable completion field/migration, period parsing, scoped ranking/evidence/profile queries, and cache keys. | Boundary, correction, qualification, and all-time compatibility tests pass; query plans and representative timing are recorded. |
| A2 Period navigation | Week/month controls, history navigation, period metadata, profile context, and stable share URLs. | Direct links, back/forward, rollover, empty periods, and inspection-preserving refresh work on desktop and mobile. |
| A3 Personal best highlights | Derived milestones, profile section, and match annotations. | Strict improvements, ties, first results, variants, corrections, and undated history behave as specified; long player histories remain practical. |
| A4 Result sharing | Accessible preview, PNG download, authenticated copy-link action, and supported native sharing. | Export matches the preview, contains only selected result data, handles long/incomplete data, and works after live-server deletion. Update leaderboard documentation and commit the completed workstream. |
| B1 Shared visual identity | Original emblems, resolver, game labels, achievement tokens, and usage guidance. | Every map selection resolves correctly or falls back; no misleading variant, color-only meaning, or missing-asset layout shift. |
| B2 Fleet and operational presentation | Apply identity/hierarchy/status rules to Fleet, server detail, Autoscaling, provisioning, and host operation history. | Existing actions and critical states remain visible and usable; no new polling or behavior changes. |
| B3 Player presentation and review | Apply the shared visual identity to player highlights, match pages, and export layouts; refresh documentation images. | Complete the visual/accessibility review and update UI guidance, then commit the completed workstream. |
## Validation
+9 -9
View File
@@ -20,16 +20,16 @@ active, interrupted and nonqualifying records.
On the local Intel i5-8279U / Node 25.9.0 / SQLite 3.49.2 test environment, three
measured runs after warmup gave these median query times on the large fixture:
| Request | Before | After |
| --- | ---: | ---: |
| Request | Before | After |
| ------------------------ | -------: | -----: |
| Highest round, all games | 1,717 ms | 829 ms |
| Total kills, all games | 1,555 ms | 858 ms |
| One game | 899 ms | 491 ms |
| One map | 345 ms | 224 ms |
| Player search | 1,515 ms | 757 ms |
| Deep page | 1,421 ms | 752 ms |
| Headshot percentage | 1,444 ms | 760 ms |
| Exact player rank | 1,524 ms | 554 ms |
| Total kills, all games | 1,555 ms | 858 ms |
| One game | 899 ms | 491 ms |
| One map | 345 ms | 224 ms |
| Player search | 1,515 ms | 757 ms |
| Deep page | 1,421 ms | 752 ms |
| Headshot percentage | 1,444 ms | 760 ms |
| Exact player rank | 1,524 ms | 554 ms |
The small fixture's unfiltered request fell from 41 to 30 ms. All 150 additional
small-fixture parity cases and 22 timed small/large request cases matched the
+18 -18
View File
@@ -26,16 +26,16 @@ The live host kernel, Wine version, image digest, installed scripts/plugins, CPU
## Options
| Option | Expected effect on idle CPU | Availability and gameplay tradeoff | Evidence and priority |
| --- | --- | --- | --- |
| 1. Enable and verify NTSYNC | Potentially substantial if Wine synchronization dominates | Intended to retain normal joins and gameplay; compatibility testing required | Official Wine/kernel support; Plutonium operator anecdote. First trial |
| 2. Audit busy-wait settings, scripts, and helpers | Small to substantial, depending on the actual cause | Usually compatible with keeping lobbies online | Documented settings plus diagnostic experiments. First trial |
| 3. Apply CPU quotas only when empty | Enforceable ceiling on each empty container's CPU time | Excessive throttling can delay joins, RCON, and heartbeats | Docker-supported mechanism; automatic policy needs development |
| 4. Run fewer spare lobbies | Removes the stopped instances' process CPU use | Fewer immediately available lobbies | Predictable mechanism; best larger saving if acceptable |
| 5. Start on request or on a schedule | Removes process CPU use during stopped periods | Players wait for startup; stopped servers cannot provide live listings | Practical alternative for infrequent maps or private sessions |
| 6. Pause and resume containers | Suspends their process execution while retaining memory | Server cannot answer while paused; resume behavior needs validation | Docker supports pause; Plutonium behavior unproven |
| 7. Wake through a UDP gateway | Could combine stopped backends with automated wakeup | Protocol, listing, session, and timeout problems | Experimental; substantially harder for T5 |
| 8. Change the host/runtime or develop engine idling | Potentially substantial, cause dependent | Migration cost or ongoing engine compatibility work | Conditional alternatives after profiling |
| Option | Expected effect on idle CPU | Availability and gameplay tradeoff | Evidence and priority |
| --------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| 1. Enable and verify NTSYNC | Potentially substantial if Wine synchronization dominates | Intended to retain normal joins and gameplay; compatibility testing required | Official Wine/kernel support; Plutonium operator anecdote. First trial |
| 2. Audit busy-wait settings, scripts, and helpers | Small to substantial, depending on the actual cause | Usually compatible with keeping lobbies online | Documented settings plus diagnostic experiments. First trial |
| 3. Apply CPU quotas only when empty | Enforceable ceiling on each empty container's CPU time | Excessive throttling can delay joins, RCON, and heartbeats | Docker-supported mechanism; automatic policy needs development |
| 4. Run fewer spare lobbies | Removes the stopped instances' process CPU use | Fewer immediately available lobbies | Predictable mechanism; best larger saving if acceptable |
| 5. Start on request or on a schedule | Removes process CPU use during stopped periods | Players wait for startup; stopped servers cannot provide live listings | Practical alternative for infrequent maps or private sessions |
| 6. Pause and resume containers | Suspends their process execution while retaining memory | Server cannot answer while paused; resume behavior needs validation | Docker supports pause; Plutonium behavior unproven |
| 7. Wake through a UDP gateway | Could combine stopped backends with automated wakeup | Protocol, listing, session, and timeout problems | Experimental; substantially harder for T5 |
| 8. Change the host/runtime or develop engine idling | Potentially substantial, cause dependent | Migration cost or ongoing engine compatibility work | Conditional alternatives after profiling |
### 1. Test NTSYNC before treating Wine overhead as unavoidable
@@ -155,15 +155,15 @@ First record the actual host/VM details, kernel, image digest, Wine version, Plu
Use one representative instance from each game, then expand successful changes to other maps. Suggested measurement windows are 10–15 minutes after startup settles, repeated after players join and all leave. These are proposed test windows, not product requirements.
| Test | What it resolves |
| --- | --- |
| Normal deployment, empty immediately after startup | Initial baseline |
| Same instance after the last player disconnects | Whether work accumulates or persists after play |
| Minimal scripts/plugins on the same map | Contribution from custom behavior |
| Verified NTSYNC versus current synchronization path | Wine synchronization contribution |
| One idle quota at a time, with repeated fresh joins | Lowest useful ceiling without degraded access |
| Test | What it resolves |
| ---------------------------------------------------------- | --------------------------------------------------------- |
| Normal deployment, empty immediately after startup | Initial baseline |
| Same instance after the last player disconnects | Whether work accumulates or persists after play |
| Minimal scripts/plugins on the same map | Contribution from custom behavior |
| Verified NTSYNC versus current synchronization path | Wine synchronization contribution |
| One idle quota at a time, with repeated fresh joins | Lowest useful ceiling without degraded access |
| Stop/start or pause/resume after brief and long inactivity | Real wake latency, listing recovery, and join reliability |
| Occupied session with ordinary and high-round gameplay | Whether the change preserves active performance |
| Occupied session with ordinary and high-round gameplay | Whether the change preserves active performance |
Collect container CPU averages and peaks, process/thread CPU, memory, quota throttling, RCON response times/failures, external browser visibility, first-join success, and time until players can actually play. Measure the whole host as well to catch work shifted into a proxy or management process. Use unprofiled runs for final CPU comparisons because tracing itself adds overhead.