text Untitled 16 views
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