SCREECHVR V21: ALERTS, MANUAL REVIEW + REJOIN RESTRICTIONS Date: 2026-10-30 Scope: Research-only architecture using local Worker/auth source and V18-V20 findings. No code, dashboard, or production changes. V08 remains an unverified draft; Photon/Cloudflare dashboards were not accessed. SUMMARY The existing stack can support a manual-review workflow in principle: Cloudflare Worker + D1 for incidents/restrictions/audit, private Discord alerts, and Photon connection-time authentication. It does not currently implement incident storage, an incident route, D1, a local dashboard, or Player-side restriction admission. Photon custom auth can deny a new authenticated connection, but is not a reliable targeted kick for an already-connected player under PUN Cloud. The current anonymous Photon fallback must be removed before restrictions can prevent rejoin. Zero additional recurring cost is plausible on Cloudflare Free at low volume, not guaranteed until account usage/limits and private Discord configuration are verified. 1. CURRENT AUTH / WORKER FACTS - `! Screech VR/Screech VR/Assets/Scripts/Oculus_Login.cs`: PlayFab login stores PlayFab ID/session ticket and obtains a separate PlayFab Photon auth token. - `! Screech VR/Screech VR/Assets/Scripts/PhotonVRManager.cs`: authenticated connect sends `username` = PlayFab ID and `token` = PlayFab Photon token in custom AuthenticationValues. `WaitForAuthThenConnect()` waits 20 seconds then falls back to `Connect()`, which clears AuthValues. This is a confirmed code path that bypasses any required custom auth. Whether production Photon admits anonymous clients is dashboard-dependent and unverified. - `! Screech VR/discord-bot/worker.js`: `/redeem` already calls PlayFab `/Server/AuthenticateSessionTicket` and derives PlayFabId from server response (`handleRedeem`, 461-480), but this is only for account linking. There is no general game-auth/incident/restriction endpoint or D1 binding in inspected source. `/updateplayers` is a no-op (457-459); `/motd` is public (524-530). - Discord interactions verify signature/timestamp and use OWNER_ID checks (`verifyDiscordRequest`, `isOwner`, lines 246-264); these can inform owner-only review commands but do not currently administer D1 incidents. - Existing `ADMIN_CHANNEL_ID` is configured in project notes; verify its actual visibility is restricted to the owner/reviewers before sending player evidence. A channel name does not prove privacy. - D1 is not currently bound or used. V19 contains the prior Worker route/security and free-tier review; this report does not repeat it. 2. PROPOSED DATA MODEL AND SERVER-SIDE INCIDENT INGESTION Every detection must be recorded server-side before attempting a Discord alert. An incident is not automatically a restriction: if every weak movement signal immediately blocked login, tracking loss and lag would wrongly lock out players. Keep incident review state separate from the player’s temporary restriction and any later PlayFab ban. Suggested logical tables (minimize retained evidence): - `incidents`: incident_id, player_id (PlayFabId derived from trusted auth), session_id, rule_id/rule_version, severity, confidence, evidence_json or evidence_ref, detected_at/server_received_at, mode/build, review_status (PendingReview/Approved/Rejected), created_at, updated_at, reviewed_by, reviewed_at, reviewer_note. - `restrictions`: player_id primary key, status (none/PendingReview), source incident_id, created_at, expires_at, released_at, release_reason, version. Store only active restriction; do not duplicate a permanent ban here. - `decision_audit`: audit_id, owner_id, action, incident_id, player_id, previous/new incident state, previous/new restriction state, ban decision ref, reason, timestamp, request_id. Append-only to Worker APIs; dashboard cannot update/delete audit rows. - Optional `alert_outbox`: incident_id unique, state, attempts, next_attempt, sent_at, last_error. This prevents alert failure from losing the incident. Detection source: V20 movement/Tag rules depend on untrusted client samples under PUN. A future Worker ingestion endpoint must authenticate a PlayFab session or short-lived session token, derive player identity server-side, validate schema, rate-limit, bound payload size, record provenance and confidence, and never accept client-supplied severity/reviewer status as authoritative. Client-reported signals remain low confidence. Trusted backend-observed violations (e.g., an invalid auth/replay protocol) can have higher confidence. Every received detection creates a server-side record, even if alert delivery is delayed. 3. REVIEW / RESTRICTION STATE MACHINE Incident status: - `PendingReview`: created automatically for every detection; immutable evidence and rule version attached. - `Approved`: owner confirms incident/action after review. Approval of a restriction releases it; it does not imply a permanent ban. - `Rejected`: owner concludes incident/action is not supported. Rejection maintains any existing restriction as required by the request; owner must use a separate explicit release action if releasing is intended. The separate ban decision is a distinct audited action and must not be automatic. Restriction state is separate: - No restriction -> temporary `PendingReview` restriction only for approved high-confidence rules (criteria below) or explicit owner action. Do not apply restrictions for every low-confidence detection. - PendingReview restriction -> owner `Approve`: incident Approved and restriction released atomically; owner `Reject`: incident Rejected and restriction remains until explicit release/review or configured expiry. Record every transition. - No transition is accepted from client-side UI/state. Worker checks authenticated owner identity on every review/release/ban endpoint; D1 conditional update requires expected current status/version and unique request ID. Repeat requests return prior result; conflicting/stale requests fail and are audited. - Ban is separate: only after a human-reviewed decision; issue via authorized PlayFab ban operation and record the PlayFab result. Never permanently ban based solely on weak movement/latency/tracking heuristics. 4. WHICH RULES MAY TEMPORARILY RESTRICT Default: incident + alert only, no admission restriction. Candidates for a short-lived PendingReview restriction (not ban) after empirical validation: - Invalid/revoked/expired PlayFab session or confirmed restriction lookup: deny session as authentication policy, not anti-cheat punishment. - Repeated high-confidence protocol abuse with server-verifiable evidence, such as replayed/invalid one-time challenge sequence or authenticated request associated with a known restricted account. Treat service/D1 errors as unavailable, not evidence against player. - Repeated impossible state transitions where actor/session identity and sequence are server-validated; avoid movement distance alone. Do NOT restrict solely on speed/acceleration residuals, single teleport-like sample, hand reach, tag distance, latency, missed heartbeat, tracking loss, client report, or Photon owner-reported pose. V20 explains the false-positive and non-authoritative limits. For movement/Tag anomalies, log + alert for human review; only an owner may apply a temporary restriction after examining evidence. Any temporary restriction should have a short explicit expiry, retry/appeal path, and automatic release on expiry; the duration is a policy decision, not established by source. 5. AUTHENTICATION ENFORCEMENT / REJOIN / EXISTING CONNECTIONS Connection admission: 1. PlayFab login and `GetPhotonAuthenticationToken` succeed in Unity. 2. Photon custom authentication reaches configured auth endpoint. Existing PlayFab Photon endpoint validates PlayFab photon token, but it does not query Voideration D1 restriction state. To enforce restrictions, use a custom authentication service that validates the PlayFab token using the documented PlayFab/Photon contract, derives the actual PlayFab ID, checks D1 active restriction/ban, then returns Photon-compatible success/denial. Do not trust `username` alone. Validate exact provider response contract in staging. 3. Configure Photon to reject anonymous connections and fail authentication when the provider denies/unavailable, subject to deliberate outage policy. Remove `PhotonVRManager`’s unauthenticated fallback; retry/report errors instead. Photon dashboard custom-auth URL, anonymous toggle, app association, and provider-failure behavior are unverified; do not change in this report. 4. Reconnect, room rejoin and fresh app login repeat admission check against current restriction state. Never cache approval solely in the client. Expire or revoke old session tokens as appropriate; add rate limits and avoid creating fresh PlayFab account/session as a fallback. Quick rejoin: enforce a server-side per-player restriction independent of Photon actor number, room, device IP or client state. Recheck every fresh Photon authentication. For currently connected restricted users, notify through cooperative client/backend and request disconnect if available, but do not claim Worker can perform a targeted PUN Cloud disconnect. PUN Cloud custom auth is connection-time; the Worker is not a room plugin/server with authority to evict an already-authenticated actor. Photon room/master handoff or room closure is not a safe targeted-kick substitute and can disrupt legitimate users. Exact account features/Photon Server plugins are unverified. Outage behavior: - D1 or auth backend unavailable during new authentication: retryable deny with clear service-unavailable behavior. This prevents restriction bypass but can temporarily block all new sessions; existing sessions remain connected unless independently disconnected. Decide and test this availability/security tradeoff. - Never interpret D1/Worker/PlayFab timeout as a detection, ban, approval or restriction. Alert owner on backend health failure without player blame. - Discord unavailable: incident remains durable; outbox retries with bounded backoff and dashboard shows unsent alert. - If client cannot reach Worker, do not have local client decide restriction; retry auth and surface service issue. Do not bypass to anonymous access. 6. PRIVATE DISCORD ALERT + LOCAL OWNER DASHBOARD Alert every new incident, including low-confidence incidents, with rate-limited aggregation to avoid alert floods. Suggested private message/embed: - Incident ID; UTC detected/received timestamps; rule ID/version; severity and confidence; review status PendingReview; mode/build/session reference; concise evidence summary and evidence-quality caveats; active restriction yes/no and expiry; local dashboard review instructions. - Minimize identity: PlayFab ID only if owner needs it; never include session ticket, Photon token, Meta receipt, raw IP, proof nonce or credentials. Send to owner-only channel after verifying its effective permissions. Keep Discord bot token/webhook secret in Worker secret bindings, not client or D1 plaintext. - Include incident ID and dedupe key. Alert delivery is asynchronous and does not change incident/restriction state. Dashboard: - Serve only on `127.0.0.1`/loopback, not `0.0.0.0` or public Pages. Require owner authentication, CSRF protection for actions, secure local credential storage, short session expiry, and explicit confirmation/reason for approve, reject, release and separate ban action. Local-only is not guaranteed merely by a UI URL; verify bind/firewall/browser behavior. - Query Worker over authenticated owner channel/API or use a local process with narrowly scoped credentials. Never embed Cloudflare/PlayFab secrets in static JavaScript or share owner bearer credentials in Discord. - Show evidence, provenance, data-quality indicators, history and status; actions call server-side API, not direct D1 from browser. Only owner identity may transition or release restrictions. Audit denied attempts as well as success. 7. COST, PRIVACY, AND DEPENDENCIES - D1 adds no recurring charge by itself within Workers Free included quotas; the V19 review records current published limits and emphasizes shared account usage. Worker requests for every detection/alert/review/auth call also share Workers Free 100K/day. Incidents are event-driven, unlike five-minute beats; avoid writing per-frame evidence. No reliable volume estimate without rule rates. At 100 CCU, alerts should be batched/aggregated to prevent Discord spam. - €0 additional recurring is plausible if aggregate Worker/D1 usage stays within the actual account’s free tier and dashboard/local hosting stays on owner machine. It is not guaranteed: current plan, usage, quotas, and need for a dedicated authoritative game server are unknown. A dedicated server likely changes the cost target. - Privacy: retain only evidence required to review; define retention/deletion, access log and appeal process; redact identifiers from broad Discord channels. Do not store credentials, full auth tokens, raw headset pose streams indefinitely, or unnecessary IP/device identifiers. Restriction decisions need human reason and review date. - Required dependencies: D1 schema/migrations and durable state; backend auth route validating PlayFab/Photon token; Cloudflare secrets; private Discord channel/bot permission; owner identity binding; local dashboard; Unity removal of anonymous fallback and any cooperative existing-session notification; Photon custom-auth configuration and staging Realtime/Voice tests; owner policy for review SLA, restriction expiry, release and appeal. 8. REGRESSION TESTS Staging/test accounts only: 1. Every detection creates exactly one server-side incident with trusted player identity, rule/version, evidence, UTC timestamps, severity/confidence and PendingReview; retry does not duplicate incident. 2. Discord private alert arrives; alert failure/retry does not lose incident or accidentally restrict; owner dashboard shows pending and evidence context. 3. Non-owner and client-forged approval/release/reviewer IDs denied and audited; owner approval releases restriction; rejection preserves it; separate explicit release and separate ban workflow have distinct audit entries. 4. Restricted player denied new Photon auth, room rejoin and reconnect after app restart; unrestricted player retains normal login/voice/room flow. Test via actual configured Photon custom-auth endpoint, not mocked UI only. 5. Confirm anonymous fallback is not used on token timeout; anonymous connection rejected by staging Photon config. Test auth backend unavailable behavior. 6. Existing session restriction: verify documented cooperative/disconnect limitations; do not claim targeted disconnect if unsupported. Ensure other room participants are not kicked or disrupted. 7. Lag, tracking loss, normal movement, Tag/Infection and low-confidence V20 detections create review incidents without automatic restrictions/bans. 8. D1 outage, Worker timeout, PlayFab unavailable, Discord outage, duplicate clicks, stale state version and concurrent owner actions; no false approval, lost audit or bypass. Check bounded retry and local dashboard loopback-only. 9. Verify expiry/release and rejoin access; audit log tamper resistance/retention; confirm no secrets or raw sensitive evidence in alerts/logs. 9. REMAINING UNKNOWNS - Photon dashboard custom auth URL, anonymous-client setting, failure policy, Realtime/Voice app IDs, and whether Photon supports the required proxy/provider behavior for this title. - Current production Worker version, D1/account plan/traffic headroom, alert channel permissions and existing Discord bot capabilities. - Exact targeted disconnect capability for this Photon Cloud subscription; no such Worker API was found in reviewed source. Do not assume it exists. - Validated incident rates, restriction policy durations, review SLA, appeals, privacy retention and legally appropriate evidence scope. - PlayFab ticket lifetime/revocation and the precise backend token validation contract for custom Photon auth. OFFICIAL DOCUMENTATION - Photon PUN PlayFab authentication/dashboard integration: https://doc.photonengine.com/pun/current/reference/playfab - Photon PUN custom authentication and anonymous admission: https://doc.photonengine.com/pun/current/connection-and-authentication/authentication/custom-authentication - Photon PUN auth overview: https://doc.photonengine.com/pun/current/connection-and-authentication/authentication/overview-auth - PlayFab Server AuthenticateSessionTicket: https://learn.microsoft.com/en-us/rest/api/playfab/server/authentication/authenticate-session-ticket - Cloudflare D1 pricing and query accounting: https://developers.cloudflare.com/d1/platform/pricing/ - Cloudflare Workers limits: https://developers.cloudflare.com/workers/platform/limits/ No implementation or production changes. This report is a proposed owner-led workflow, not evidence that current Worker/Photon configuration enforces it. END OF REPORT