togethere.cloud/files/admin_tasks/7c637ad4255c489f991f2953395e5280.bin

873 lines
28 KiB
Plaintext

# Ping-Pong PvP 1v1 - Full Technical Architecture Audit (Node.js + Redis + WebSocket)
Data audytu: 2026-05-20
Zakres: pełny statyczny audyt kodu (bez refaktoru i bez zmian architektury na tym etapie)
Tryb: production-grade technical assessment
## Zakres przeanalizowanych komponentów
- Node server: `public_html/disciplines/ping-pong/1v1/node-server/src/*`
- Klient gry WebSocket: `public_html/disciplines/ping-pong/1v1/js/online.js`
- Strona wejściowa gry: `public_html/disciplines/ping-pong/1v1/index.php`
- API PHP (ticket/status/rewards): `public_html/api/matches/ping-pong/1v1/*`
- Internal HMAC/ticket/env helpers: `public_html/api/matches/ping-pong/1v1/internal/*`
- CRON rewards worker: `public_html/cron/process_rewards_jobs.php`
- Session/auth bootstrap: `public_html/includes/session_bootstrap.php`
- PM2 deployment config: `public_html/disciplines/ping-pong/1v1/node-server/ecosystem.config.cjs`
## Metodologia
- Audyt oparty wyłącznie na kodzie źródłowym i aktualnych artefaktach repo.
- Brak założeń "na wiarę" o infrastrukturze poza tym, co jest jawnie zaimplementowane.
- Brak zmian kodu produkcyjnego (zgodnie z wymaganiem).
---
# 1. OGOLNA ARCHITEKTURA
## 1.1 Mapa systemu (as-is)
```mermaid
flowchart LR
A[Browser Client online.js] -->|GET ticket| B[PHP /api/matches/ping-pong/1v1/ticket.php]
B -->|signed short-lived ticket| A
A -->|WebSocket hello/queue/input| C[Node 1v1 Server index.js]
C -->|queue, snapshots, worker ownership| D[(Redis)]
C -->|match rows + ticks + direct rewards| E[(MySQL)]
C -->|fallback HTTP rewards signed HMAC| F[PHP /api/matches/ping-pong/1v1/index.php]
F -->|rewards_jobs status| E
A -->|poll rewards job| G[PHP /api/matches/ping-pong/1v1/status.php]
G --> E
H[PM2 cluster mode] --> C
C -->|cross-worker WS routing| D
```
## 1.2 Zaleznosci modulow
- `index.js` jest orchestrator-em runtime:
- auth ticket (`ticket.js`)
- transport (`server.js`)
- queue (`matchmaking.js`)
- physics (`physics.js`)
- redis storage (`redisClient.js`, `matchStore.js`)
- cross-worker IPC (`ipc.js`)
- persistence/economy (`mysqlWriter.js`, fallback `rewardsClient.js`)
- PHP warstwa dostarcza:
- issuance ticketu WebSocket (`ticket.php`)
- fallback settlement (`index.php`)
- polling statusu settlementu (`status.php`)
- profile snapshot do UI (`player-summary.php`)
## 1.3 Lifecycle requestow i sesji
1. Uzytkownik otwiera `/disciplines/ping-pong/1v1/`.
2. Front pobiera ticket GET (`ticket.php`) na bazie sesji PHP.
3. Front otwiera WS i wysyla `hello` z ticketem.
4. Node waliduje HMAC ticketu i TTL.
5. User moze wyslac `queue.join`.
6. Matchmaking loop dobiera pare i tworzy obiekt `Match`.
7. Match emituje `match.found`, potem cykliczne `match.state`.
8. Klient wysyla `match.input` (33 ms) i app-level `ping` (3 s).
9. Koniec meczu: `match.end`, snapshot finalny, settlement DB (direct), fallback HTTP gdy direct fail.
10. Front opcjonalnie polluje `status.php` dla `rewards_jobs`.
## 1.4 Lifecycle meczu (dokladny)
- Warmup 10 s + pre-start break 3 s.
- Faza gry: tick server-authoritative `step()`.
- Point pause 1 s po zdobyciu punktu.
- Set break 3 s przy zakonczeniu seta.
- Best of 5 (setsToWin=3), set do 11 z przewaga 2.
- Match ending reasons:
- `sets`
- `forfeit_left` / `forfeit_right`
- `both_disconnect`
- `disconnect_timeout_left` / `disconnect_timeout_right`
## 1.5 Przeplyw danych
- Sterowanie: client -> WS `match.input` -> `Match.onInput` -> physics tick.
- Stan: server -> WS `match.state` (broadcast co tick).
- Reconnect: Redis snapshot (`match:{matchId}`) + IPC ownership map.
- Economy: Node direct MySQL transaction albo fallback signed HTTP do PHP.
## 1.6 Flow uzytkowania od wejscia do konca meczu
- Wejscie i walidacja username/suspension po stronie PHP page bootstrap.
- Ticket oparty o aktywna sesje PHP.
- Matchmaking przez Redis ZSET.
- Gameplay w Node (authoritative physics + score).
- Settlement rewards i statystyk.
- Powrot do lobby po animacji post-match.
## 1.7 Najwazniejsze obserwacje architektoniczne
- Architektura jest hybrydowa Node+PHP i dziala, ale ma duzy coupling w obszarze economy.
- Istnieja dwa niezalezne pathy rewardow z roznymi stawkami winnera.
- Cluster PM2 jest oparty o Redis IPC, ale Redis fallback do in-memory pozostaje aktywny i zmienia semantyke systemu rozproszonego.
---
# 2. MATCHMAKING
## 2.1 Jak gracze sa dobierani
- Queue: Redis ZSET `pp:1v1:queue:zset`.
- `enqueue` robi `zAdd` z `score=Date.now()`.
- Co 150 ms odpalany jest `dequeuePair`.
- `dequeuePair`:
- lock globalny `queue:lock` (`SET NX PX 200`)
- `zRange(0, -1)` po calej kolejce
- losowe dwa indexy
- `zRem` obu graczy
## 2.2 Bezpieczenstwo matchmakingu
Co jest OK:
- ZSET trzyma unikalny `userId` (brak wielokrotnych wpisow tej samej wartosci).
- Basic lock redukuje herd effect miedzy workerami.
Co jest ryzykowne:
- Globalny lock i pelny scan kolejki co 150 ms (O(n)) sa bottleneckiem skali.
- Lock TTL 200 ms moze wygasac podczas wolnych operacji (risk overlap).
- Brak atomowej logiki pairingu po stronie Redis (Lua/transaction script).
## 2.3 Race conditions i exploity
Krytyczne:
- Lost players po dequeue gdy walidacja username fail:
- para jest juz usunieta z kolejki
- przy `!leftUsername || !rightUsername` funkcja `return` bez requeue
- efekt: user znika z kolejki bez meczu (ghost/lost queue)
- Stale ownership keys (remote alive false-positive):
- dla remote owner `alive = !!owner` bez heartbeat worker liveness
- crash workera + TTL key -> matchmaking moze uznac gracza za aktywnego
- rezultat: dead/ghost match
- Queue leave vs dequeue race:
- gracz moze wyslac `queue.leave` gdy jest juz pobrany do pary
- brak finalnego potwierdzenia uczestnictwa przed utworzeniem Match
Wysokie:
- Brak MMR/skill/fairness, dobieranie losowe.
- Brak anti-abuse throttle na `queue.join` spam.
## 2.4 Podwojne matchowanie i stuck/ghost/dead przypadki
- Podwojne matchowanie (tego samego usera):
- niskie ryzyko na poziomie queue (ZSET unique)
- ale ryzyko istnieje po stronie reconnect (patrz sekcja 6), gdzie user moze wejsc ponownie do queue bez restore `session.matchId`
- Stuck queue:
- mozliwe przy stale state i lost dequeue path.
- Ghost queue:
- mozliwe po crash workerow i nieswiezych map ownership.
- Dead match:
- mozliwy przez stale ownership i cross-worker routing do niezyjacego workera.
- Duplicate match:
- low/medium ryzyko z uwagi na lock + unikalny userId w ZSET.
- wzrasta gdy lock TTL jest zbyt krótki vs latency.
## 2.5 Skalowalnosc matchmakingu (1k/10k/100k+ CCU)
1k CCU:
- prawdopodobnie dziala, ale z ryzykiem jitter i okazjonalnych race.
10k CCU:
- pelny `zRange(0,-1)` co 150 ms staje sie kosztowny.
- lock contention i redis CPU widoczne.
100k+ CCU:
- obecny algorytm nie jest wystarczajacy.
- global queue scan + global lock nie skaluja horyzontalnie.
## 2.6 Bottlenecks i distributed systems issues
- Jedna globalna kolejka + jeden lock.
- Brak shardingu/bucketow kolejki.
- Brak atomowego pairing script.
- Semantyka alive oparta na key presence, nie na real heartbeat worker/session.
---
# 3. WEBSOCKET ARCHITECTURE
## 3.1 Mapa eventow (kto, kiedy, co zmienia)
### Client -> Server
- `hello`
- owner: klient po open
- state: inicjalizuje session usera
- risk: replay ticket w oknie TTL
- `queue.join`
- owner: klient lobby
- state: queue zset add
- risk: spam bez rate limit
- `queue.leave`
- owner: klient lobby
- state: queue zset rem
- `match.input`
- owner: klient w meczu
- state: `players[side].input`
- risk: packet spam/CPU abuse
- `ping`
- owner: klient w meczu
- state: heartbeat + opponent ping forwarding
- `match.leave`
- owner: klient przy wyjsciu
- state: forfeit path
- risk: frame-close race
### Server -> Client
- `hello`
- handshake prompt
- `hello.ok` / `hello.error`
- auth result
- `queue.status`
- searching/idle + queue size
- `match.found`
- tworzy lokalny match context
- `match.reconnected`
- restore side/opponent/match metadata
- `match.snapshot`
- snapshot restore/final state hint
- `match.state`
- authoritative game state stream
- `match.set_break`
- break countdown signal
- `match.end`
- final payload + reason
- `rewards.done` / `rewards.queued` / `rewards.error`
- settlement status
- `pong`
- RTT update
- `opponent.ping`
- przeciwnik latency info
- `opponent.status`
- connected/disconnected signal
## 3.2 Kolejnosc i ownership
- Ownership sesji usera jest trzymany przez `userSockets` + Redis key `ws:w:{userId}`.
- Ownership meczu przez `match:w:{matchId}`.
- Cross-worker events routeowane Redis Pub/Sub (`ipc:w{worker}`).
## 3.3 Duplicate event risks
- Brak idempotency keys dla eventow gameplay.
- `match.input` nie uzywa `seq` do deduplikacji/reorder control.
- UI moze dostac stale kombinacje `match.snapshot` + `match.state` z roznych workerow.
## 3.4 Race conditions i packet spam
- Brak per-socket rate limit.
- Brak anty-spam dla `match.input`, `ping`, `queue.join`.
- `JSON.parse` i walidacja wykonywane dla kazdej ramki bez budget guard.
## 3.5 Heartbeat i stale sockets
- Brak WS-level ping/pong po stronie serwera (`ws.ping`).
- Heartbeat app-level (`ping`) tylko podczas meczu.
- Disconnect detection oparta o `lastSeenAt` (input lub ping).
## 3.6 Reconnect handling i ghost players
- Reconnect wymaga `matchId` hint w `hello`.
- Front usuwa `pp1v1.matchId` juz przy load strony.
- Browser refresh traci hint reconnectu.
- Efekt: mozliwy ghost player i draw timeout zamiast poprawnego resume.
## 3.7 Memory leak i dangling listeners
Server:
- `connections`, `userSockets`, `activeMatches` maja cleanup w typowych pathach.
- Brak graceful shutdown hooks (`SIGTERM`) moze zostawic stale keys i niesfinalizowane mecze.
Client:
- `setInterval` input loop zyje stale (celowo), ale wysyla tylko przy zmianie.
- Timery maja cleanup w return flow; brak twardego central cleanup managera, ale nie widac twardego leak path krytycznego.
---
# 4. GAME STATE
## 4.1 Gdzie przechowywany jest state
- Runtime authoritative state w `Match.state` (Node memory).
- Reconnect snapshoty w Redis (`match:{matchId}`) co `redisSnapshotMs`.
- Optional tick persistence do MySQL (`match_ticks`).
## 4.2 Authoritative model
- Ball/score/sets liczone po stronie serwera.
- Klient wysyla tylko input intent (`move`, `targetY`).
- Physics po stronie serwera.
## 4.3 Deterministic game loop
- Tick jest semi-deterministyczny:
- `dt` zalezny od czasu sciany i jitter scheduler
- `resetBall` uzywa `Math.random()` przy serwisie
- zatem brak strict determinism/replay determinism
## 4.4 Tick correctness
- Global single scheduler dla wszystkich matchy per worker.
- `dt` clamp 1ms..50ms ogranicza skoki.
- Przy duzym obciazeniu wszystkie mecze dziela jeden event loop worker.
## 4.5 Score i physics desync risk
- Score authoritative server-side, ale render client-side interpolowany predykcja.
- Desync UX mozliwy przy lag/jitter, logic desync final score mniej prawdopodobny.
## 4.6 Reconnect odzyskiwanie state
- dziala gdy klient poda prawidlowy `matchId` i trafi logicznie w reconnect path.
- refresh browsera jest problematyczny przez czyszczenie localStorage na starcie.
## 4.7 Miejsca powodujace instant draw / duplicate finish / phantom score
Krytyczne:
- `disconnect_timeout_{side}` konczy mecz jako draw (`winnerSide=null`) nawet gdy tylko jedna strona timeout.
- To umozliwia exploit: przegrywajacy disconnectuje i wymusza remis/refund.
Wysokie:
- Rozlaczenie + brak reconnect hint -> utrata kontroli paddle -> timeout draw.
- `_end()` ma guard `_ended`, wiec duplicate finish jest ograniczony.
Medium:
- MySQL `endMatch` i `processMatchResult` sa oddzielnymi operacjami; partial persistence mozliwa przy awariach miedzy krokami.
---
# 5. SERVER AUTHORITIVE ANALYSIS
## 5.1 Co jest server-authoritative
- Physics ball/paddle constraints
- Score/sets/end reason
- Match lifecycle state
- Final payload `match.end`
## 5.2 Co jest client-influenced
- Input intent frequency i pattern
- App-level ping values (`rtt` przesylane przez klienta)
- Queue join/leave cadence
## 5.3 Gdzie klient moze oszukiwac
- Nie moze bezposrednio ustawic score.
- Moze spamowac inputy dla DoS i unfair resource usage.
- Moze manipulowac reconnect pattern, by wymuszac draw przez timeout.
- Moze wysylac sztuczne `rtt` (informacyjne, niekrytyczne logicznie).
## 5.4 Czy klient moze manipulowac:
- wynikiem: bezposrednio nie, posrednio tak przez disconnect-draw exploit.
- pozycja: serwer clampuje i limituje velocity, wiec teleport cheating ograniczony.
- tickami: bezposrednio nie.
- eventami: moze floodowac i probowac replayowac legalne eventy.
## 5.5 Lista potencjalnych exploitow
- Intentional timeout draw exploit (ekonomia + rank integrity).
- Multi-tab/session race (duplicate_session tylko na active socket, nie na stale states).
- Reconnect hijack w oknie skradzionego ticketu (60s) bez nonce/one-time use.
- Flood `match.input` / `queue.join` / `ping`.
---
# 6. RECONNECT / DISCONNECT
## 6.1 Disconnect flow (server)
- `close` event:
- usuwa mapowania user socket
- user poza meczem -> leaveQueue
- user w meczu -> `onDisconnect` (chyba ze intentional leave)
- Dodatkowo safety net:
- brak input/ping przez `disconnectStatusMs` -> opponent status disconnected
- timeout `disconnectForfeitMs` -> end reason disconnect timeout
## 6.2 Reconnect flow
- klient wysyla `hello` z opcjonalnym `matchId`.
- server probuje local match reconnect.
- albo pyta Redis o owner workera i forwarduje `match.reconnect`.
## 6.3 Browser refresh i network interruption
Krytyczne:
- Front usuwa localStorage matchId przy init.
- Refresh usuwa hint niezbedny do `match.reconnected`.
- User po refresh zwykle nie dostaje legalnego session.matchId server-side.
Skutek:
- gracz moze nie moc wysylac inputow po refresh.
- mecz konczy sie timeout draw zamiast poprawnego resume.
## 6.4 Packet loss i websocket reconnect
- Klient probuje reconnect 5 razy (rosnacy delay).
- Brak explicit exponential jitter strategy per infra signal.
- Brak server-side session token dedicated for robust resume niezalezny od localStorage.
## 6.5 Ghost sessions i orphan matches
- Ghost sessions: mozliwe przez stale worker keys i crash bez cleanup.
- Orphan matches: mozliwe przy crash worker (brak graceful drain).
- Redis snapshot pomaga, ale ownership mapping liveness nie jest twardo gwarantowany.
## 6.6 Czy gracze moga przegrywac przez lag
- Tak, i nawet remisowac przez timeout przy chwilowym packet-loss > window.
- Disconnect status i timeout sa relatywnie agresywne dla niestabilnych sieci.
## 6.7 Edge cases (komplet)
- refresh strony w trakcie seta
- worker crash w trakcie meczu
- stale ownership key po crash
- `match.leave` frame utracony przy natychmiastowym close
- reconnect na innym workerze bez matchId hint
- oba sockety alive, ale brak input+ping -> false disconnect
- opoznione IPC message po zakonczeniu meczu
---
# 7. SYSTEM PLAYONS / WALLET
## 7.1 Aktualny model economy (as-is)
- Brak widocznego debit stake przy starcie meczu.
- Na koncu meczu wykonywane sa credit operations do `user_stats.balance`.
- Winner/loser rewards stale, hardcoded.
- Draw daje refund (tez hardcoded).
## 7.2 Ledger i atomic operations
- Jest tabela `transactions` i wpisy transakcji.
- Node direct path ma transakcje DB (`beginTransaction/commit`) i idempotency `match_rewards_log`.
- PHP fallback ma osobny flow `rewards_jobs` + inline processing i tez transakcje.
## 7.3 Krytyczny problem ekonomii
- Rozjazd reward constants:
- Node direct winner = 0.80
- PHP fallback winner = 1.00
- loser 0.20, draw 1.00
To oznacza niespojnosc finansowa zalezna od sciezki runtime.
## 7.4 Mozliwe exploity economy
Krytyczne:
- Disconnect timeout draw exploit:
- przy jednostronnym timeout mecz konczy sie draw
- to pozwala uniknac porazki i potencjalnie wymusic refund flow
- Brak stake debit before match:
- system jest praktycznie reward-only crediting
- mozliwa inflacja salda nawet przy przegranej (loser +0.20)
Wysokie:
- Dwa niezalezne settlement pathy (Node/PHP/cron) moga tworzyc rozjazdy operacyjne.
- DDL w runtime moze destabilizowac settlement pod obciazeniem.
## 7.5 Double spend / duplicate payout / rollback
- Node direct: idempotency przez `match_rewards_log` (dobry kierunek).
- PHP fallback: idempotency przez `rewards_jobs` unique match_key.
- Globalnie: trzy miejsca logiki reward (Node direct, PHP endpoint inline, CRON worker) zwiekszaja blast radius niespojnosci.
## 7.6 Floaty i rounding
- DB amounty sa DECIMAL(12,2) (dobrze).
- W payload/UI wystepuja float casty, ale glowna ksiega jest decimal DB.
- Rounding risk medium/low, glowny problem to logika stawek, nie precision.
---
# 8. REDIS ANALYSIS
## 8.1 Uzycie Redis
- Queue ZSET
- Queue lock string key
- Match snapshot string key JSON
- User->worker mapping
- Match->worker mapping
- IPC Pub/Sub channels per worker
## 8.2 Key structure
- `pp:1v1:queue:zset`
- `pp:1v1:queue:lock`
- `pp:1v1:match:{matchId}`
- `pp:1v1:ws:w:{userId}`
- `pp:1v1:match:w:{matchId}`
- `pp:1v1:ipc:w{workerId}`
## 8.3 TTL strategy
- user worker mapping: EX 7200
- match worker mapping: EX 14400
- snapshot match: zwykle 30 min, final snapshot 5 min
- queue entries: brak TTL (usuniecie explicit)
## 8.4 Stale keys i memory growth
- stale ownership keys po crash do TTL expiry.
- brak okresowego refresh heartbeat dla ownership (moze wygasnac przy dlugich sesjach).
- snapshoty maja TTL, wiec growth ograniczony czasowo.
## 8.5 Locks/pubsub/distributed sync
- Lock nie jest fenced i ma krotki TTL.
- Unlock jest best-effort (`get` + `del`), bez Lua atomic compare-delete.
- Pub/Sub daje co najwyzej at-most-once semantics.
- Brak durable queue dla IPC events.
## 8.6 Bottlenecks i scaling risks
- queue scan O(n)
- global lock contention
- cross-worker routing wymaga extra Redis operations per remote input/event
- przy duzym cross-worker mix moze byc Redis CPU/network bottleneck
---
# 9. DATABASE ANALYSIS
## 9.1 Transaction safety
- Node direct settlement: transakcyjny block i rollback (dobrze).
- PHP settlement: rowniez transakcja dla glownej logiki.
- `endMatch` update i final settlement sa rozdzielone (partial state possible).
## 9.2 Consistency i duplicate writes
- `INSERT IGNORE` + unique keys ograniczaja duplikaty.
- Rozne pathy rewardow moga miec inna semantyke payout.
- Brak jednego canonical write-service dla economy.
## 9.3 Indeksy i query performance
Pozytywne:
- `transactions` ma `(user_id, created_at)`.
- `match_results` ma unique `(discipline, mode, match_key)` i index winner/loser.
- `rewards_jobs` ma unique `(discipline, mode, match_key)` i index `(status, created_at)`.
Ryzyka:
- DDL wykonywany runtime w request path i settlement path.
- Optional `match_ticks` moze bardzo zwiekszac write volume.
## 9.4 Rollback safety
- rollback obecny przy exceptions.
- brak external saga compensation gdy czesc flow zakonczy sie po commit a przed broadcast/cleanup.
## 9.5 Query concurrency
- locki i contention potencjalne przy masowym settlement.
- connectionLimit default 20 na worker przy PM2 cluster moze latwo rozmnozyc laczne polaczenia do MySQL.
---
# 10. SECURITY ANALYSIS
## 10.1 WebSocket abuse
- Brak rate limiting i quotas per socket/user/IP.
- `maxPayload` 16KB jest, ale to nie zabezpiecza przed high-rate spam.
## 10.2 Replay attacks
- Ticket ma `exp` 60s i HMAC, ale brak nonce one-time store.
- Replay w oknie TTL jest mozliwy, ograniczony przez duplicate active session check.
## 10.3 Forged events
- Bez valid ticketu eventy sa odrzucane (`not_authenticated`).
- Po uwierzytelnieniu brak granular ACL na event frequency/shape poza podstawowa walidacja.
## 10.4 Session hijacking / reconnect hijacking
- Kradziez aktywnej sesji PHP lub ticketu umozliwia przejecie wejscia do WS w oknie TTL.
- Brak binding ticketu do IP/UA/fingerprint.
## 10.5 Reconnect hijacking
- Resume oparty o `matchId` i ownership keys.
- Brak dedykowanego signed reconnect tokena z rotacja.
## 10.6 Fake matches / fake payouts
- Rewards endpoint HMAC signed i ma timestamp skew check (dobrze).
- Brak nonce anti-replay w naglowkach HMAC, replay w oknie czasu blokowany glownie przez idempotency DB kluczy.
## 10.7 Redis abuse i DoS vectors
- Queue spam (`queue.join`) i input spam.
- Cross-worker remote input path generuje dodatkowe Redis obciazenie.
- Global lock + queue full scan to latwy target latency DoS.
## 10.8 Krytyczne dodatkowe ryzyko
- `session_bootstrap.php` zawiera hardcoded DB credentials (`root` + haslo) w kodzie.
- To jest security smell wysokiego ryzyka operacyjnego i audytowego.
---
# 11. PERFORMANCE ANALYSIS
## 11.1 CPU bottlenecks
- Matchmaking O(n) scan queue.
- JSON parse/stringify wysokiej czestotliwosci.
- Tick loop na pojedynczym event loop per worker dla wszystkich meczy workera.
## 11.2 Redis bottlenecks
- global lock queue
- zRange full
- remote routing extra calls (`getMatchWorker`/`ipcSend`)
## 11.3 Memory bottlenecks
- `activeMatches` i state obiektow rosna liniowo z liczba aktywnych meczy per worker.
- Snapshot JSON i state allocations per tick/persist.
## 11.4 WebSocket scaling i throughput
Przyblizenie (tylko `match.state`):
- Mecz: 30 tick/s * 2 klientow = 60 msg/s
- 1k graczy (~500 meczy): ~30k msg/s
- 10k graczy (~5k meczy): ~300k msg/s
- 100k graczy (~50k meczy): ~3M msg/s
Do tego input messages i ping oraz IPC overhead.
## 11.5 Tick loop cost i GC pressure
- Kazdy tick tworzy payloady JSON i serializacje.
- Brak pooling/zero-copy strategii.
- Potencjalnie duzy GC pressure przy bardzo duzym concurrency.
## 11.6 Czy architektura wytrzyma skale
1k graczy:
- Tak, przy dobrej infrastrukturze i monitoringu.
10k graczy:
- Ryzykowne bez zmian matchmaking i write-path.
- Mozliwe bottlenecks Redis i MySQL.
100k graczy:
- Obecna architektura nie jest gotowa.
- Niezbedne redesign matchmaking i transport efficiency.
Kilkaset tysiecy:
- Bez istotnej przebudowy distributed model i economy pipeline: nie.
---
# 12. CODE QUALITY ANALYSIS
## 12.1 Spaghetti dependencies / duplicate logic
- Economy logic jest zduplikowana i rozjechana:
- Node `processMatchResult`
- PHP rewards endpoint inline
- CRON worker rewards
- DDL obecny w wielu runtime pathach.
## 12.2 Anti-patterns
- Runtime schema migrations (CREATE/ALTER) w request handlers.
- In-memory Redis fallback w architekturze deklarowanej jako cluster distributed.
- Global queue scan lock pattern.
## 12.3 Unsafe async / unhandled promises
- Sporo fire-and-forget `void` calli (celowe), ale bez centralnego telemetry/compensation.
- Brak timeout/circuit breaker w `fetch` fallback rewards.
## 12.4 Missing validation
- Brak strict rate limiting eventow.
- `status.php` participant guard zalezy od payload completeness.
## 12.5 Missing cleanup
- Brak graceful shutdown hooks dla cleanup ownership keys i open matches.
## 12.6 Missing tests
- Brak test suite Node server (physics, reconnect, reward idempotency, queue races).
---
# 13. PRODUCTION READINESS SCORE
## 13.1 Ocena modulow (1-10)
- Core gameplay physics authority: 7/10
- WebSocket transport i event model: 6/10
- Matchmaking scalability/concurrency: 3/10
- Reconnect/disconnect resilience: 4/10
- Redis distributed coordination: 4/10
- Economy/playons settlement consistency: 3/10
- DB safety i idempotency foundations: 6/10
- Security hardening (abuse/replay/rate limit): 4/10
- Observability/operability: 4/10
- Testability/quality gates: 2/10
Global production readiness (dla duzej skali PvP): 4/10
## 13.2 Krytyczne bledy (Critical)
- C1: Disconnect timeout jednostronny konczy mecz jako draw (exploit fairness + economy).
- C2: Niespojne reward constants Node direct vs PHP fallback.
- C3: Brak stake debit i reward-only economy (inflation exploit vector).
- C4: Matchmaking O(n) full queue scan + global lock nie skaluje do high CCU.
- C5: Reconnect po browser refresh jest niestabilny przez usuwanie `pp1v1.matchId` na start.
## 13.3 High priority
- H1: Stale ownership keys i false-positive alive dla remote worker.
- H2: Brak event rate limiting i anti-spam.
- H3: Runtime DDL w settlement/request paths.
- H4: Brak graceful shutdown i recovery strategy dla aktywnych meczy.
- H5: Brak timeout/retry policy/circuit breaker dla fallback rewards fetch.
## 13.4 Medium priority
- M1: Brak deterministic replay capability (debug/anti-cheat forensic limitation).
- M2: Brak dedup/order handling dla `seq` w `match.input`.
- M3: Prosta klasyfikacja ping quality bez hysteresis.
- M4: Niewystarczajaca separacja warstw economy od gameplay orchestratora.
## 13.5 Low priority
- L1: Uporzadkowanie nieuzywanych helperow (`playerKey` etc.).
- L2: Ujednolicenie naming/reason messages.
- L3: Drobne UX niespjnosci statusow reconnect.
---
# 14. FINAL REFACTOR ROADMAP (kolejnosc dzialan)
## 14.1 Etap 0 - Immediate Hotfix Safety (najpierw)
- Ujednolicic semantyke `disconnect_timeout_*` (jednostronny timeout nie moze dawac remisu z refund policy).
- Ujednolicic payout constants i settlement source-of-truth.
- Zablokowac ekonomiczne rozjazdy miedzy Node/PHP/CRON.
- Naprawic reconnect po refresh (trwale i bezpieczne resume identity).
## 14.2 Etap 1 - Economy Integrity Core
- Jedna canonical sciezka ledger/settlement.
- Twarda idempotency warstwa i audit trail.
- Stake lifecycle end-to-end: reserve -> settle/refund -> journal.
- Ograniczyc runtime DDL do migracji deploymentowych.
## 14.3 Etap 2 - Matchmaking i Distributed Correctness
- Przebudowa matchmaking (atomowy dequeue bez full-scan).
- Sharding/bucketing queue.
- Worker/session heartbeat z twardym liveness, nie tylko key presence.
- Harden locks (atomic compare-delete / script).
## 14.4 Etap 3 - WebSocket Hardening i Anti-Cheat
- Rate limits per event/user/IP.
- Abuse budgets i temporary bans.
- Seq/order validation pipeline.
- Strong reconnect tokens i sesja resume security.
## 14.5 Etap 4 - Performance Scaling
- Ograniczenie write pressure MySQL (batch/coalesce, optional ticks policy).
- Optymalizacja serialization/state broadcast.
- Capacity tests 1k/10k/100k z SLO gates.
## 14.6 Etap 5 - Operability i QA
- Metrics/tracing/alerts (queue depth, tick lag, reconnect success, reward latency).
- Testy automatyczne (unit + integration + load + chaos disconnect).
- Runbook incident response.
## 14.7 Moduly najbardziej niebezpieczne
- `node-server/src/matchmaking.js`
- `node-server/src/index.js` (disconnect/reconnect lifecycle)
- `node-server/src/mysqlWriter.js` + `api/matches/ping-pong/1v1/index.php` (economy divergence)
## 14.8 Co przepisac calkowicie vs poprawic
Przepisac (high confidence):
- matchmaking engine (algorytm + distributed lock semantics)
- settlement orchestration (single source ledger flow)
Mocno przebudowac:
- reconnect/session restore protocol
- distributed ownership liveness model
Poprawic inkrementalnie:
- physics core (dziala relatywnie poprawnie)
- UI interpolation i ping UX
- health/monitoring endpointy
---
## Konkluzja
System ma solidny fundament server-authoritative gameplay dla 1v1, ale obecnie nie jest production-ready dla wysokiej skali i ekonomii stake/playons o wysokiej integralnosci. Najpowazniejsze ryzyka dotycza nie fizyki gry, tylko consistency economy, reconnect correctness i distributed matchmaking semantics pod obciazeniem.