# 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 counter text uses `SetTextUnlimited`; HP uses `SetValue`.** Repeatedly generating unique strings through `SetText` risks exhausting the text config-string pool; identical strings can be reused by the engine's [string lookup](https://github.com/MisaDev4/bo1-zombies-decompiled/blob/102f6992be332610aff587e681e3180b15f395bb/src/game_mp/g_utils_mp.cpp#L46). The centered `N ZOMBIES` element uses Plutonium r5334+'s `SetTextUnlimited` only when the count changes. Composite HP retains numeric elements around a static slash and `HP` suffix; the group appears only while injured. Help reuses up to nine explicitly positioned command/description pairs, and record rows skip unchanged text. - **`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. - **Connection-time HUD creation must not seed clocks.** The unboxed core is available to waiting spectators with placeholders: timers upper left, branding top center and zombies/health bottom center. Timer updates retain their existing spawn/round lifecycle, independently of element creation. - **Visibility is separate from updates.** HUD owns the exact, case-insensitive `.hud` chat command and its help entry. Common's core wrappers register each element and preserve its intended alpha behind a per-player visibility mask; health and AFK changes use `set_core_alpha()`. Hidden data/timers keep updating until game over freezes them. Help, records and stock elements remain separate. Visibility defaults on, survives death/respawn during the connection/map, and resets on reconnect or map restart. AFK protection is unaffected. - **One service view per player.** Common owns the unboxed upper-right service area, ordered help rows, record display state and cleanup. Help shows all nine installed commands; extra registrations use nine rows per page. Help pauses record page time; `.leaders` during help replaces the pending replay from page one. Keep both services independent of the HUD module. Services never reposition the core, and AFK remains centered above the zombie count. - **Bound HUD element counts, including hidden elements.** Core + AFK use 10 + 1 archived elements. Common allocates only the active service view: 19 nonarchived elements for nine-command help (20 with a multi-page counter), or four for records (five with a multi-page counter), and none while idle. Switching help/record views destroys the previous view first; pages reuse its elements. Keep the controller and pending requests separate from element lifetime so paused records resume normally. Both engines use explicit help rows; BO1 can concatenate newline-separated HUD text. Hiding an element does not free its slot. The previous 24-element persistent service allocation was consistent with the reported core corruption after records first appeared. The exact live T5 SP capacity remains unverified. The [BO1 HUD update](https://github.com/MisaDev4/bo1-zombies-decompiled/blob/102f6992be332610aff587e681e3180b15f395bb/src/game/g_hudelem.cpp#L2596) comes from an MP-based reconstruction and is not proof of T5 SP limits or current Plutonium stock usage. Validate automatic records, help, view switches and dismissal alongside stock notifications and other installed HUD mods; the core must remain intact throughout and after every service closes. - **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). HUD `1.4.2` requires matching common `1.4.0+` and [Plutonium r5334+](https://plutonium.pw/docs/changelog/#r5334) for `SetTextUnlimited`. On r5334+, update the HUD script and restart the map; older Plutonium clients and servers need updates and full restarts first. AFK `1.2.0` and leaderboard `1.1.0` remain compatible; AFK still requires common `1.4.0+`. The full help order is `.help`, `.hud`, `.afk`, `.back`, `.join`, `.leaders`, `.b`, `.d`, `.w`: one page of nine commands, with full `` bank syntax. `.hud` is absent when HUD is omitted. Records use right-aligned `Map record | Round N` and up to three bounded holder lines, with no footer and no single-page counter. Both views retain 12-second page timing. Fullscreen X/Y overlay anchors use direct normalized `640 × 480` coordinates. Dynamic health fills use `make_overlay_width()` like their tracks. Apply `int()` after `max()` in that helper: native `max()` returns a float, but `SetShader` requires an integer width. Legacy panel helpers and their `0.75` horizontal conversion remain available for existing consumers. Font scale is at least `1.0`. Numeric HP appears only while injured; the core and services have no panel backgrounds. The counter is one `N ZOMBIES` element centered at the health track's midpoint with uniform scale `1.25`, starting as `-- ZOMBIES`. There is no new native-plugin/backend dependency or full server-process restart requirement when already using Plutonium r5334+. Run the [shared HUD checks](../../README.md#hud-validation); portable fixtures do not replace T5 live loading, layout and lifecycle validation. Check the counter's centering at `0`, `6`, `9`, `10`, `99` and `100`, after `.hud` hides/shows it, and after game over freezes the count. ## Module map | File | Role | Fires notifies | Reads notifies | |---|---|---|---| | `tws_t5_common` | Shared config, HUD helpers, paginated `.help`, service window, spawn-handler registry, single connect listener + dispatcher | — | `say`, game-end notifications | | `tws_t5_hud` | Unboxed core overlays and private `.hud` visibility toggle | `hud_round_changed`, `hud_match_started`, `hud_game_over` | `say`, internal notifications | | `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` | Automatic `g_password` lock (`tws_lock_round`, default 20) plus majority `.lock` / `.unlock` votes | `tws_lock_game_over` (internal) | `say`, `hud_round_changed`, `hud_game_over`, `end_game`, `game_ended`, `intermission` | | `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` |