-
Mahmoud Aglan authored
Four independent defects each broke competitive chess on their own, and they compounded. Verified against live production (Supabase, the Swiss API and the CapRover deployment), not inferred from code alone. Full diagnosis in FIX_PLAN.md. A. Match completion was rejected for every decisive game. The client sends result 'win'|'loss'|'draw'; game.php's $validResults did not contain 'win' or 'loss', so every won or lost game got a 400. That list was also wrong in its own right — timeout_white, abandon_white, resign_white, checkmate_* and bot_* are not in the public.match_result enum. And handleComplete ignored the write's return value, so a Postgres rejection was invisible and the endpoint still reported success. Consequence: no rating, no coins, no XP, matches stuck in_progress forever, tournament results lost, and the winner shown a fabricated +12. The client now reports only *how* the game ended. The server derives the winner from the final position (the mated side is the side to move), from who resigned, or from whose clock expired, validates against the real enum, and verifies the write landed. A losing player can no longer report a win. B. Both players could be assigned White — "playing against yourself". `playerColor = color || 'w'` defaulted both players to White whenever a caller supplied no colour, and the poll only ingests the opponent's move while it is *not* your turn, so two Whites never saw each other and each played a private board. The async correction also wrote gameState.playerColor while the clock closures still read a stale local copy. Colour now has one source of truth. game.php returns my_color computed server-side; the board stays inert until the server confirms it. Fixed the callers that passed no colour (refresh recovery, group invites — which also lacked mode:'live' and so started a bot game) and stopped lobby.js guessing from isHost. C. Finished matches were never released. unmountGame never tore down the match session, so localStorage kept the recovery key; combined with A, every app start dragged players back into a dead game with no colour, straight into B. D. Hand-offs were destroyed. matchmaking.php deleted all of a player's queue rows including one already matched and carrying a match_id, so the waiting player never learned their match and their opponent sat alone. Tournament create-or-join did a read-then-insert with no lock, so both paired players could create their own match. Match ids are now derived from (tournament, round, board): both clients compute the same id and the primary key decides, so exactly one match exists per pairing. E. There was no tournament engine at all. Nothing created a Swiss tournament, consumed auto_start, generated pairings, advanced a round or computed standings. api/swiss.php sent no Authorization header, so every call to the external pairing service returned 401 and each getter swallowed it and returned an empty array — which is why standings and pairings have always rendered blank. includes/tournament-engine.php is a native Dutch-system Swiss engine with no external dependency: backtracking pairing that never repeats an opponent, FIDE colour allocation, byes, Buchholz cut-1 / Buchholz / Sonneborn-Berger tiebreaks, automatic round advance and final placings. api/tournament-admin.php creates and starts tournaments; api/cron.php is the scheduler that acts on auto_start and forfeits no-shows. Also fixed - Server authority: turn ownership, move-count monotonicity, FEN transition sanity and a server-side clock, so a client can no longer overwrite the position or report its own remaining time. - Disconnect detection was dead: match-live marked the opponent active on every poll, and match-session's abandon branch was unreachable behind the disconnect branch's latch. Heartbeats are now recorded per player in game_state. - Draw acceptance required no offer from the opponent — a way out of a lost game. - Removed the in-process HTTP call back into our own Apache for tournament reporting (deadlock risk, and it lost results silently when it failed). - Three time controls in the picker (blitz_5_5, rapid_20_0, classical_45_45) are not in the time_control enum; choosing one created no match. - Auth did an upstream GoTrue call plus a ban query on every request, including the 2s poll. Now verifies HS256 locally when the secret is set, otherwise memoises briefly. - curl_close() removed repo-wide: deprecated in 8.5, where its notice lands in the middle of every JSON body. Dockerfile now turns display_errors off. Tests: tests/run.sh — 5 suites against a disposable local Postgres carrying the production schema. Covers 2-100 player tournaments, 167 real master-game move transitions (no legal move is ever rejected), the pairing race, JWT verification, and a full two-client game and tournament over HTTP. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
33fd4d40
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| constants.php | ||
| database.php |