## Summary
Adds a full-game performance harness under `tests/perf/fullgame/` that
runs the **real simulation pipeline** headlessly — `GameRunner` +
`Executor` with the real `Config`, nations from the map manifest, and
bots on a production map from `resources/maps/` — for a configurable
number of ticks, then reports where the time goes.
```bash
npm run perf:game # world, 400 bots, 1800 ticks
npm run perf:game -- --map giantworldmap --ticks 3600
npm run perf:game -- --no-exec-profile # purest CPU profile (no timing wrappers)
```
## What it reports
1. **Per-tick wall time** — mean / p50 / p95 / p99 / max, count of ticks
over the 100ms budget, and the slowest ticks by tick number.
2. **Time per Execution class** — every `Execution`'s `init()`/`tick()`
is timed and aggregated by class name (`AttackExecution`,
`NationExecution`, …).
3. **Top functions by self time** — via the V8 sampling profiler
(`node:inspector`), so no instrumentation skew. Also writes a
`.cpuprofile` to `tests/perf/output/` (gitignored) that opens in Chrome
DevTools as a flame graph.
## Determinism
The run is fully deterministic for a given `--seed`/`--map`/`--bots`
(verified: identical final hashes across runs), and the final game-state
hash is printed — so an optimization can be checked to not change
simulation behavior.
## Sample output (world, 400 bots, 1800 ticks)
```
--- Per-tick wall time (game phase) ---
mean 9.04ms | p50 7.90ms | p95 17.1ms | p99 21.5ms | max 31.7ms
Over 100ms budget: 0 / 1800 ticks
--- Time by Execution class ---
execution total ms % tick ms init ms ticks instances
AttackExecution 6568 48.8 6288 280 212536 4200
PlayerExecution 2832 21.0 2832 0.36 492049 472
NationExecution 2508 18.6 2508 0.23 144654 72
TransportShipExecution 703 5.2 96.0 607 30440 257
...
--- Top functions by self time (V8 sampling profiler) ---
self ms % function location
1065 6.5 forEachNeighborWithDiag src/core/game/GameImpl.ts
979 6.0 conquer src/core/game/GameImpl.ts
948 5.8 (anonymous) src/core/execution/AttackExecution.ts
595 3.6 toFullUpdate src/core/game/PlayerImpl.ts
...
```
The harness lives in a subdirectory so the existing `npm run perf`
micro-benchmark runner (which globs `tests/perf/*.ts`) doesn't pick it
up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>