Skip to content

refactor(games): consolidate TF2 log parsing into the tf2-analyst module - #791

Open
garrappachc wants to merge 4 commits into
masterfrom
refactor/tf2-analyst-log-parser
Open

refactor(games): consolidate TF2 log parsing into the tf2-analyst module#791
garrappachc wants to merge 4 commits into
masterfrom
refactor/tf2-analyst-log-parser

Conversation

@garrappachc

Copy link
Copy Markdown
Member

Why

TF2 log-line interpretation was split across two layers — match-event-listener (line → event regexes, with a bit of in-memory restart-detection state) and track-match-rounds (round assembly, side-swaps, score aggregation in Mongo). This makes the parsing logic hard to reason about in isolation and impossible to reuse.

This consolidates all of it into a self-contained, stateful fold:

analyze(context: GameContext, line: string): LogEvent[]

under src/tf2-analyst/, as a first step toward extracting it as a standalone package. GameContext is a plain, serializable per-game blob the caller owns; LogEvent is a discriminated union of interpreted events (round ended, teams swapped, score reset, player connected, …).

How

  • src/tf2-analyst/analyze + GameContext + LogEvent, with all restart-detection, round-assembly and stopwatch-swap logic folded in. Reuses the app's SteamId64/Tf2Team/is-stopwatch-round for now (marked with TODOs to isolate on extraction).
  • parse-game-log plugin — a thin adapter that serializes per logSecret (giving the fold a single-threaded, ordered stream), persists the context, and translates LogEvent[] back into the existing match:* events + roundEnded/teamsSwapped game-event writes. All downstream consumers are unchanged.
  • The persisted GameContext (in games.logparsestate, TTL-swept) replaces the games.roundprogress collection.
  • Deletes match-event-listener, track-match-rounds, and the four now-internal events (match:roundWon, match:roundLength, match/score:reported, match/controlPoint:captured).

Since the single-threaded fold removes cross-line races, the old atomic round-commit / duplicate-key-retry machinery is gone.

Verification

  • Unit/type/lint green locally; tf2-analyst ports the old match-event-listener + track-match-rounds test cases and adds round-assembly/idempotency coverage.
  • The e2e log-replay specs (tests/20-game/15–18, 20, 21) are the real integration gate — relying on CI to run them.

Extract all TF2 log-line interpretation (regex matching, round assembly,
stopwatch side-swaps, restart detection, running score) into a
self-contained, stateful `analyze(context, line)` fold under src/tf2-analyst,
as a step toward publishing it as a standalone package. A thin per-game
serialized adapter (parse-game-log) drives it and translates its events back
into the existing match:* events, so downstream consumers are unchanged. The
persisted GameContext blob (TTL-swept) replaces the games.roundprogress
collection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Playwright test results

passed  246 passed

Details

stats  246 tests across 47 suites
duration  12 minutes, 53 seconds
commit  f7249a7

TF2 servers emit many log lines a second, most matching nothing; reading and
writing the context per line spammed the database. Hold it in memory (and cache
the logSecret->game lookup), reading from the db only on a cache miss and
writing only when a line actually moves the context.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/games/plugins/parse-game-log.ts
Comment thread src/games/plugins/parse-game-log.ts Outdated
Comment thread src/games/plugins/parse-game-log.ts Outdated
Comment thread src/games/plugins/parse-game-log.ts Outdated
Rename the module to tf2-game-analyzer and expose a Tf2GameAnalyzer class that
owns the context, tracks its own dirtiness and exports it for persistence, so
the adapter no longer stringifies the context inline. Addresses PR review:
delay the in-memory cache eviction after game end (logs.tf/demos.tf uploads
still arrive) and drop the redundant comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@garrappachc garrappachc left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review — a few things worth a look before merge. The refactor itself is a nice consolidation; round-assembly idempotency, restart-doubling detection, the stopwatch-swap logic and the regexes all match the old behavior. Findings inline, ranked by severity.

Comment thread src/tf2-game-analyzer/analyze.ts
Comment thread src/games/plugins/parse-game-log.ts
Comment thread src/games/plugins/parse-game-log.ts
Comment thread src/tf2-game-analyzer/analyze.ts Outdated
Comment thread src/tf2-game-analyzer/analyze.ts
- clear swapPending on Game_Over so a next-map Round_Start on a still-cached
  analyzer can't emit a stray teams-swapped onto the ended game
- drop the never-read isStopwatch context field
- evict the per-logSecret queue entry alongside the other caches on game end
- negative-cache unknown games for a minute so a stale gameserver streaming to
  a swept game no longer fires a findOne per line

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant