## ARENA BATTLE — CHAT MODERATION / v15 R28

## Scope and identity
The operator panel's Chat moderation page controls room chat, spectator chat and reactions for that operator's players. New sites default to chat enabled. Disabling it hides the feed from that site's players and rejects new messages/reactions, including in shared rooms. Other sites retain their own setting. Messages use the immutable Arena Battle `publicId`; the operator's private username is never substituted into chat. Staff actions use the `tenantId` + internal `userId` returned by the panel API, not a user-supplied display name.

Restrictions apply to chat only. They do not block game access, remove seats, cancel battles, move balances or issue refunds. A manual or automatic permanent chat ban remains until authorized staff remove it. Enforcement follows the player's source site across local and shared rooms and across reconnects.

## Filter and escalation
Server-side filtering covers common Turkish/English profanity, links/domains (including common obfuscation), advertising/contact promotion and spam. Normalization handles case, common character substitutions, invisible characters and spacing tricks. A finite rule filter cannot guarantee detection of every language or invented spelling; staff can review false positives and apply/remove restrictions.
- First and second recorded filter violations: warning; the rejected message is not published.
- Third violation: 10-minute timeout.
- Fourth violation after the timeout: 60-minute timeout.
- Fifth violation after the timeout: permanent chat ban.
The rolling flood window permits up to 5 chat attempts or 12 reaction attempts in 10 seconds. Repeated normalized messages within 30 seconds and excessive character repetition are also classified as spam. Disabled/blocked attempts do not add strikes. The server clock controls expiry. Warnings remain after an automatic timeout expires; a manual unban clears accumulated strikes.

Retries must reuse the same operation key and payload. Rejected-operation receipts prevent the same network retry from adding another strike. Do not resend a rejected message automatically with fresh keys. Reusing a key with different content is 409. A manual unban does not turn an old rejected request into a published message; send a new legitimate message with a new key.

## Player API and bearer session
```http API routes
GET /play/:slug/api/social?room=:roomId&channel=room|tribune&after=:cursor
```

Response fields (notation, not a JSON response):

```text Moderation fields
moderation: {
  enabled, canSend, blocked, status, strikes, until, expiresAt,
  publicId, message, messageEn, serverTime
}
```

status is active|warned|timeout|banned|disabled. Times are Unix milliseconds. No private staff note/name is exposed to players. Existing room visibility/channel rules still apply.
```text Endpoint contract · notation, not JSON
POST /play/:slug/api/chat {key,roomId,channel:room|tribune,message}
POST /play/:slug/api/reactions {key,roomId,emoji}
```

Use Content-Type: application/json, the player's bearer token and the integration's allowed origin. key is 8..80 characters from letters, digits, underscore and hyphen; message is trimmed, non-empty and at most 300 characters. Supported reaction wire values remain the existing six values returned in reactions.allowed; the shipped client renders original SVG art, not system emoji glyphs. At most six recent reactions appear at once; extras are represented by a counter.

Success: 200. Invalid request: 400. Missing session: 401. Disabled/banned: 403. Conflicting operation key: 409. Filter warning: 422. Timeout/rate limit: 429. Preserve the existing error body and refresh the social feed for current moderation state. Authenticated errors include a stable CHAT_* code where emitted by central moderation.

## Panel API and separate cookie session
```text Endpoint contract · notation, not JSON
GET  /control/api/chat?tenant=all|tenantId
GET  /operator-api/chat
POST /control/api/chat/settings  {tenantId,enabled:boolean}
POST /operator-api/chat/settings {enabled:boolean,tenantId?:ownTenantId}
POST /control/api/chat/moderate  {tenantId,userId,action,reason,minutes?}
POST /operator-api/chat/moderate {userId,action,reason,minutes?,tenantId?:ownTenantId}
```

action: ban|timeout|unban. reason: trimmed 5..500 characters. minutes is required only for timeout and must be 10, 60 or 1440. Do not send minutes for ban/unban. Unknown fields are rejected. Successful writes return 200 with `{ok:true,setting}` or `{ok:true,restriction}`.

These endpoints require the existing provider or operator panel cookie, the same configured panel Origin and `X-CSRF-Token` for writes. Read permission: `chat.read`. Write permission: `chat.manage` plus `chat.read`. Staff permissions and current sessions are revalidated by the backend. API keys and ordinary player bearer tokens cannot manage chat. Operator sessions always resolve their own site; requesting another tenant is rejected. Shared-room messages are attributed to their source site, and operator staff cannot modify another site's member.

GET returns settings[], policy, summary, messages[], restrictions[], events[], `serverTime`. Message rows include source `tenantId`/`userId`, canonical `publicId`/name, `roomId`/channel/message/`createdAt` and current restriction. Staff restriction/event rows include reason, source, actor and timestamps. Each returned feed is capped at 300 rows. Summary counters cover the bounded selected-site read window, not lifetime totals, and can cover more records than the final merged feed. Panel search/status filters apply to returned rows. Provider polling is 15 seconds; operator polling is 20 seconds; dialogs, hidden tabs and active text editing suspend background refresh.

## Staff and retention
Create/grant named staff through existing Team / Team and roles pages. Limited accounts need explicit `chat.read`/`chat.manage` grants; `chat.manage` without `chat.read` is invalid. Existing full operator-manager grants are upgraded and their old sessions revoked; limited grants are not expanded. Actions and settings are audited. Restricted players may still read enabled chat; a site-wide disabled setting returns an empty player feed.

Central SQLite persists settings, restrictions, attempts, operation receipts and moderation events. On moderation activity, bounded cleanup retains receipts for up to 30 days, attempts for up to 7 days and events for up to 90 days, with per-site caps of 100,000 / 50,000 / 10,000 respectively. Restrictions are retained until changed by staff; the general audit log follows its existing policy. Chat text keeps the existing last-1,000-per-room/channel limit. Staff should treat evidence as private operational data.

## Upgrade and verification
Deploy provider and file/module client together. Iframe clients load the provider's current assets; file clients must redeploy the rebuilt client ZIP so `chat-reactions.js`, `chat-moderation-player.js` and `chat-player.css` match the backend. Provider/admin/operator UI assets are in the provider update package. No wallet callback or operator server-key scope changed in R28.

The delivered automated checks cover actual isolated HTTP/SQLite and browser workflows, including scoped site settings, direct ban/timeout/unban, shared rooms, retry/idempotency, filter rules, TR/EN panels and mobile geometry. Controlled clocks test timeout expiry; no external game purchase is part of the moderation suite.

Do not downgrade to a version that ignores active restrictions or chat permissions. R28 has no automatic rollback. Follow R28 ROLLBACK-GUARD.txt: inspect and preserve settings, restrictions, events and staff grants, with the service offline, before any manual source replacement.
