# TWS T5 script notes Reference for the `tws_t5_*.gsc` files in this repo. Platform quirks and load-bearing decisions that show up as invariants in the code. ## Install Copy all `tws_t5_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server (v2 Plutainer: `app/runtime/plutonium/storage/t5/scripts/sp/`). - `sudo chown -R 1000:1000 ` — Plutainer runs as UID 1000 - `docker exec rcon-cli map_restart` — reloads scripts in place For existing installations, follow the [upgrade cleanup](README.md#install--upgrade) before copying the renamed scripts. ## Platform quirks (T5 SP engine, Plutonium) - **Initialize locals before loops when reading them afterwards.** T5 rejected leaderboard's `chosen` variable at load time when its only assignment was inside the map-search loop, even though its later use was guarded by the match count. It now starts as `undefined` before that loop in both versions. The JavaScript fixture harness and T6 compiler did not catch this T5 error. - **One `level waittill("connected")` only.** `tws_t5_common` owns it. A second listener breaks the T5 SP connect handshake. Per-player work goes through `tws_t5_common::register_spawn_handler` after `spawned_player`, or `register_connect_handler` for spectator-safe work after the dispatcher yields. Leaderboard uses the latter; its level worker owns all HTTP work. - **Chat uses `level waittill("say", ...)`**, not `callbackPlayerSay`. T5 SP dedicated disables the sound subsystem, cascading through the say-callback dispatch — `callbackPlayerSay` never fires. - **`SetTimerUp(N)` only ticks when `N == 0`** on T5 SP. Non-zero seeds render "0:00" but don't advance. Late joiners see 0, not real elapsed; authoritative elapsed lives in `level.match_start_time`. - **Dynamic text: use `SetValue`, not `SetText`.** `SetText` allocates a config string per call; per-tick rebuilds overflow and take the server down. Composite strings like `"87/160"` are three elements. - **`NewClientHudElem(player)`, not `NewHudElem()`.** `NewHudElem` is globally owned; in co-op every player's HUD draws on top of every other player's. Fall back to `NewHudElem` only if elements don't render at all (diagnostic). - **Zombies namespace is WaW-derived** (`maps\_zombiemode_*`), not BO2's `maps\mp\zombies\_zm_*`. - **Check `t5-gsc-utils` source as well as its README.** Current source includes `http::request` and `jsonSerialize` / `jsonParse`. Telemetry and leaderboard require a DLL built with these HTTP/JSON components; older builds may lack them. See [HTTP source](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/http.cpp) and [JSON source](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/json.cpp). ## Load-bearing decisions - **Match / round timers anchor to first spawn / first observed round transition — never level init.** Anchoring at init included the map-load + connect + intro window, producing "displayed 4:00 → frozen 7:00" drift. Many BO1 maps pre-set `round_number = 1` at init, so the round watcher fires on transitions only and the timer row seeds on first spawn as a fallback. - **Game-over trigger is level-scope, raced across three candidate notifies** (`end_game` / `game_ended` / `intermission`) since BO1 maps are inconsistent. Per-player triggers do not work — `self waittill("death")` is intercepted by the zombiemode scripts before it reaches our scope, and polling `self.health` misses the zero-crossing (health is rewritten during the game-over sequence). ## Module map | File | Role | Fires notifies | Reads notifies | |---|---|---|---| | `tws_t5_common` | Shared config, HUD helpers, `.help` registry, spawn-handler registry, single connect listener + dispatcher | — | `say` | | `tws_t5_hud` | Timer row, zombie count, health bar | `hud_round_changed`, `hud_match_started`, `hud_game_over` | (internal) | | `tws_t5_afk` | `.afk` / `.back` chat toggle, owns its [AFK] indicator | — | — | | `tws_t5_join` | `.join` for spectators who have not played on this connection | — | `say`, `end_game`, `game_ended`, `intermission` | | `tws_t5_lock` | Lobby `g_password` lock at milestone round (`tws_lock_round`, default 20) | — | `hud_round_changed`, `hud_game_over` | | `tws_t5_telemetry` | Ordered JSON game start, round, 30-second heartbeat, and game end reports with full player snapshots | `telemetry_queued`, `telemetry_finished` (internal) | `end_game`, `game_ended`, `intermission` | | `tws_t5_leaderboard` | Cached completed-match map record and all tied holders, privately on join | — | `end_game`, `game_ended`, `intermission` |