Engineering/Architecture
Performance hot paths and efficiency notes
Last updated 2026-08-21
Source files
This document captures known high-impact execution paths, recent optimizations, and where deeper profiling belongs for A House Divided. It complements Repo Operating Map and AGENTS.md in AHDGame.
Turn processing ()#
- Orchestrator:
processTurn()loads allcharactersonce per turn (find({})), then executes the adapters registered inturnPhaseRegistry.tsthrough the shared runtime. The registry changes frequently, so use it rather than a frozen phase count. The full character scan is load-bearing for action refresh and fund generation; reducing it would require semantic changes and broader tests. - Game state:
getGameState()accepts an optionalDbinstance so callers that already have a connection avoid a redundantgetDb()hop (same pool, fewer awaits). SeegetGameState(db)at . - Group 7 ordering: Candidate party sweep, primaries, vote accumulation, campaign-spend reset, timers, primary snapshots, general resolution, Support cleanup, leadership vacate, and government formation must stay strictly sequential. Do not parallelize for speed without new correctness tests.
Election resolution ()#
- Completed elections:
resolveGeneralElectionsruns after elections reachcompletedstatus. It previously loaded the entirepoliticalPartiescollection to build an unused map; that query was removed, it did not affect outcomes, only I/O. - News:
generateElectionNewsin batches outcomes into one post per turn when multiple races resolve, good pattern; avoid per-race inserts in hot paths.
Vote accumulation ()#
accumulateGeneralElectionVotes: For non-presidential elections, each turn loads demographic preload and an approval map.getAllStateApprovalsForElection({ countryIds })now restrictsstates/stateMetricsqueries to countries that actually have active state-level general elections, while still computing national averages within each country (same math as a full-world load for those countries).- Hypothesis: Multi-country live games spend less CPU and less MongoDB scanned data on
stateMetricswhen only one country has concurrent races.
NPP context ()#
loadNPPContext: Performs batched parallel reads (NPPs, elections, bills, whips, parties, etc.). A second query loads active election IDs for candidacy tracking; merging it with the primary election query would trade one round-trip for different memory/network shapes depending on how many elections are in general phase, left for profiling before changing.
UI and API#
- News page: is a server component, it fetches via
getDb()/getAuthUser()server-side and delegates client interactivity toNewsPageClient. Auth-heavy layouts elsewhere may still duplicate/api/auth/mefetches client-side, consolidate only where product allows, to avoid double requests on first paint. - Large route surfaces: ~1256 API routes under
src/app/api/, performance work is most valuable on high-QPS or turn-adjacent routes, not one-off admin tools.
Benchmarking and profiling (recommended next steps)#
| Area | Suggestion |
|---|---|
| Turn total duration | Log or trace phase timings in processTurn (already has durationMs on TurnLog), compare before/after in staging with production-like data volume |
| MongoDB | Atlas or explain() on stateMetrics / states with and without countryIds filter during peak election seasons |
| React | Next.js devtools / React Profiler on dashboard and news feeds with many posts |
Hot paths to leave unchanged until tests improve#
- Vote distribution math, ,
voteCalculations.ts: small numeric changes alter election outcomes across the whole game. - Primary elimination and tally initialization, (
resolvePrimariesIfNeeded): ordering and elimination rules are load-bearing. - Seat allocation and president resolution, , .
- Party org cleanup cascade, : cross-collection deletes; any batching change needs integration tests.
Implemented optimizations (audit follow-up)#
| Change | Rationale |
|---|---|
Optional Db for getGameState |
Removes redundant getDb() in processTurn, initializeGameState, startTurnSystem |
Drop unused politicalParties read in resolveGeneralElections |
Full collection scan with no consumers |
getAllStateApprovalsForElection({ countryIds }) from vote accumulation |
Smaller stateMetrics / states reads when not all countries have active state races |
Connected pages
← Referenced by
None