maniator / maniator/verticopolis
[Feature]: [P2] session_fps field telemetry + Frame health section in the analytics report
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
> From the full repository audit (2026-07-21, recommendation N-1; report at _bmad-output/planning-artifacts/reviews/full-repository-audit-2026-07-21/), design shaped with the owner in session. Backlog row session-fps-telemetry.
Problem
Nothing measures real-player frame rate. The audit measured hard scaling on real saves (engine tick 8.6 ms/game-minute, 53 ms hour-boundary spikes at 25k units) but its absolute FPS numbers came from a GPU-less container; the render-perf spec says on-device data should gate the remaining tuning (drain budget, hour-boundary amortization, tick follow-ups in #539). Field data decides whether and where that effort goes.
Design
- Capture in
runFrame(src/game/frameLoop.tsreceivesdtMs): a fixed array of frame-time buckets (<8, 8-16, 16-33, 33-66, >66 ms), one integer increment per frame, no allocation. Pause counting whiledocument.hidden, while paused/in menus, and for a warm-up window after load or import. - Emit once per session at the first background after gameplay (the lesson #531 learned:
session_endre-fires per tab-hide and there is no session id to dedup by), via the existingGameplaySessionmachinery. - Payload shape depends on decision D-1 (audit §19, tracked in #542): under Vercel WA, named bands plus a combined
fps_units: "30-45|5k-15k"property (25 values, under the 100-bucket cap) to get the fps-by-tower-size correlation; under PostHog, raw p50/p95 values with real percentiles server-side and no band tricks. - Report: a "Frame health" section in
scripts/analytics-report.mjsin the shape of the merged Depth section (share of sessions per band, fps x tower-size matrix), or its PostHog equivalent.
Dependencies
- The merged #531 session machinery (in main).
- Payload/report shape finalized by D-1 (#542). Capture-side work is identical either way and can start first.
Acceptance criteria / required tests
- Bucket counting is exercised by a unit test (hidden/paused/warm-up exclusions pinned); zero allocation on the frame path (no new objects in
runFrame). - At most one
session_fpsemission per session, pinned like the depth-events once-per-session test. - Report section renders with real and empty data, with the same skip/truncation honesty as the Depth section.
- Event property cardinality stays under the platform's group-by cap.
Analytics/tooling: /bmad-code-review.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/game/frameLoop.ts and the existing GameplaySession and depth-events once-per-session tests, then read scripts/analytics-report.mjs and decision D-1 in #542. Confirm the finalized payload shape before implementing. Done means frame buckets exclude hidden, paused, menu, and warm-up periods; emission occurs once per session; and the report handles real and empty data while preserving cardinality and allocation constraints.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- analytics
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100