# 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.