feat: scale village queries and enrich Jajce center
This commit is contained in:
@@ -21,3 +21,6 @@ Reviewed captures:
|
||||
- [Simulation scaling baseline 01](SIMULATION_SCALING_BASELINE_01.md) records
|
||||
the first full-fidelity population/history measurements and the bounded next
|
||||
optimization.
|
||||
- [Simulation scaling baseline 02](SIMULATION_SCALING_BASELINE_02.md) records
|
||||
checksum-identical results after the shared per-tick population view: the
|
||||
600-NPC case is 23.3% faster, while small fixtures expose its fixed cost.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,308 @@
|
||||
{
|
||||
"benchmark_seed": 8088,
|
||||
"captured_utc": "2026-07-16T21:38:42Z",
|
||||
"cases": [
|
||||
{
|
||||
"arrival_share_percent": 4.47176756206437,
|
||||
"arrival_usec_median": 571,
|
||||
"arrival_usec_samples": [
|
||||
563,
|
||||
571,
|
||||
579
|
||||
],
|
||||
"arrivals_processed": 209,
|
||||
"case_id": "population_006",
|
||||
"elapsed_usec_max": 12771,
|
||||
"elapsed_usec_median": 12769,
|
||||
"elapsed_usec_min": 12507,
|
||||
"elapsed_usec_samples": [
|
||||
12507,
|
||||
12769,
|
||||
12771
|
||||
],
|
||||
"end_event_count": 221,
|
||||
"end_known_reference_count": 0,
|
||||
"end_state_bytes": 58576,
|
||||
"end_tick": 210,
|
||||
"events_recorded": 209,
|
||||
"final_checksum": "100dd5dc1e0c22ea23f35dd876d6f56fd5c33c58b1586b6fa9aec47ee1536511",
|
||||
"history_seed_events": 0,
|
||||
"measured_ticks": 200,
|
||||
"npc_updates": 1200,
|
||||
"population": 6,
|
||||
"realtime_factor_median": 18795.5204009711,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"seed": 8088,
|
||||
"setup_usec_median": 627,
|
||||
"setup_usec_samples": [
|
||||
546,
|
||||
627,
|
||||
6728
|
||||
],
|
||||
"simulation_usec_median": 12173,
|
||||
"simulation_usec_samples": [
|
||||
11916,
|
||||
12173,
|
||||
12186
|
||||
],
|
||||
"start_event_count": 12,
|
||||
"start_known_reference_count": 0,
|
||||
"start_state_bytes": 7632,
|
||||
"start_tick": 10,
|
||||
"state_growth_bytes": 50944,
|
||||
"tick_interval": 1.2,
|
||||
"ticks_per_second_median": 15662.9336674759,
|
||||
"usec_per_tick_median": 63.845,
|
||||
"warmup_arrivals": 12,
|
||||
"warmup_ticks": 10,
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
},
|
||||
{
|
||||
"arrival_share_percent": 5.66845000490481,
|
||||
"arrival_usec_median": 7512,
|
||||
"arrival_usec_samples": [
|
||||
7458,
|
||||
7512,
|
||||
7571
|
||||
],
|
||||
"arrivals_processed": 2090,
|
||||
"case_id": "population_060",
|
||||
"elapsed_usec_max": 133541,
|
||||
"elapsed_usec_median": 132523,
|
||||
"elapsed_usec_min": 130182,
|
||||
"elapsed_usec_samples": [
|
||||
130182,
|
||||
132523,
|
||||
133541
|
||||
],
|
||||
"end_event_count": 2210,
|
||||
"end_known_reference_count": 0,
|
||||
"end_state_bytes": 587324,
|
||||
"end_tick": 210,
|
||||
"events_recorded": 2090,
|
||||
"final_checksum": "58c7f96fc5df7f5399fd82d1a9c212b3c53067e807059e88ffa484ba0eb41e16",
|
||||
"history_seed_events": 0,
|
||||
"measured_ticks": 200,
|
||||
"npc_updates": 12000,
|
||||
"population": 60,
|
||||
"realtime_factor_median": 1811.00639134339,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"seed": 8088,
|
||||
"setup_usec_median": 3788,
|
||||
"setup_usec_samples": [
|
||||
3481,
|
||||
3788,
|
||||
3917
|
||||
],
|
||||
"simulation_usec_median": 125039,
|
||||
"simulation_usec_samples": [
|
||||
122647,
|
||||
125039,
|
||||
125926
|
||||
],
|
||||
"start_event_count": 120,
|
||||
"start_known_reference_count": 0,
|
||||
"start_state_bytes": 71278,
|
||||
"start_tick": 10,
|
||||
"state_growth_bytes": 516046,
|
||||
"tick_interval": 1.2,
|
||||
"ticks_per_second_median": 1509.17199278616,
|
||||
"usec_per_tick_median": 662.615,
|
||||
"warmup_arrivals": 120,
|
||||
"warmup_ticks": 10,
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
},
|
||||
{
|
||||
"arrival_share_percent": 17.9571567087978,
|
||||
"arrival_usec_median": 418549,
|
||||
"arrival_usec_samples": [
|
||||
417929,
|
||||
418549,
|
||||
425849
|
||||
],
|
||||
"arrivals_processed": 20950,
|
||||
"case_id": "population_600",
|
||||
"elapsed_usec_max": 2346707,
|
||||
"elapsed_usec_median": 2330820,
|
||||
"elapsed_usec_min": 2326981,
|
||||
"elapsed_usec_samples": [
|
||||
2326981,
|
||||
2330820,
|
||||
2346707
|
||||
],
|
||||
"end_event_count": 22150,
|
||||
"end_known_reference_count": 0,
|
||||
"end_state_bytes": 5948447,
|
||||
"end_tick": 210,
|
||||
"events_recorded": 20950,
|
||||
"final_checksum": "fa4efac63fde63f92eb28141bebd1922f665e85ffe7b7ac2b9702dae6e4c24b7",
|
||||
"history_seed_events": 0,
|
||||
"measured_ticks": 200,
|
||||
"npc_updates": 120000,
|
||||
"population": 600,
|
||||
"realtime_factor_median": 102.968054161196,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"seed": 8088,
|
||||
"setup_usec_median": 34966,
|
||||
"setup_usec_samples": [
|
||||
34505,
|
||||
34966,
|
||||
34980
|
||||
],
|
||||
"simulation_usec_median": 1912210,
|
||||
"simulation_usec_samples": [
|
||||
1908995,
|
||||
1912210,
|
||||
1920790
|
||||
],
|
||||
"start_event_count": 1200,
|
||||
"start_known_reference_count": 0,
|
||||
"start_state_bytes": 713361,
|
||||
"start_tick": 10,
|
||||
"state_growth_bytes": 5235086,
|
||||
"tick_interval": 1.2,
|
||||
"ticks_per_second_median": 85.8067118009971,
|
||||
"usec_per_tick_median": 11654.1,
|
||||
"warmup_arrivals": 1200,
|
||||
"warmup_ticks": 10,
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
},
|
||||
{
|
||||
"arrival_share_percent": 5.24844484116275,
|
||||
"arrival_usec_median": 7585,
|
||||
"arrival_usec_samples": [
|
||||
7540,
|
||||
7585,
|
||||
7604
|
||||
],
|
||||
"arrivals_processed": 2090,
|
||||
"case_id": "history_000600",
|
||||
"elapsed_usec_max": 145539,
|
||||
"elapsed_usec_median": 144519,
|
||||
"elapsed_usec_min": 144335,
|
||||
"elapsed_usec_samples": [
|
||||
144335,
|
||||
144519,
|
||||
145539
|
||||
],
|
||||
"end_event_count": 2810,
|
||||
"end_known_reference_count": 0,
|
||||
"end_state_bytes": 743004,
|
||||
"end_tick": 810,
|
||||
"events_recorded": 2090,
|
||||
"final_checksum": "7ae86044f7683359d6bfbc8753e2656f3af90f23d040039154c6869dca48cf65",
|
||||
"history_seed_events": 600,
|
||||
"measured_ticks": 200,
|
||||
"npc_updates": 12000,
|
||||
"population": 60,
|
||||
"realtime_factor_median": 1660.68129450107,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"seed": 8088,
|
||||
"setup_usec_median": 22723,
|
||||
"setup_usec_samples": [
|
||||
21887,
|
||||
22723,
|
||||
23413
|
||||
],
|
||||
"simulation_usec_median": 136897,
|
||||
"simulation_usec_samples": [
|
||||
136707,
|
||||
136897,
|
||||
137966
|
||||
],
|
||||
"start_event_count": 720,
|
||||
"start_known_reference_count": 180,
|
||||
"start_state_bytes": 253098,
|
||||
"start_tick": 610,
|
||||
"state_growth_bytes": 489906,
|
||||
"tick_interval": 1.2,
|
||||
"ticks_per_second_median": 1383.90107875089,
|
||||
"usec_per_tick_median": 722.595,
|
||||
"warmup_arrivals": 120,
|
||||
"warmup_ticks": 10,
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
},
|
||||
{
|
||||
"arrival_share_percent": 2.64320413797924,
|
||||
"arrival_usec_median": 7563,
|
||||
"arrival_usec_samples": [
|
||||
7552,
|
||||
7563,
|
||||
7628
|
||||
],
|
||||
"arrivals_processed": 2090,
|
||||
"case_id": "history_006000",
|
||||
"elapsed_usec_max": 291609,
|
||||
"elapsed_usec_median": 286130,
|
||||
"elapsed_usec_min": 285483,
|
||||
"elapsed_usec_samples": [
|
||||
285483,
|
||||
286130,
|
||||
291609
|
||||
],
|
||||
"end_event_count": 8210,
|
||||
"end_known_reference_count": 0,
|
||||
"end_state_bytes": 2141615,
|
||||
"end_tick": 6210,
|
||||
"events_recorded": 2090,
|
||||
"final_checksum": "b4df2bac21ed5ce6dcd92736e210db5f3114d959dcb15ae7796493362911b6d7",
|
||||
"history_seed_events": 6000,
|
||||
"measured_ticks": 200,
|
||||
"npc_updates": 12000,
|
||||
"population": 60,
|
||||
"realtime_factor_median": 838.779575717331,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"seed": 8088,
|
||||
"setup_usec_median": 153306,
|
||||
"setup_usec_samples": [
|
||||
153290,
|
||||
153306,
|
||||
154064
|
||||
],
|
||||
"simulation_usec_median": 278467,
|
||||
"simulation_usec_samples": [
|
||||
277892,
|
||||
278467,
|
||||
284022
|
||||
],
|
||||
"start_event_count": 6120,
|
||||
"start_known_reference_count": 180,
|
||||
"start_state_bytes": 1649700,
|
||||
"start_tick": 6010,
|
||||
"state_growth_bytes": 491915,
|
||||
"tick_interval": 1.2,
|
||||
"ticks_per_second_median": 698.982979764443,
|
||||
"usec_per_tick_median": 1430.65,
|
||||
"warmup_arrivals": 120,
|
||||
"warmup_ticks": 10,
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
}
|
||||
],
|
||||
"engine_version": "4.7-stable (official)",
|
||||
"exclusions": [
|
||||
"manager_and_fixture_setup",
|
||||
"state_serialization",
|
||||
"world_scene",
|
||||
"rendering",
|
||||
"navigation",
|
||||
"npc_visual"
|
||||
],
|
||||
"host_label": "Apple_M1_Max_64_GB",
|
||||
"measured_ticks": 200,
|
||||
"platform": "macOS",
|
||||
"processor_count": 10,
|
||||
"sample_count": 3,
|
||||
"schema_version": 1,
|
||||
"timed_phases": [
|
||||
"simulation_tick",
|
||||
"headless_arrival_completion"
|
||||
],
|
||||
"warmup_ticks": 10,
|
||||
"workload": "All NPCs receive full per-tick needs/task updates and ordinary action decisions; travel resolves through the deterministic immediate-arrival headless convention.",
|
||||
"workload_id": "full_fidelity_headless_arrival"
|
||||
}
|
||||
Reference in New Issue
Block a user