Repository navigation
fix(statistics): stop suppressing victim deaths so the F13a cap stops undercounting - #18
Merged
Merged
Conversation
… undercounting
The F13a per-game cap left the pre-F13a `counterDeathReferences` suppression
in place, which leaks deaths. The set held `observedPlayerRef` for every
`player_counter` with a DEFINED `counterDeaths(payload)` (an explicit-zero
`deaths_total:0` still yields `{total:0}`, which is defined), so the victim
kill-row death path was suppressed whenever the entity had any counter — even
when the counter path then recorded nothing (explicit-zero magnitude, or an
`observedPlayerRef` that did not resolve in `playersByEntity`). The two paths
also key differently (counter on `observedPlayerRef`, victim on
`String(victim_entity_id)`), so a resolution mismatch dropped the death from
both paths. Result: recalc `deaths.total` ran ~5% below legacy and below the
parser's own `d>0` replay count.
With F13a's per-replay cap (`foldCappedDeaths`, <=1 total and <=1 by_teamkills
per replay) already in place, the suppression is redundant: double-counting
between the counter path and the victim path collapses to a single death. Drop
`counterDeathReferences` and the two victim-path suppression checks; let
whichever path resolves the entity record the capped death. All-time
`deaths.total` now equals the count of distinct replays the player died in,
matching legacy "games-died-in" semantics. The one-life cap is preserved
(d=3 in one replay still counts 1) and `by_teamkills <= total` still holds.
Service-layer only: no parser, schema, repository SQL, or API-contract change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Afgan0r
marked this pull request as ready for review
June 14, 2026 16:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After F12 + F13a (PR #17) + a full recalc,
sgdeaths.totalruns ~5% below legacy, while games/kills are within ~1.5%. Per-player, the recalc'sdeaths.totalis 3-6% lower than the parser's own death-game signal (replays whereplayer_counter.deaths_total > 0, i.e. parserplayer.d > 0), which itself matches legacy. The parser is correct; the loss is in server-2's death aggregation.Root cause
The F13a per-game cap (
foldCappedDeaths) was added, but the pre-F13acounterDeathReferencessuppression was left in place incalculatePlayerAndSquadAggregates(src/modules/statistics/service/service.ts):counterDeathReferencesheldobservedPlayerReffor everyplayer_counterwhosecounterDeaths(payload)is DEFINED. An explicit-zero counter (deaths_total:0) still yields a defined{total:0,…}, so it landed in the set.applyVictimDeath/applySquadEventsuppressed the victim kill-row death whenevercounterDeathReferences.has(String(victim_entity_id)).applyCounterEventdropped the death when itsobservedPlayerRefdid not resolve inplayersByEntity, andincrementDeathsByCounteradds 0 for an explicit-zero counter.So for an entity with a present-but-non-contributing counter plus a real victim kill row, the death was lost from both paths. The two paths also key differently (counter:
observedPlayerRef; victim:String(victim_entity_id)), so any resolution mismatch leaked a death.Fix
With F13a's per-replay cap already collapsing any double-count to <=1 death and <=1 teamkill death per replay, the suppression is redundant — its only remaining effect was this leak. Remove
counterDeathReferencesand the two victim-path suppression checks; let whichever path resolves the entity record the capped death.Result: all-time
deaths.totalequals the count of distinct replays the player died in (legacy "games-died-in" semantics). One-life cap preserved (d=3 in one replay still counts 1);by_teamkills <= totalstill holds.Service-layer only — no parser, schema, repository SQL, or API-contract change.
Tests (TDD)
Added to
service/tests/aggregates.test.ts(failing repro written first):deaths.total: 1(was0).by_teamkills <= total.total: 3.Validation
pnpm lint,pnpm typecheck,pnpm format— green.pnpm test(unit) — 657 passed (78 files);aggregates.test.ts14/14.service.tsis 100% (statements/branches/functions/lines) from the unit suite. The fullpnpm test:coveragegate runs the integration files (*/tests/postgres.test.ts,src/test/integration/**) which need Docker/testcontainers; Docker was unavailable in this environment, so the repo-wide 100% gate (dependent on those integration files for the repository/route/infra layers) was not exercised here.Planning doc:
.planning/quick/260614-f14-f13a-deaths-fold-undercount/.🤖 Generated with Claude Code