refactor(server): extract the match telemetry recorder out of GameServer - #5145
Conversation
Phase 3 of docs/GameServerRefactor.md, sixth and last module. One match's view of the telemetry stream moves into src/server/MatchTelemetryRecorder.ts: the event envelope and per-match sequence (emit, with a throwing emitter counted as a drop), the per-tick intent counters the turn_committed event reports (intentObserved, takeTickCounts), the archive-attempted flag, and match_finished going out once however many times end() runs. identityFor, which keeps persistentID out of telemetry, is an exported function. GameServer passes turns.length explicitly where the old emitTelemetry default applied. No behaviour change: the golden wire snapshot is untouched and MatchTelemetryIntegration.test.ts passes unmodified. Tests: MatchTelemetryRecorder.test.ts covers the envelope, the sequence gap a drop leaves, the counters, and finished-once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review. WalkthroughGameServer telemetry handling now uses a dedicated ChangesTelemetry recorder extraction
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This refactor moves telemetry state into a dedicated recorder while preserving sequencing, drop handling, identity minimization, and completion behavior; no actionable merge-blocking risk remains at the current head after normal checks and review. Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Claude Code ReviewVerdict: No issues found — clean, faithful mechanical refactor. 0 findings (0 critical, 0 major, 0 minor). This PR extracts
No CLAUDE.md violations: no Four independent review passes (2× CLAUDE.md compliance, 2× bug-focused) each returned zero findings. |
## Summary Phase 4 of `docs/GameServerRefactor.md` (the plan checked in by #5114; Phases 0–3 are merged, #5145 was the last Phase 3 module). - **`src/server/Roster.ts`** (new): the seven parallel client collections in `GameServer` — `activeClients`, `allClients`, `websockets`, `persistentIdToClientId`, `admittedPersistentIds`, `kickedPersistentIds`, `clientsDisconnectedStatus` — become one object. Bookkeeping: `add`, `reconnect` (also swaps in the new socket and closes the old one, which `rejoinClient` did by hand), `markLeft`, `forgetReconnect` (the lobby-phase seat release), `kick`, `pruneStale`, `closeAll`. Reads: `active()` (readonly, join order kept — `gameStartInfo.players` depends on it), `players()`, `all()`, `get()`, `byPersistentId()`, `isKicked`, `wasAdmitted`, `isDisconnected`/`setDisconnected`, `votingUniqueIPs` (moved with its spectator-electorate comment). - **`GameServer.ts`** keeps every policy decision — allowlist, IP cap, duplicate sessions, host-left, the kicked checks on join and reconnect — and delegates the bookkeeping through `this.clients` (`roster()` is already a public method, hence the field name). `byPersistentId()` is deliberately raw so `lobbyCreatorID` resolves exactly as before; the kicked exclusion stays in `getClientIdForPersistentId`. 1,944 → 1,888 lines. - `activeClients` is no longer public; `GameManager.activeClients()` sums `numClients()`. - **Tests:** `Roster.test.ts` (9 tests) covers the bookkeeping on its own. The seven roster reach-ins in `GameServerRejoin.test.ts` and `AdminBotIntent.test.ts` are replaced by real joins and observable outcomes (`wasAdmitted(...)` false, a re-join returning `"kicked"`). `(game as any)` reach-ins 36 → 29; what remains is lifecycle/ingress state for Phases 5–6. Pure move, no behaviour change: the golden wire transcript (`GameServerWire.test.ts.snap`) is byte-identical. ## Test plan - `npx vitest tests/server --run`: 53 files / 531 tests pass (was 52 / 522); snapshot unchanged - `npx tsc --noEmit`, `npm run lint` (oxlint + eslint), `prettier --check`: clean - Mutation spot-check: dropping the kicked exclusion from `Roster.wasAdmitted` fails 4 tests across 4 files (`Roster`, `AdminBotIntent`, `GameServerJoin`, `GameServerRejoin`), restored - Full `npm test`: 31 failed files / 379 failed tests — identical to pristine `origin/main` (pre-existing client `localStorage` failures) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Phase 3 of
docs/GameServerRefactor.md, sixth and last module extraction (previous: #5114, #5116, #5117, #5122, #5124, #5126, #5142, #5143). A pure move with no behaviour change — the golden wire snapshot is untouched andMatchTelemetryIntegration.test.tspasses unmodified.src/server/MatchTelemetryRecorder.ts(new): one match's view of the telemetry stream.emitbuilds the envelope (schemaVersion,matchId, per-matchsequence,observedAt,serverTick); a throwing emitter counts as a drop and still consumes a sequence number, so gaps mark drops.intentObserved/takeTickCountskeep the per-tick countersturn_committedreports;noteArchiveAttemptedandmatchFinished(once, however many timesend()runs) cover the end of the match.identityFor, which keepspersistentIDout of telemetry, is an exported function.GameServer: four fields and four private methods gone; every emit site passesturns.lengthexplicitly where the oldemitTelemetrydefault applied. Net −68 lines.tests/server/MatchTelemetryRecorder.test.ts(new, 7 tests): envelope/sequence/tick, the sequence gap a drop leaves, per-tick counters split by verdict, zero counts and clearing on take, finished-once with the archive flag,identityFornever carrying the persistentID.With this, Phase 3 is complete: six modules in six PRs, golden snapshot unchanged throughout.
GameServer.tsis 1,944 lines (from 2,365) and test reach-ins are 36 (from ~150), none of them spies on private methods; what remains is lifecycle and ingress state, which Phases 5–6 own.Test plan
npx vitest tests/server --run: 52 files / 522 tests green (was 51 / 516).GameServerWire.test.ts.snap): unchanged.matchFinishedfails 1 test; restored.npm test: 31 failing files / 379 tests — identical to the pristineorigin/mainbaseline (the pre-existinglocalStorageclient failures); +6 passing.tsc --noEmit, prettier, oxlint, eslint clean.🤖 Generated with Claude Code