# GTAHUB V2 — Round 10 Audit
## Focus: The Block / weapons / blood / controllers / multiplayer hardening

## Executive verdict

Round 9 is a genuine product upgrade:
- realistic weapon roles are implemented
- blood hit/pool FX are implemented
- content acknowledgment is account-backed
- controller preference is persistent
- touch controller layouts are wired into live movement/attack/chat
- active-account and avatar gates remain in place

Do **not** roll these features back.

However, the current multiplayer trust model is not suitable for a professional public launch. The biggest issue is no longer the visual feature set; it is Realtime authorization and combat authority.

Indicative scores:
- GTAHUB core architecture: 8/10
- Round 9 product direction: 8/10
- Round 9 UI/game feel foundation: 7/10
- The Block multiplayer security/integrity: 4/10
- Public-launch readiness of The Block: ~4.5/10

---

# CRITICAL

## C12 — `world:global` is not configured as a private Realtime channel

File:
- `components/world/WorldCanvas.tsx`

Current code creates:

```ts
supabase.channel(WORLD_CHANNEL, {
  config: { presence: { key: userId } },
})
```

There is no `private: true`.

There are also no `realtime.messages` RLS policies in the supplied migrations for this world channel.

Supabase distinguishes public and private channels. Public channels allow clients to subscribe/publish without the Realtime Authorization layer that private channels use.

Impact:
- The page UI requires sign-in, but the Realtime room itself is not enforcing the same product rule.
- A client outside the intended `/world` UI can potentially publish crafted movement/chat/combat messages to the public topic when public channels are allowed in project settings.
- The application currently assumes that every broadcast came from a legitimate active GTAHUB player. That assumption is not enforced by the channel.

Required fix:
1. Switch The Block to a private Realtime channel.
2. Add `realtime.messages` SELECT/INSERT RLS policies.
3. Require an authenticated, active GTAHUB profile to receive/publish world messages.
4. Consider district/topic membership in the policy before sharding.
5. Configure the Supabase project to disallow public channels if GTAHUB does not need them elsewhere.
6. Add an integration test proving anonymous and banned clients cannot join/publish.

---

# HIGH

## H23 — Hit-range validation uses attacker-supplied coordinates, contrary to the code comments

Files:
- `lib/world/combat.ts`
- `components/world/WorldCanvas.tsx`

The trust-model comments say the target checks the attacker's last-known position.

It does not.

`isPlausibleHit()` computes distance from:

```ts
hit.attackerX
hit.attackerY
```

Those coordinates came directly from the attacker's `WorldHitPayload`.

A modified attacker can therefore claim coordinates adjacent to the victim even while actually standing across the map.

Round 8/9 correctly acknowledges position spoofing as deferred, but the stronger statement that a modified client "can't one-shot someone from across the map" is not accurate under the current implementation. Damage is bounded by the fixed weapon table, but repeated forged in-range hits remain possible.

Required fix:
- immediate improvement: target must validate against its locally tracked `RemotePlayer` position for the attacker, never the coordinates included in the hit message
- validate timestamp freshness
- validate plausible movement delta/speed
- validate cooldown server-side or through an authoritative room service
- validate weapon ownership/pickup state
- for any meaningful leaderboard/reward system, move combat authority to a trusted game/room server

## H24 — Broadcast payloads are trusted TypeScript casts, not runtime-validated

File:
- `components/world/WorldCanvas.tsx`

Examples:

```ts
const hit = payload as WorldHitPayload;
const ko = payload as WorldKoPayload;
const upd = payload as WorldHpUpdatePayload;
```

Network payloads are untrusted at runtime.

A crafted `ko` with an unknown `weapon` can reach:

```ts
WEAPONS[ko.weapon].label
```

and can throw because the map entry may not exist.

Other crafted payloads can:
- fake HP updates
- fake KOs/toasts
- inject impossible positions
- fake pickup events
- impersonate usernames/user ids in visual broadcasts

Required fix:
- add Zod/Valibot/runtime schemas for every Realtime event
- reject invalid UUIDs/enums/numbers/timestamps/ranges
- do not trust `username` supplied in broadcast payloads; derive display identity from authenticated/presence state
- bind events to the authenticated sender where possible
- ignore malformed messages without crashing the room
- add fuzz/malformed-payload tests

## H25 — Combat identity is not bound to the authenticated publisher

Files:
- `components/world/WorldCanvas.tsx`
- `lib/world/presence.ts`
- `lib/world/combat.ts`

`attackerId`, `userId`, `username`, crew information and movement identity are fields supplied inside client broadcast payloads.

Even after making the channel private, an authenticated malicious client can still publish events claiming another player's id/name unless the game layer validates sender identity.

Required fix:
- use a server-issued room session identity
- do not accept actor id/name as authoritative client fields
- server/room authority should attach actor identity to accepted actions
- client messages should be intents such as `move_input` / `attack`, not authoritative state claims

## H26 — `world_ko_log` can be fabricated by the target client

File:
- `supabase/migrations/0003_world_combat.sql`

RLS allows an active user to insert a KO row whenever:
- `auth.uid() = target_id`
- rate limit passes

The database cannot verify that a fight occurred.

A user can therefore manufacture losses and assign kills to arbitrary attacker ids, allowing friends/alts to inflate the leaderboard.

This is acceptable only while the leaderboard is explicitly for-fun and carries zero value.

Required fix for professional scale:
- authoritative combat service writes KO results
- clients get SELECT only
- if self-reporting remains temporarily, remove any competitive/reward implication and clearly label results unverified
- never connect tips, verification, prizes, access, matchmaking rank or money to this table

## H27 — Weapon ownership/pickups remain client-authoritative

Files:
- `components/world/WorldCanvas.tsx`
- `lib/world/combat.ts`

A modified client can claim `pistol` or `shotgun` without collecting it.

Pickup collision is also only locally synchronized.

Required fix:
- room authority owns pickup availability and player inventory
- clients request a pickup
- server validates position + availability and broadcasts accepted inventory state
- accepted attack uses server-owned weapon state

## H28 — Friendly-fire protection is only a courtesy

File:
- `components/world/WorldCanvas.tsx`

Same-crew filtering happens only in the honest attacker's local targeting loop.

A modified client can send a hit directly to a crewmate.

Required fix:
- enforce crew/friendly-fire rule at trusted combat authority
- never rely on the attacker's target-selection UI for game rules

---

# HIGH / MIGRATION

## H29 — Migration 0004 can fail if any Round 8 KO rows already exist

File:
- `supabase/migrations/0004_realistic_weapons_and_gate.sql`

The migration says old `bat`, `water_blaster`, and `paintball_gun` rows can remain as historical data.

But it drops the old check constraint and immediately adds a new constraint that only accepts:

- fists
- knife
- pistol
- shotgun

PostgreSQL validates a normal new CHECK constraint against existing rows.

If old Round 8 rows exist, adding the new constraint can fail.

Required fix — choose an explicit migration policy:
- map old weapon ids to new historical equivalents, OR
- retain legacy values in a historical enum/check, OR
- create a separate display mapping and permit legacy values, OR
- if this is guaranteed pre-production data, explicitly clear the test rows before applying the constraint

Do not leave the migration comment claiming historical rows remain valid when the SQL does not guarantee that.

---

# MEDIUM

## M25 — ContentWarningGate is acknowledgment, not age verification

Files:
- `components/world/ContentWarningGate.tsx`
- `supabase/migrations/0004_realistic_weapons_and_gate.sql`

The code comments are appropriately honest about this.

Keep the gate, but do not treat `violence_ack_at` as verified age.

For public distribution:
- complete the applicable store/content-rating questionnaires accurately
- add a parental/content control strategy if needed
- add a "Reduce blood" preference
- keep an auditable content-policy description

The gate is useful UX; it is not a legal/compliance substitute.

## M26 — Add a blood/gore intensity setting

Round 9 already lists this as deferred.

Recommended:
- Full
- Reduced
- Off

Store it as a profile/device preference.
Do not require a new account acknowledgment just to change intensity.

## M27 — Controller picker hides save failures

File:
- `components/world/ControllerPicker.tsx`

The picker optimistically updates and ignores the Supabase error.

This is low-risk because it is a UI preference, but it creates confusing cross-device behavior.

Required fix:
- show a small non-blocking save error
- retry or revert on persistence failure
- debounce repeated scheme switches

## M28 — Interact and Emote are visible dead controls

File:
- `components/world/VirtualController.tsx`

The code deliberately wires these buttons to no-op handlers.

For a professional UI:
- either implement them
- or hide/disable them with clear visual state until they work

A controller should not present active-looking buttons that silently do nothing.

## M29 — World moderation is still incomplete

Open items:
- world chat moderation
- combat griefing controls
- mute/block inside The Block
- report player
- admin session/action history
- temporary world bans
- anti-spam/anti-harassment controls

The core account-state/admin system already exists, so extend it into The Block rather than creating a separate moderation universe.

## M30 — One global room will not scale far

File:
- `lib/world/presence.ts`

The existing changelog already acknowledges this.

Before larger launch:
- district/room sharding
- room capacity
- match/room assignment
- reconnect behavior
- regional latency strategy
- authoritative room ownership

---

# PRODUCT / IP

## P1 — Keep the realistic weapon direction, but keep art original

Round 9 handles this reasonably:
- generic weapon names
- abstract original canvas silhouettes
- no real-world operating instructions

Keep this approach.

## P2 — Do not market the controller layouts as official PlayStation/Xbox controllers without licensing

Current user-facing labels `Shapes`, `Letters`, `Minimal` are safer than branded labels.

For commercial release:
- keep original colors/body/layout presentation
- do not use Sony/Microsoft logos
- get an IP review before making official compatibility/brand claims

---

# RECOMMENDED ROUND 10 ARCHITECTURE

## Short-term hardening
1. Private Supabase Realtime channel.
2. Realtime Authorization RLS.
3. Runtime schemas on all incoming events.
4. Validate hit position using target's tracked attacker state, not attacker-supplied hit coordinates.
5. Guard every enum lookup.
6. Fix migration 0004.
7. Add report/mute/reduce-blood controls.

## Professional-scale combat
Move from:

`client broadcasts authoritative result`

to:

`client sends input/action intent -> trusted room/game authority validates -> authority broadcasts accepted state`

The authoritative layer should own:
- player position
- velocity/speed limits
- health
- weapon inventory
- pickup availability
- cooldowns
- crews/friendly-fire
- KOs
- respawns
- leaderboard result writes

Supabase can remain:
- identity/auth
- profiles
- persistent social data
- durable logs
- matchmaking/room metadata
- Realtime transport where appropriate

But a public competitive game mode should not use each browser as the authority for its own combat state.

---

# ROUND 10 IMPLEMENTATION ORDER

1. C12 private Realtime + auth policies.
2. H24 runtime event schemas/crash guards.
3. H23/H25 actor and hit-position validation.
4. H29 migration correction.
5. Design authoritative room service.
6. Move weapons/pickups/HP/KOs to room authority.
7. World moderation/report/mute.
8. Blood intensity controls.
9. Complete/hide dead controller buttons.
10. District sharding/load test.

---

# LAUNCH GATE FOR THE BLOCK

Do not open The Block broadly until:
- anonymous/banned users cannot join/publish
- malformed broadcast payloads cannot crash clients
- a client cannot impersonate another player id
- a client cannot claim impossible range by supplying fake hit coordinates
- leaderboard results cannot be freely fabricated if they carry value
- migration 0004 applies safely to existing data
- report/mute/admin enforcement exists
- content rating/distribution metadata reflects violence/blood accurately
