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