# nigig-traffic perf baseline (plan Phase 6.3) Machine: MacBook Pro, Apple Silicon (darwin x86_64 binary under Rosetta — `target/debug/nigig-traffic: Mach-O 64-bit executable x86_64`), debug profile. Two consecutive runs, `cargo test -p nigig-traffic --test perf -- --test-threads=1 --nocapture`. Catalog: **47 scenarios** (the old "36+" copy is a floor, still true). ## build_world_for — median µs over 20 reps | Category | Run 1 | Note | |------------|-------|-----------------------------| | Lanes | 2 | | | Roundabout | 11 | 16 ring segs + island | | Parking | 5 | 4 bays | | UTurns | 4 | | | Signs | 3 | | | Priority | 2 | | | Hazards | 3 | | | Speed | 2 | | Single-digit microseconds everywhere. World build is not a cost center — no caching/baking work is justified. ## tick — 600 ticks/scenario, idle input, per-tick average | | Run 1 | Run 2 | |------------|-------|-------| | median | 14 µs | 26 µs | | slowest #1 | haz_rain 501 µs | lane_bus_lane 450 µs | | slowest #2 | pri_yield_car 255 µs | pri_yield_car 386 µs | | slowest #3 | rb_multi_exits 248 µs| ut_single_turn 257 µs| Reading, per HPC ch.5.6: the slowest set **moves between runs** (`haz_rain`/`rb_multi_exits` appear once each) — that is noise (debug build, shared machine), not a scenario-intrinsic cost. `pri_yield_car` ranks top-3 twice; it spawns a moving AI car, so it is the prime suspect for the only genuine above-median cost — unconfirmed until a pinned, quiescent re-run. No optimization is authorized off this table alone. ## Live run (NIGIG_TRAFFIC_STATS=1, same machine, windowed) ``` traffic stats: tick us p50=48 p95=270 p99=5761 (n=300) traffic stats: draw us p50=69 p95=563 p99=4182 (n=300) traffic stats: tick us p50=55 p95=332 p99=1839 (n=300) traffic stats: draw us p50=76 p95=663 p99=4272 (n=300) traffic stats: tick us p50=59 p95=561 p99=3681 (n=300) ``` Live tick p50 (~55µs) runs hotter than the harness median — it shares the machine with the window server and includes rig/camera work around the timed region. p99 spikes are millisecond-scale frame hitches (scheduler / allocator / display-link, not sim code): the next profiling step is an Instruments sample attributing p99, not optimizing p50. ## Blocked: release-vs-size profile decision (Phase 9) `release` currently sets `opt-level = "z"` with no LTO. Whether the game binary wants `opt-level = 3` (throughput over size) must be measured on the Phase-6 bench — but the workspace-wide fork rev bump broke compilation of every crate (upstream `b9a083c` references a never-landed isolate refactor: `enter_isolate`/`leave_isolate`/`IsolateEntry` used in 4 files, defined in 0; plus a 4-vs-5-arg `camera_boom_limit` drift), so no release numbers exist yet. Decision and both measurements go here once the workspace builds again. Do not choose on aesthetics. ## Conclusions authorizing later phases 1. Tick ≈ 15–25µs median (debug). At 60Hz the budget is 16,600µs — the sim consumes ~0.1%. There is no throughput emergency. 2. Costs are latency-shaped (lookups, allocations, branches over dozens of entities), never compute-shaped. Phases 7–8 target exactly that; hand-written SIMD is off the table (documented anti-goal). 3. Next measurement before any Phase 7 code: quiescent release-profile re-run + Instruments sample confirming `step_world` vs `evaluate` vs text-shaping split.