# Simulation scaling baseline 02 This is the reviewed comparison after introducing one reusable per-tick `SimulationPopulationView`. The view indexes all, living, and starving NPCs by stable ID, is refreshed as each NPC advances, and is shared by relationship and action-selection queries. It is transient derived state and is not serialized. The machine-readable samples are in [`simulation_scaling_baseline_02.json`](simulation_scaling_baseline_02.json). The workload, seed, host, exclusions, warmup, and three-sample median contract match [baseline 01](SIMULATION_SCALING_BASELINE_01.md). ## Results | Case | NPCs | Seeded history | Median us/tick | Ticks/s | Simulated realtime | Arrival share | End JSON | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | Population 6 | 6 | 0 | 63.85 | 15,662.93 | 18,795.5x | 4.5% | 0.06 MiB | | Population 60 | 60 | 0 | 662.62 | 1,509.17 | 1,811.0x | 5.7% | 0.56 MiB | | Population 600 | 600 | 0 | 11,654.10 | 85.81 | 103.0x | 18.0% | 5.67 MiB | | History 600 | 60 | 600 | 722.60 | 1,383.90 | 1,660.7x | 5.2% | 0.71 MiB | | History 6,000 | 60 | 6,000 | 1,430.65 | 698.98 | 838.8x | 2.6% | 2.04 MiB | All samples within each case were deterministic. More importantly, every final checksum exactly matches the corresponding baseline-01 checksum, so the optimization preserves selection, interleaved NPC updates, event growth, and serialized continuation state. ## Comparison | Case | Baseline 01 us/tick | Baseline 02 us/tick | Change | | --- | ---: | ---: | ---: | | Population 6 | 55.74 | 63.85 | 14.6% slower | | Population 60 | 621.87 | 662.62 | 6.6% slower | | Population 600 | 15,187.74 | 11,654.10 | **23.3% faster** | | History 600 | 677.12 | 722.60 | 6.7% slower | | History 6,000 | 1,421.19 | 1,430.65 | 0.7% slower | The shared dictionaries add a small fixed per-tick cost, visible in tiny fixtures and normal local timing noise. At the measured population knee they remove repeated all-NPC lookup construction and improve the 600-NPC case from 65.84 to 85.81 ticks per second. The local 50-ticks-per-second reference target remains comfortably met without introducing simulation LOD. ## Decision and next measurement Keep the population view. It has a real relationship/action consumer, improves the measured pressure point, and preserves exact state. Do not add batching or active/abstract NPC modes merely to improve this data-only number. The next bounded scale slice should support the world vision directly: a loaded-resource spatial query owned by `ActiveWorldAdapter`. It should let villagers discover finite, stable-ID `ResourceNode` anchors placed much farther apart across Terrain3D while preserving the current score, reachability, reservation, and save/rebind contracts. A benchmark should compare 18, 180, and 1,800 loaded candidates before choosing a grid, tree, or other index. Terrain3D-instanced visual trees and grass are not automatically authoritative resources. Intentional harvest anchors must continue to bind simulation-owned `ResourceStateRecord` data by stable ID; large decorative populations can stay presentation-only until a gameplay rule needs them. ## Capture command ```bash /Applications/Godot.app/Contents/MacOS/Godot \ --headless --path "$PWD" \ --script res://tools/benchmark_simulation_scaling.gd -- \ --host-label=Apple_M1_Max_64_GB \ --output=res://docs/benchmarks/simulation_scaling_baseline_02.json ```