475 lines
23 KiB
Markdown
475 lines
23 KiB
Markdown
# tws-t5-aio
|
|
|
|
Custom HUD + persistent bank + AFK toggle + immediate join + lobby lock + game telemetry for Plutonium T5
|
|
(Black Ops 1) zombies dedicated servers. Split across eight `.gsc` files,
|
|
each installable / removable in isolation (with `tws_common` as the shared
|
|
hub).
|
|
|
|
Platform quirks and deploy commands: [NOTES.md](NOTES.md).
|
|
|
|
## Files
|
|
|
|
| File | Role |
|
|
|---|---|
|
|
| [tws_common.gsc](tws_common.gsc) | Shared config, HUD helpers, spawn-handler registry, single `connected` listener + dispatcher |
|
|
| [tws_db.gsc](tws_db.gsc) | MariaDB pool config + reachability probe |
|
|
| [tws_hud.gsc](tws_hud.gsc) | Bottom-left HUD stack: timers, zombie count, health bar |
|
|
| [tws_bank.gsc](tws_bank.gsc) | Persistent per-player bank + console commands |
|
|
| [tws_afk.gsc](tws_afk.gsc) | `.afk` / `.back` chat toggle + `[AFK]` indicator |
|
|
| [tws_join.gsc](tws_join.gsc) | `.join` lets newcomers spawn before the current round ends |
|
|
| [tws_lock.gsc](tws_lock.gsc) | Lobby `g_password` lock at a milestone round |
|
|
| [tws_telemetry.gsc](tws_telemetry.gsc) | Direct HTTP game start, round, heartbeat, and game end reports |
|
|
|
|
## Dependency graph
|
|
|
|
Arrows read "depends on" — solid means the source names the target with
|
|
`scripts\sp\tws_common::...`, dashed means the source waits on a level
|
|
notify fired by the target. Transitive edges are elided (so `tws_bank`
|
|
and `tws_lock` reach `tws_common` through `tws_db` / `tws_hud` on the
|
|
graph, but they also call `tws_common` directly).
|
|
|
|
```mermaid
|
|
graph BT
|
|
common[tws_common<br/><i>config + helpers + dispatcher</i>]
|
|
|
|
db[tws_db]
|
|
hud[tws_hud]
|
|
afk[tws_afk]
|
|
join[tws_join]
|
|
bank[tws_bank]
|
|
lock[tws_lock]
|
|
telemetry[tws_telemetry]
|
|
|
|
db --> common
|
|
hud --> common
|
|
afk --> common
|
|
join --> common
|
|
telemetry --> common
|
|
|
|
bank -. db_probe_done .-> db
|
|
lock -. hud_round_changed<br/>hud_game_over .-> hud
|
|
```
|
|
|
|
- `tws_common` is the only module named by other files at compile time.
|
|
- All other cross-module coupling flows through level notifies
|
|
(`db_probe_done`, `hud_round_changed`, `hud_match_started`,
|
|
`hud_game_over`) or through spawn handlers registered with
|
|
`tws_common::register_spawn_handler`.
|
|
- Any file except `tws_common` can be removed; the rest still load.
|
|
`tws_afk` has no downstream consumers — pull it independently.
|
|
`tws_bank` needs `tws_db`; `tws_lock` needs `tws_hud`.
|
|
|
|
## Runtime notifies
|
|
|
|
| Notify | Fired by | Read by |
|
|
|---|---|---|
|
|
| `db_probe_done` | `tws_db` | `tws_bank` (via `bank_deferred_start`) |
|
|
| `hud_round_changed` | `tws_hud` | `tws_hud` (round-timer reset), `tws_lock` |
|
|
| `hud_match_started` | `tws_hud` | `tws_hud` (match-timer reveal) |
|
|
| `hud_game_over` | `tws_hud` | `tws_hud` (freeze timers), `tws_lock` (clear password) |
|
|
|
|
---
|
|
|
|
## tws_common.gsc
|
|
|
|
Hub file. Shared config, HUD helpers, spawn-handler registry, and the
|
|
single `level waittill("connected")` in the fleet. Adding a second
|
|
connect listener anywhere else crashes the T5 SP handshake.
|
|
|
|
**Features**
|
|
- Shared HUD constants: `level.hud_x`, `hud_y`, `hud_row_*`, `hud_col_*`,
|
|
`hud_font_*`, `hud_bar_*`, `hud_c_*`, `hud_rate`, `hud_health_threshold`
|
|
- Spawn-handler registry
|
|
- Connect listener + per-player dispatch: iterates
|
|
`level.tws_spawn_handlers` after `spawned_player` and threads each on
|
|
the player
|
|
|
|
**Provides**
|
|
| Function | Purpose |
|
|
|---|---|
|
|
| `common_config()` | Populate every `level.hud_*` (idempotent) |
|
|
| `ensure_config()` | No-op if config already set; called by every module's `init` |
|
|
| `register_spawn_handler(fn)` | Appends `::fn` to `level.tws_spawn_handlers` |
|
|
| `make_text_elem(p, x, y, ax, s, c)`| `NewClientHudElem` text element with black-glow outline |
|
|
| `make_shader_elem(p, x, y)` | `NewClientHudElem` shader element |
|
|
| `destroy_on_disconnect(elems)` | Waits on `disconnect`, destroys the array |
|
|
| `int_dvar_or(name, fallback)` | Parse an integer dvar with default |
|
|
| `format_mmss(elapsed_ms)` | Format milliseconds as `M:SS` |
|
|
|
|
**Depends on**: nothing (leaf module).
|
|
|
|
---
|
|
|
|
## tws_db.gsc
|
|
|
|
**Features**
|
|
- One MariaDB connection pool for the whole script (used by bank now,
|
|
leaderboard later)
|
|
- Reachability probe: round-trips `SELECT 1` and flips `level.db_online`
|
|
- Fires `db_probe_done` so waiters can proceed regardless of outcome
|
|
|
|
**Provides**
|
|
| Level var / notify | Meaning |
|
|
|---|---|
|
|
| `level.db_online` | `true` iff the probe query returned without error |
|
|
| `level.db_probe_done` | `true` once the probe has completed (either way) |
|
|
| notify `db_probe_done` | Fired once after probe completion |
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_common::ensure_config()`
|
|
- `t5-gsc-utils` (`mysql::set_config`, `mysql::query`)
|
|
|
|
**Config (currently hardcoded — TODO: dvarize before repo goes public)**
|
|
- Host `mariadb`, port `3306`, DB `tws_zm`, user `josh`, password `joshster`
|
|
|
|
---
|
|
|
|
## tws_hud.gsc
|
|
|
|
Bottom-left cluster. Per-player elements spawn via `::hud_start`.
|
|
|
|
**Features**
|
|
- Match timer + round timer (round resets each round, both freeze on game over)
|
|
- Player-name row (only visible in co-op)
|
|
- Zombies-remaining counter
|
|
- Health bar with green / amber / red ramp; numbers fade in below full HP
|
|
- Round-transition and game-over notify machinery
|
|
|
|
**Provides**
|
|
| Notify / level var | Meaning |
|
|
|---|---|
|
|
| notify `hud_round_changed` | Fires each time `level.round_number` transitions |
|
|
| notify `hud_match_started` | Fires once when the match clock seeds |
|
|
| notify `hud_game_over` | Fires on the first `end_game` / `game_ended` / `intermission` notify |
|
|
| `level.round_start_time` | Anchored at first observed round transition |
|
|
| `level.match_start_time` | Anchored at first observed round-1 (or on first player spawn) |
|
|
|
|
Spawn handler registered: `::hud_start` — threads `hud_timer_row`,
|
|
`hud_zombie_counter`, `hud_health_display`.
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`,
|
|
`make_shader_elem`, `destroy_on_disconnect`, `format_mmss`,
|
|
`register_spawn_handler`
|
|
- Every `level.hud_*` config value from `tws_common`
|
|
- Stock BO1 state: `level.round_number`, `level.zombie_total`,
|
|
`level.players`, `self.health`, `self.maxhealth`, `self.name` /
|
|
`self.playername`, AI entities' `.animname` and `.ignore_enemy_count`
|
|
|
|
---
|
|
|
|
## tws_bank.gsc
|
|
|
|
Persistent per-player bank. Balances survive server hops within the fleet
|
|
because rows are keyed on XUID.
|
|
|
|
**Features**
|
|
- Server-console commands: `bank`, `deposit <amount>`, `withdraw <amount>`
|
|
- DB-first / memory-second: `self.bank` and `self.score` only move after
|
|
the DB write confirms
|
|
- Deposits: `INSERT ... ON DUPLICATE KEY UPDATE` — lazy row create, atomic
|
|
- Withdrawals: `WHERE balance >= ?` — DB blocks overdrafts under concurrency
|
|
- Score-drift refund guard on deposit
|
|
- `bank_load` currently a **diagnostic stub** (bare-minimum to isolate the
|
|
connect-handshake crash — do not restore the stripped body until it passes
|
|
join)
|
|
|
|
**Provides**
|
|
| Command | Args | Effect |
|
|
|---|---|---|
|
|
| `bank` | — | Prints current `Bank` and `Score` to the caller |
|
|
| `deposit` | `<amount>` | Score → bank, DB-first with refund on race |
|
|
| `withdraw` | `<amount>` | Bank → score, DB-first with overdraft guard |
|
|
|
|
Per-player state: `self.bank` (int), `self.bank_unavailable` (bool),
|
|
`self.bank_guid` (set in the full impl — currently unset by the stub).
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_common::` — `ensure_config`, `register_spawn_handler`
|
|
- `tws_db` — waits on `db_probe_done`, gates on `level.db_online`
|
|
- `t5-gsc-utils` — `mysql::query`, `mysql::prepared_statement`,
|
|
`command::add_sv`
|
|
- Stock BO1: `self.score`
|
|
- Dvar: `tws_server_id` (optional; labels `last_server` on bank rows)
|
|
- External: MariaDB schema — `bank(guid, balance, last_name, last_server)`
|
|
|
|
Spawn handler registered: `::bank_load`.
|
|
|
|
---
|
|
|
|
## tws_afk.gsc
|
|
|
|
Self-invoked pause mode. Owns the `[AFK]` indicator element entirely — HUD
|
|
module has zero knowledge of AFK.
|
|
|
|
**Features**
|
|
- Chat commands `.afk` and `.back` (via `level waittill("say")` — not
|
|
`callbackPlayerSay`, which never fires on T5 SP dedicated)
|
|
- On enter: `enableInvulnerability`, `freezeControls(true)`,
|
|
`self.ignoreme = true` (drops out of zombie targeting)
|
|
- On exit: reverse, then a 2s grace tail keeps invuln + ignoreme through
|
|
the target-reacquisition window
|
|
- `[AFK]` HUD element created per-player via spawn handler; alpha-toggled
|
|
by enter / exit
|
|
|
|
**Provides**
|
|
- Per-player: `self.hud_afk_on` (bool), `self.hud_afk_elem` (HUD element),
|
|
writes `self.ignoreme` (stock BO1 targeting flag)
|
|
- Level var: `level.afk_grace_seconds` (default 2)
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`,
|
|
`destroy_on_disconnect`, `register_spawn_handler`
|
|
- Shared config: `level.hud_x`, `level.hud_row_match`,
|
|
`level.hud_font_small`, `level.hud_c_afk`
|
|
- Stock BO1: `enableInvulnerability`, `disableInvulnerability`,
|
|
`freezeControls`, `.ignoreme`, `level waittill("say", message, player)`
|
|
|
|
Spawn handler registered: `::afk_spawn`.
|
|
|
|
---
|
|
|
|
## tws_join.gsc
|
|
|
|
Type `.join` in chat while spectating after joining a match in progress to
|
|
spawn immediately. Install with `tws_common.gsc`; no HUD or DB dependency.
|
|
|
|
**Eligibility**
|
|
|
|
- Only before the player's first playable spawn on the current connection.
|
|
- A normal spawn or successful `.join` permanently closes eligibility for
|
|
that connection. Getting downed, bleeding out, or changing rounds never
|
|
restores it. Dead players must wait for the normal round-end respawn.
|
|
- Requests during connection setup, without a living teammate, or after
|
|
game over cannot spawn a player. Repeated `.join` requests cannot create
|
|
overlapping spawns.
|
|
- This is connection-local tracking; reconnecting creates a new player and
|
|
resets eligibility. It is not an account-level reconnect restriction.
|
|
|
|
Uses the stock `level.spawnPlayer` entry point and preserves map-specific
|
|
`level.custom_spawnPlayer` callbacks. If absent, installs the stock zombies
|
|
spectator callback, as the round-end routine does. Missing spectator spawn
|
|
locations are seeded from the map's `initial_spawn_points`.
|
|
|
|
Spawn handler registered: `::join_spawn`. Stock `self.initialized` provides
|
|
an additional first-spawn guard; `hasSpawned` cannot distinguish spectators.
|
|
See the [stock zombies lifecycle](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode.gsc)
|
|
and [spawn entry point](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_callbackglobal.gsc).
|
|
|
|
**Live validation (requires a T5 server)**
|
|
|
|
1. Join during an active round, type `.join`, and verify one playable spawn
|
|
with normal weapons, controls, zombie targeting, and HUD initialization.
|
|
Check both round 1 and a later round, including each deployed map.
|
|
2. Repeat `.join` rapidly; verify no duplicate spawns. Try it alive, while
|
|
downed, and after bleeding out; verify no revive or respawn.
|
|
3. Join and wait for the normal round-end spawn instead; after dying, verify
|
|
`.join` remains blocked, including on subsequent rounds.
|
|
4. Try during round transition and game over; verify no duplicate spawn or
|
|
restart of an ended match. Check that `.afk` / `.back` still work.
|
|
|
|
---
|
|
|
|
## tws_lock.gsc
|
|
|
|
**Features**
|
|
- Sets `g_password` to a random per-lobby value when the round crosses a
|
|
milestone (default: round 20)
|
|
- Existing players are unaffected; `g_password` is only checked at connect
|
|
- Clears `g_password` on `hud_game_over` so the next lobby opens unlocked
|
|
- Trigger round is dvar-driven — retune without redeploying
|
|
|
|
**Provides**
|
|
| Level var | Meaning |
|
|
|---|---|
|
|
| `level.lock_round` | Round at which the lobby locks (read at init) |
|
|
| `level.lock_active` | `true` after the password has been set |
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_common::` — `ensure_config`, `int_dvar_or`
|
|
- `tws_hud` — waits on `hud_round_changed` and `hud_game_over`
|
|
- Stock BO1: `level.round_number`
|
|
- Dvar: `tws_lock_round` (int, default `20`)
|
|
|
|
---
|
|
|
|
## Dvars
|
|
|
|
| Dvar | Consumer | Default | Purpose |
|
|
|---|---|---|---|
|
|
| `zombiemode` | every module | — | Gate: every `init()` returns early unless `"1"` |
|
|
| `tws_lock_round` | `tws_lock` | `20` | Round at which the lobby locks |
|
|
| `tws_server_id` | `tws_bank`, `tws_telemetry` | Required for telemetry | Exact registered Electric Cherry server name; also used as the bank label |
|
|
| `tws_telemetry_enabled` | `tws_telemetry` | `1` | Set to `0` to disable telemetry at init |
|
|
| `tws_telemetry_url` | `tws_telemetry` | `http://192.168.1.199:3000/api/telemetry` | Electric Cherry telemetry endpoint for JSON POSTs |
|
|
| `tws_telemetry_log_level` | `tws_telemetry` | `1` | Read live: `0` warnings/errors/recovery only, `1` lifecycle/request/health logs, `2` also capture/queue logs and response previews |
|
|
|
|
## tws_telemetry.gsc
|
|
|
|
Uses `t5-gsc-utils` HTTP/JSON components to POST directly to Electric Cherry's
|
|
`/api/telemetry`. Install alongside `tws_common.gsc` in `storage/t5/scripts/sp/`.
|
|
No HUD or database dependency. Reports run whether or not a beta is active.
|
|
|
|
Example server config (except log level, read at script init; restart the map after changes):
|
|
|
|
```cfg
|
|
set tws_server_id "t5_zombie_prototype_1"
|
|
set tws_telemetry_enabled "1"
|
|
set tws_telemetry_url "http://192.168.1.199:3000/api/telemetry"
|
|
set tws_telemetry_log_level "1"
|
|
```
|
|
|
|
`tws_server_id` must match the registered server name exactly. If it is
|
|
unset, the script logs a configuration error and does not start reporting.
|
|
An unknown or destroyed server is rejected by Electric Cherry with HTTP 404.
|
|
|
|
The reporter emits exactly four event types:
|
|
|
|
| Event | When | Payload |
|
|
|---|---|---|
|
|
| `game_start` | First playable spawn, with the stock `begin_spawning` flag as a fallback | `server`, `map`, `players` with `name` and `guid` |
|
|
| `round_change` | First observed round after start, then each change (checked every 0.5 seconds) | `server`, `map`, `round`, full player stats |
|
|
| `heartbeat` | Every 30 seconds after `game_start`, until game end | Same fields as `round_change`, with current round and player stats |
|
|
| `game_end` | First stock `end_game`, `game_ended`, or `intermission` notification | `server`, `map`, final `round`, full player stats |
|
|
|
|
Each player in round/heartbeat/end reports has `name`, `guid`, `kills`, `headshots`,
|
|
`downs`, `revives`, and `score`. Names and GUIDs remain strings; GUIDs never
|
|
pass through numeric conversion. Round and stats are nonnegative integers,
|
|
with missing or negative stat values reported as zero. The four counters
|
|
come from T5's `player.stats`; `score` is the player's current spendable
|
|
`player.score`, rather than cumulative points earned. `map` is `mapname`.
|
|
|
|
Every report includes everyone returned by `getPlayers()`, including
|
|
spectators, downed players, and AFK players. Empty rosters are sent as `[]`.
|
|
Undefined players and players with missing/empty names or GUIDs are skipped
|
|
with a warning containing their roster index; a later snapshot can include
|
|
them once their identity is available.
|
|
The script builds the JSON array delimiters explicitly because the plugin
|
|
serializes an empty GSC array as `null`; player objects and strings still
|
|
use `jsonSerialize` for escaping.
|
|
|
|
```json
|
|
{"event":"game_start","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","players":[{"name":"Josh","guid":"12345678901234567"}]}
|
|
{"event":"round_change","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":17,"players":[{"name":"Josh","guid":"12345678901234567","kills":420,"headshots":160,"downs":1,"revives":3,"score":32000}]}
|
|
{"event":"heartbeat","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":17,"players":[{"name":"Josh","guid":"12345678901234567","kills":430,"headshots":165,"downs":1,"revives":3,"score":32500}]}
|
|
{"event":"game_end","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":18,"players":[{"name":"Josh","guid":"12345678901234567","kills":445,"headshots":170,"downs":2,"revives":3,"score":33500}]}
|
|
```
|
|
|
|
Events are captured immediately into an in-memory queue. One HTTP worker
|
|
POSTs each snapshot once, in gameplay order, with `Content-Type:
|
|
application/json`, waiting for completion before sending the next. Gameplay
|
|
and round detection continue while requests are pending. Repeated joins or
|
|
end notifications do not duplicate start/end reports. At game over, the
|
|
reporter captures any round change since the last poll, stops detection,
|
|
and lets the sender drain through `game_end`.
|
|
|
|
The first heartbeat is captured 30 seconds after match start, then every
|
|
30 seconds even when the round is unchanged or the roster is empty. Round
|
|
changes and later spawns do not reset the interval or add heartbeat workers.
|
|
Heartbeats use the same snapshot builder and ordered sender as round reports;
|
|
pending HTTP requests can delay transmission. Game end stops the heartbeat
|
|
worker, and already queued heartbeats drain before `game_end`.
|
|
|
|
There is no discovery, beta token, session ID, sequence field, custom event,
|
|
external helper, or separate beta acknowledgement. Delivery is best effort:
|
|
no retries or persistence across map reloads, and no cross-game exactly-once
|
|
guarantee. A pending request delays later reports; the current plugin has no
|
|
request timeout option. A map reload can discard queued reports.
|
|
|
|
Transport failures log to the server console. The plugin does not expose
|
|
HTTP status codes, so its success flag does not establish API acceptance.
|
|
Verify acceptance at Electric Cherry: success is HTTP 200 with `ok: true`
|
|
and telemetry containing `receivedAt`; invalid payloads return 400 and
|
|
non-JSON content types return 415.
|
|
|
|
**Diagnosing a lobby that stops reporting**
|
|
|
|
Default logging includes initialization, match start/end, each request's
|
|
event/round and local ID, queue wait and request duration in milliseconds,
|
|
transport failures/recovery, and the final queue drain. IDs reset on map
|
|
load and only appear in logs; payloads are unchanged. Every log includes
|
|
the server, map, and game time in milliseconds.
|
|
|
|
An independent watchdog prints health every 30 seconds, even before match
|
|
start and while HTTP is pending or the game-end queue is draining. It shows
|
|
the sender stage, current snapshot capture, pending queue size (excluding
|
|
the in-flight request), total queued, completed transports, failures, and
|
|
time since the last completion (`-1` means none). Warnings remain enabled
|
|
at log level `0`:
|
|
|
|
- `HTTP stalled`: a request has been pending for at least 30 seconds. The
|
|
warning repeats on watchdog ticks with its ID, event, age, and queue size.
|
|
- `sender is not consuming`: the oldest queued report has waited at least
|
|
30 seconds with no in-flight request.
|
|
- `round watcher has stopped advancing`: no polling progress for over
|
|
5 seconds, detected on the next watchdog tick.
|
|
- `no snapshot captured`: an active match has gone at least 65 seconds
|
|
without a new snapshot, despite its 30-second heartbeat.
|
|
|
|
Set `tws_telemetry_log_level "2"` in the server console while investigating;
|
|
no map restart is needed. This adds capture/queue stages and API response
|
|
previews, escaped onto one line and limited to the first 256 characters.
|
|
Look for `ok: false` and the rejection reason in those previews; HTML may
|
|
indicate a proxy error. Previews can include player identities echoed by
|
|
the API. Restore `1` afterward, or use `0` to retain only warnings, errors,
|
|
and transport recovery messages.
|
|
|
|
Missing request handles and reported transport failures are counted and
|
|
skipped so the sender can continue with the next event. Empty responses
|
|
produce a warning. Arbitrary response bodies are not parsed as JSON,
|
|
because malformed/proxy responses must not break the sender. The watchdog
|
|
does not cancel or retry stuck requests: the current plugin offers neither
|
|
timeout nor cancellation through GSC, and starting another request could
|
|
reorder events or accumulate native workers. A stall remains blocked until
|
|
the plugin completes it; these logs identify that condition, not fix it.
|
|
|
|
Requires a plugin build containing
|
|
[`http::request`](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/http.cpp)
|
|
and [`jsonSerialize`](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/json.cpp).
|
|
Disabling the dvar does not remove that compile-time dependency; remove the
|
|
script when using a plugin build without those functions.
|
|
|
|
Stat and lifecycle references: [stock T5 zombies](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode.gsc)
|
|
and [score tracking](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode_score.gsc).
|
|
|
|
**Live validation (requires a T5 server)**
|
|
|
|
1. Start a match and verify one `game_start`, then an initial `round_change`
|
|
and one per new round. Check the exact server name, map, string GUIDs,
|
|
all player identities, and all five stats against the game.
|
|
2. Join again in the same round, go AFK, become downed, and spectate. Verify
|
|
the next report includes the full roster without another start event.
|
|
3. End a match and verify one `game_end` with final stats after all round
|
|
reports, including if more than one stock end notification fires. Check
|
|
a round-0 intro and an empty roster, including last-player disconnect.
|
|
4. Delay HTTP completion while rounds advance and the match ends. Verify
|
|
requests remain ordered and retain the stats captured at each event.
|
|
With the API unreachable, verify transport-error logging and continued
|
|
gameplay. Repeat without an active beta and with HUD/DB modules removed.
|
|
5. Stay in one round for over a minute. Verify heartbeats at 30 and 60 seconds
|
|
after start, with current stats and the same fields as `round_change`.
|
|
Check that round changes and joins do not reset or duplicate the interval,
|
|
and no heartbeat follows `game_end`, including with requests pending.
|
|
6. Change log level between `0`, `1`, and `2` without a map restart. Check
|
|
request IDs pair sends with completions and level `2` shows bounded,
|
|
escaped response previews. Use an unregistered server name on a test
|
|
server and confirm the API rejection is visible in the response even
|
|
though transport completes. Also check an empty or HTML response.
|
|
7. Point a test server at an endpoint that accepts a connection but does
|
|
not reply. Verify recurring `HTTP stalled` warnings and increasing
|
|
pending counts while gameplay continues, including after game over.
|
|
Release the delayed reply and verify the queue resumes in order and
|
|
the watchdog stops after the final drain. Test a refused connection
|
|
separately to check failure counts and the recovery log once restored.
|
|
|
|
## External dependencies
|
|
|
|
- **[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils)** — required.
|
|
`tws_db` and `tws_bank` need the `mysql::` namespace; `tws_bank` also
|
|
needs `command::add_sv`.
|
|
- **MariaDB / MySQL server** reachable from the Plutonium container as
|
|
`mariadb:3306` (currently hardcoded — see `tws_db.gsc`).
|
|
- **Schema** — `bank(guid VARCHAR PRIMARY KEY, balance INT, last_name
|
|
VARCHAR, last_server VARCHAR)`. `tws_bank` lazily inserts rows on first
|
|
deposit.
|