VOIDERATION V24: PLAYFAB BANS + BADGUYSCENE INTEGRATION
Date: 2026-10-30
Status: Research/planning only. No code, revision, API, dashboard or production
changes. Additive update to V22/V23; those reports remain unchanged.
DECISION / CURRENT FACTS
PlayFab is authoritative for ban state. Voideration dashboard is the owner
control surface; Worker is the only holder of PlayFab Title Secret Key and
performs Admin ban/query/revoke operations. D1 stores Voideration incident and
action history plus a reconciled projection of PlayFab results, not the final
authority for whether a PlayFab ban is active.
CONFIRMED from local source/reports:
- Unity `Oculus_Login` uses client-obtained Meta ID as PlayFab CustomId; successful
login returns canonical PlayFabId and SessionTicket. Meta ID is not proof.
- `HandleBan` can display ban reason/expiry using configured UI, but does not
load BadGuyScene. `KickIfBanned` separately polls account info then disconnects
and loads scene index 1 on `AccountBanned`. No `BadGuyScene` was found.
- Current Photon auth passes PlayFabId as username and the distinct PlayFab
Photon token as token; a 20-second anonymous fallback exists. Neither the
browser nor Unity may receive PlayFab Title Secret Key.
- Worker `/redeem` validates a PlayFab SessionTicket and derives PlayFabId for
link flow. Existing owner Discord ban commands are not a sufficient dashboard
ban system: KV keeps a fragile single BanId; unban relies on it. Worker source
must verify the current PlayFab revoke route against documented `/Admin/RevokeBans`.
- Worker/D1 dashboard, ban API, BadGuyScene and reliable live PUN-session kick
are not implemented. No dashboard/provider settings were inspected.
TARGET ARCHITECTURE / TRUST BOUNDARIES
Local owner website (password every opening; 127.0.0.1 only)
| owner-authenticated Worker API over TLS; no secrets in JS
v
Cloudflare Worker -- PlayFab Admin APIs (Title Secret Key server-only)
| | BanUsers/GetUserBans/RevokeBans
| v
| PlayFab = authoritative ban record
+-- D1 = incident/action audit + PlayFab BanId/result mirror
+-- trusted ban-status endpoint -> Unity BadGuyScene
+-- Photon custom-auth gate checks validated identity + effective ban
Owner actions require backend authentication/authorization every request. Never
trust a PlayFabId, Discord username, ban status, duration or reviewer supplied
by game client. Never put Title Secret Key in browser JS, Unity, logs, URLs or D1.
The local site remains primary; optional Discord notification cannot authorize,
apply, or reverse a ban.
OWNER DASHBOARD AND STATE TRANSITIONS
Incident state and PlayFab ban state are separate:
- Detection -> incident `PendingReview`; it does not itself ban or block rejoin
unless an explicitly approved temporary review restriction applies.
- `Approve`: mark incident Approved, clear PendingReview restriction and allow
rejoining after the trusted Photon admission check observes release.
- `Dismiss`: mark incident Dismissed/closed (or `Rejected` in the V22-compatible
schema with a false-positive reason), no ban, release only a PendingReview
hold. This is the false-positive close action.
- `Deny/Ban`: owner chooses reason and duration (finite duration or permanent
option if PlayFab API supports it), re-authenticates, confirms. Worker calls
PlayFab Admin BanUsers. On success store PlayFab BanId(s), returned expiry,
reason, actor, UTC time and result in D1; incident history links to the ban.
Only after confirmed PlayFab success display `Banned`. On failure/timeout show
pending/uncertain, not success; reconcile via GetUserBans before retry.
- `Release/Revoke`: explicit owner action calls PlayFab RevokeBans for recorded
active BanId(s), records response and updates mirror after provider confirms.
Do not use RevokeAllBansForUser unless owner explicitly chooses broader effect.
PlayFab GetUserBans may return inactive/expired records with `Active=true`; treat
ban as effective only when it is not revoked and `Expires` is absent (permanent)
or later than current UTC. Retain all BanIds; multiple bans are possible. Finite
duration arithmetic must use UTC and API limits. “Very long” timed ban is not a
permanent ban. Verify exact Admin API semantics and current Worker route before
implementation.
Worker ban operation should persist a unique operation/idempotency key and
intent, call PlayFab, then persist provider response. If PlayFab succeeds but
D1 update fails, operation is uncertain: query GetUserBans by trusted PlayFabId,
reconcile BanIds and do not issue a duplicate ban blindly. If PlayFab fails,
store failure and do not mark banned. D1 is an audit/projection; PlayFab remains
source of truth. Audit successful and denied owner actions, reasons, before/after,
request ID and provider result. Require owner reauthentication for ban/revoke.
BADGUYSCENE AND BAN STATUS WHEN LOGIN IS DENIED
Unity should enter `BadGuyScene` only on a definitive `AccountBanned` response
or a trusted Worker response that independently confirms an effective PlayFab
ban. Display sanitized reason, permanent vs remaining duration, and absolute UTC
expiration where finite. Missing/malformed reason/expiry gets safe generic text;
do not parse provider text as executable markup. Network, title, account, or
Worker errors are not bans and must use normal retry/error UI. Do not join Photon
while ban status is unresolved or active. Preserve existing unbanned login,
inventory, title data, MOTD, rooms, voice and scene flow; avoid scene-index-only
assumptions and test return/navigation behavior.
Secure lookup options, in preference order:
1. Use authoritative PlayFab `AccountBanned` error details returned by login if
they reliably contain reason/expiry; test current SDK/provider behavior. These
details may be absent, so sanitize and handle optional values.
2. If a trusted lookup is needed after login rejection, Unity obtains a fresh
Meta User Proof for a server-issued one-use nonce. Worker verifies proof and
identity with Meta server-to-server, checks the verified Meta-to-PlayFab
account binding, then calls PlayFab GetUserBans and returns only minimal ban
display fields. Worker derives PlayFabId from the verified mapping; client
never sends a trusted PlayFabId. Proof nonce is single-use, short-lived and
never logged/stored long-term.
3. If Meta verification/eligibility, proof exchange, or account binding is not
available, do not create a public lookup taking client PlayFabId or username.
Show generic banned/login failure when provider confirms ban but details are
unavailable. Do not claim account-specific display lookup works until this
path is proven in staging.
Meta User Verification is a possible identity bridge, not established eligibility
or current implementation. Current Meta ID is client supplied and insufficient.
For existing accounts, require verified mapping to their PlayFabId; do not assume
Meta ID equals PlayFabId or silently create a new account. An authenticated
PlayFab SessionTicket is another trusted identity input when available, but the
normal ban-rejected login may not yield a usable ticket. Photon token is not a
PlayFab SessionTicket and must not be passed to AuthenticateSessionTicket.
PHOTON SESSION DISCONNECT / RECONNECT ENFORCEMENT
- PlayFab BanUsers is authoritative for PlayFab account access but does not by
itself prove that an already-connected Photon PUN Cloud actor is immediately
removed. Current research found no reliable targeted-disconnect API available
to an ordinary Worker request. A cooperative client can receive a ban notice
and disconnect itself, but a modified client may ignore it. Do not use room
closure/MasterClient actions that disrupt others; describe existing session as
connected until observed disconnect.
- On every new Photon custom-auth/reconnect, trusted server-side admission must
validate the Photon credential with the supported PlayFab/Photon contract,
derive canonical PlayFabId, query effective PlayFab ban and D1 temporary
restriction, and deny if either active. Do not trust username. The exact
Worker-to-PlayFab Photon proxy contract remains unverified; do not guess.
- Close all Unity anonymous Connect fallbacks and configure Photon to reject
anonymous clients before claiming bans prevent reconnect. Test app restart,
room rejoin, voice app/auth and token-refresh/reconnect. Photon custom auth is
connection-time, not live-session enforcement.
- During Worker/PlayFab/D1 outage, return retryable auth-service failure with no
anonymous fallback. Do not report a new ban or change ban state on outage.
Existing sessions are not mass-kicked. If ban lookup is unavailable after a
rejected login, show retryable status; do not label user permanently banned.
LINKED DISCORD IDENTITY
Dashboard may show linked Discord account only when Worker verifies an existing
link record created by the authenticated one-time code flow and associates it
with the canonical PlayFabId. Treat it as verified link metadata, not identity
proof for login, ban lookup or permission. Never display a Discord username
received directly from client; resolve the stored Discord user ID server-side
through trusted Discord data if a current username is desired. Clearly label
unlinked/unknown; minimize Discord data and access. Audit link/unlink changes.
DASHBOARD ACCESS / FAILURE HANDLING
Website is primary and bound only to `127.0.0.1:<port>`. Require password on
every opening (fresh login; no remembered browser session), rate-limit attempts,
store only a strong salted password verifier server-side, use secure short-lived
session cookie, CSRF + strict Host/Origin checks, and require fresh password
confirmation for Ban/Revoke. Backend rechecks owner permission. Use OS-protected
local credential storage for Worker authentication; no secrets in JS/localStorage.
Local machine compromise remains a trust boundary. UI failure/offline must not
optimistically apply a ban; show pending/failed/uncertain state and reconcile.
Optional Discord critical alert follows provider confirmation; dashboard remains
usable if Discord is offline.
DEPENDENCIES / TESTS
1. Verify PlayFab Admin BanUsers/GetUserBans/RevokeBans API contracts, permanent
vs timed semantics, required fields and error/idempotency behavior; inspect
deployed Worker and secret binding names without exposing values. Reconcile
existing Discord ban/unban commands and old BanId KV records before migration.
2. Decide owner duration choices, reason policy, appeal/release process, password
provisioning/recovery, privacy/retention, and whether Meta proof is mandatory
for denied-login ban details. Default: owner-only, finite durations by default,
permanent requires explicit confirmation; dismissal never bans.
3. Stage Worker/D1 ban mirror and reconciliation, then dashboard actions. Test
PlayFab success + D1 failure, provider timeout after success, duplicate clicks,
multiple BanIds, expired/revoked bans, revoke one vs all, and audit completeness.
4. Validate login errors and `BadGuyScene`: timed/permanent ban, missing details,
malformed expiry, ordinary network/title/auth failure, Worker unavailable,
Meta proof replay/wrong user, unmapped legacy account. No client PlayFabId
spoofing; no ban display for non-ban errors.
5. Photon tests: active ban denied on fresh connect/rejoin/app restart; revoke
permits rejoin; temporary V22 review hold releases on Approve; no anonymous
fallback/admission; Voice and ordinary rooms still work. Confirm existing
connected player is not falsely reported as kicked and other room users remain.
6. Regression: preserve Oculus/PCDEBUG login, PlayFab inventory/currency and
purchases, `/link`, MOTD, CCU, Tag/Infection, PVP, movement, Sandbox/welding,
horror hub/LVL1-7, voice, scene transitions and reconnects.
Read-only sources reused: V15/V17/V19/V21/V22/V23, local `Oculus_Login.cs`,
`PhotonVRManager.cs`, `worker.js`, `/redeem` session-ticket validation, and
existing ban command paths. PlayFab Admin API contracts and Meta eligibility
must be confirmed against current provider documentation and staging before
implementation. No dashboards or production services were accessed.
END OF REPORT