# Moderation runtime T5 and T6 use the same protocol and native policy core, with separate native chat opcodes and existing engine symbols. Install the game's new moderation script 1.0.0, common 1.2.0, and native plugin 1.3.0. The controller must implement `POST /api/moderation/runtime` before enabling the scripts. ## Private configuration Each instance receives its own non-shared credential. Keep this configuration out of shared scripts, release artifacts, ordinary config exports, and logs: ```cfg set tws_server_id "registered-server" set tws_moderation_url "https://controller.example/api/moderation/runtime" set tws_moderation_token "ec_moderation_REPLACE_WITH_INSTANCE_SECRET" set tws_moderation_generation "1" ``` Set `tws_moderation_enabled "0"` to disable the runtime. Missing or invalid configuration leaves it waiting; it retries every three seconds so private configuration can be installed without a map restart. Disabling stops synchronization and enforcement; it does not revoke controller sanctions or promise to restore existing engine mutes. HTTPS is mandatory; redirects are not followed. The generation must match the controller's live instance. Only that instance's credential authorizes its roster, receipts and policy; the bank and telemetry credentials cannot authorize moderation. ## Synchronization Native HTTP runs outside the game thread with 3-second connection and 5-second transfer deadlines. Polls start at most once per 3 seconds; only one poll is in flight. Responses are limited to 1 MiB at the transport (oversize fails without truncation), 1000 sanctions and 100 actions. The native cache never crosses a GSC string boundary. Requests include protocol 1, server, generation, runtime session, sessionSequence, players `{guid,name,connection}`, capabilities, appliedRevision and up to 256 receipts. Sessions change on map/process startup. A positive safe-integer sessionSequence is incremented and atomically persisted before each new runtime starts; the controller rejects older sequences and conflicting sessions at the same sequence. Repeated unchanged configuration preserves the current sequence. Never share an instance credential or its cache directory between simultaneous game-server processes. Connections change on every native connect/disconnect event, even when the same GUID reuses the same slot. GUIDs remain strings; pure decimal IDs have leading zeroes removed without a numeric conversion; the T5 script reads getGuid and the T6 script reads the stock persistent guid field. GUIDs are limited to 128 ASCII identifier bytes. Roster names are bounded to 255 UTF-8 bytes; malformed name bytes become question marks and ASCII controls become spaces. Blank names, including Unicode whitespace-only names, become Unknown player. Names never select an action target. Responses contain `{ok,revision,serverTime,sanctions,actions,pollAfterMs}`. Sanctions contain `{id,revision,guid,kind,reason,expiresAt}`, with `kind` ban/mute and nullable expiry. Actions contain `{id,guid,connection,kind,reason,expiresAt}`; kind is warn/message/kick/announce. Only announce uses null GUID and connection. Deadlines and policy expiry are UTC ISO timestamps. Clock skew over two minutes rejects an update. Received server time is anchored to monotonic elapsed time without moving backward between polls, conservatively including the full transfer age and a one-second timestamp rounding margin, so a slow or backward-jumping host clock cannot extend one-time deadlines. This may expire an action or timed policy slightly early. Deadlines are checked again after writing the action journal and before dispatch. Revision rollback, inconsistent policy at the same revision, malformed data, duplicate IDs and changed action content reject an update. Removing sanctions already expired according to the validated serverTime is permitted without a revision increment. The current client uses its fixed 3-second poll interval after validating the server's pollAfterMs bounds (1000–60000). ## Enforcement and recovery Keep `tws-moderation/t5/` or `tws-moderation/t6/`, relative to the game process working directory, on persistent writable storage. Files are scoped to server, endpoint, instance generation and SHA-256 credential fingerprint and do not contain the credential. Complete snapshots replace files atomically. Every write verifies ownership of the current session sequence so a superseded runtime cannot roll the counter or policy backward. Corrupt cache disables startup and preserves the file; storage failures are logged and block new effectful actions. Cache loss must be recovered by rebuilding the controller instance generation and issuing a new private credential; resetting or deleting a cache under an existing credential cannot reset the controller's highest session sequence. Retired instance generations cannot inherit a newer generation's policy cache. Cached bans and mutes continue during controller outages and expire using the host wall clock until synchronized, then the later of host time and the monotonic server-time estimate. After a restart without connectivity, host clock drift remains an operator responsibility. Ban enforcement is a kick after connection, checked through the shared connected dispatcher and a 500 ms loop; it is not a pre-authentication admission hook. Still-connected banned players are checked again every five seconds. Removing the last applicable mute restores clients muted by this runtime; unrelated manually applied mutes are not deliberately cleared. Managed mute ownership survives process/map restart so revoked cached mutes can be removed when the affected player returns. No native ban.txt is written. One-time actions require the exact current connection and unexpired deadline. The native game-thread adapter validates the target and executes immediately; GSC's buffered executeCommand is never used. Only fixed kick/mute/unmute command templates are available; untrusted text cannot add delimiters, quotes, line breaks or backslash escapes. Visible text is bounded to 240 UTF-8 bytes without splitting codepoints. Messages use direct per-game server message calls. Before one-time dispatch, the action and an unknown receipt are saved durably. Duplicate IDs never execute again, including after restart. A crash between journal write and dispatch may therefore lose a one-time action; its result stays unknown instead of risking a duplicate kick. Journal entries expire from storage only after receipt acknowledgment and at least a day past their action deadline. The journal is bounded at 10000 entries; saturation stops new actions and requires controller delivery/storage investigation. A confirmed private-message receipt means the game server dispatched it, not that the player read it. A kick is confirmed only if its synchronous dispatch observes the native disconnect callback; otherwise the result is unknown. Native mute commands have no readback, so appliedRevision means the policy is cached and being enforced, not independently verified voice/text suppression. Do not describe a cached policy revision as proven enforcement. ## Validation and rollout Portable checks: ```sh node tools/check_moderation.mjs python3 tools/test_plugin_moderation.py python3 tools/test_plugin_http.py python3 tools/test_plugin_component.py ``` The native moderation runner accepts `--json-header '/path/{game}-json.hpp'` when dependencies have not been restored. Use each plugin's pinned JSON revision. It compiles the actual core and registration/HTTP bridge against minimal engine shims with ASan/UBSan, including separate process persistence tests. These checks do not validate Windows linking or the Plutonium ABI. Build both x86 DLLs using the existing Windows build workflow. Deploy the new plugins and scripts together, then fully restart each game-server process; a map restart alone cannot load the native methods. Test in isolated T5 Zombies and T6 Zombies instances before fleet rollout: duplicate names, same-account reconnect, slot reuse, kick command/reason behavior, text and voice mute/unmute, reconnect and map/process restart, expiry, revocation, controller outage, credential revocation, truncated/oversized responses and disk failure. Verify clientkick and muteclient command support in the deployed Plutonium revisions. If unsupported, keep moderation disabled; this change has no automatic production deployment.