feat: index loaded resource discovery

This commit is contained in:
Rijad Zuzo
2026-07-17 00:34:30 +02:00
parent 2c88ed6ea8
commit 5356c34fd7
21 changed files with 1392 additions and 69 deletions
@@ -0,0 +1,82 @@
# Loaded Resource Discovery 01
## Question
Can villagers resolve finite resource anchors spread across a larger Terrain3D
world without scanning every loaded `ResourceNode`, while selecting exactly the
same target as the existing distance/risk/comfort/priority score?
## Reviewed workload
- Godot: `4.7-stable (official)`;
- host: Apple M1 Max, 64 GB;
- benchmark seed: `17331`;
- loaded resources: 18, 180, and 1,800;
- 400 deterministic target resolutions per sample;
- seven fresh timing samples per case;
- 20 m horizontal source spacing with varied terrain-like height, safety risk,
comfort distance, discovery priority, and deterministic disabled sources;
- each query performs the real availability check, score, reservation, and
release through `ActionTargetResolver` and `ResourceStateRecord`.
The linear reference reproduces the previous `ResourceNode.get_all()` scan.
The spatial path uses the production `ActiveWorldAdapter` and its 24 m
resource-specific horizontal grid. Fixture construction, rendering, navigation
pathfinding, simulation ticks, and serialization are excluded.
## Results
| Loaded anchors | Linear µs/query | Spatial µs/query | Speedup | Average inspected | Reduction |
| ---: | ---: | ---: | ---: | ---: | ---: |
| 18 | 65.04 | 33.26 | 1.96x | 18 → 3.705 | 79.42% |
| 180 | 589.03 | 38.83 | 15.17x | 180 → 4.375 | 97.57% |
| 1,800 | 6,448.00 | 46.94 | 137.37x | 1,800 → 4.445 | 99.75% |
Every spatial sample produced the same selected-target checksum as its linear
reference. The raw samples are in
[`loaded_resource_discovery_01.json`](loaded_resource_discovery_01.json).
These are local workload measurements, not portable performance promises. The
important shape is that ordinary local queries remain near four inspected
anchors while the loaded set grows by 100x.
## Exactness contract
`ActionTargetResolver` queries successively larger grid radii. It may stop only
when the best possible score of every farther anchor is strictly worse than the
current winner. The lower bound uses the loaded action's authoritative maximum
comfort distance, minimum safety risk, and maximum discovery priority. Stable
registration order preserves the former first-loaded tie behavior.
Unavailable, depleted, and reserved state remains authoritative in
`ResourceStateRecord`; the grid indexes only loaded IDs, interaction positions,
and disposable score bounds. Unloading removes an anchor without deleting its
state. Rebinding the same ID or moving a loaded anchor refreshes the index.
One focused regression also gives the farthest test source an extreme priority.
The query expands beyond its local cells and selects that source exactly as the
linear scan does, proving that the common local fast path is not a hard range
cutoff.
## Decision
Keep the resource-specific grid inside `ActiveWorldAdapter`. It has one real
consumer, preserves current gameplay, and directly supports larger authored
source fields. Do not generalize it into a people/building/event index or a
simulation-LOD framework yet.
Terrain3D foliage instances remain decorative unless an intentional stable-ID
`ResourceNode` anchor binds simulation-owned state. A later placement workflow
can author those anchors alongside visual instancing without making every tree
an authoritative resource.
## Capture command
```bash
/Applications/Godot.app/Contents/MacOS/Godot \
--headless --path "$PWD" \
--script res://tools/benchmark_loaded_resource_discovery.gd -- \
--queries=400 --samples=7 \
--host-label=Apple_M1_Max_64_GB \
--output=res://docs/benchmarks/loaded_resource_discovery_01.json
```
+11
View File
@@ -16,6 +16,14 @@ The default report is written under `user://`. Pass
`-- --host-label="<hardware>" --output=res://docs/benchmarks/<name>.json` only
when intentionally capturing a reviewed project baseline.
Loaded-resource discovery has its own bounded runner:
```bash
/Applications/Godot.app/Contents/MacOS/Godot \
--headless --path "$PWD" \
--script res://tools/benchmark_loaded_resource_discovery.gd
```
Reviewed captures:
- [Simulation scaling baseline 01](SIMULATION_SCALING_BASELINE_01.md) records
@@ -24,3 +32,6 @@ Reviewed captures:
- [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.
- [Loaded Resource Discovery 01](LOADED_RESOURCE_DISCOVERY_01.md) compares the
former all-node scan with the exact resource-anchor grid at 18, 180, and
1,800 loaded sources.
@@ -47,12 +47,11 @@ 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.
That next bounded scale slice is now complete. The measured workload justified
a resource-specific horizontal grid owned by `ActiveWorldAdapter`; exact
expanding queries preserve every linear selected-target checksum and reduce the
1,800-anchor case from 6,448.00 to 46.94 microseconds. See
[Loaded Resource Discovery 01](LOADED_RESOURCE_DISCOVERY_01.md).
Terrain3D-instanced visual trees and grass are not automatically authoritative
resources. Intentional harvest anchors must continue to bind simulation-owned
@@ -0,0 +1,150 @@
{
"benchmark_seed": 17331,
"cases": [
{
"inspected_reduction_percent": 79.4166666666667,
"linear": {
"candidates_inspected_average": 18.0,
"elapsed_usec_median": 26016,
"elapsed_usec_samples": [
25865,
25941,
25967,
26016,
26055,
26153,
26498
],
"queries_per_second_median": 15375.1537515375,
"selected_checksum": "6c417e0e25360aa1d2bcba407d3cba5aafe367a2706b93fb0ca631c7bcdb6800",
"usec_per_query_median": 65.04
},
"query_count": 400,
"resource_count": 18,
"sample_count": 7,
"schema_version": 1,
"spatial": {
"candidates_inspected_average": 3.705,
"elapsed_usec_median": 13304,
"elapsed_usec_samples": [
12896,
13249,
13300,
13304,
13428,
13465,
13580
],
"queries_per_second_median": 30066.1455201443,
"selected_checksum": "6c417e0e25360aa1d2bcba407d3cba5aafe367a2706b93fb0ca631c7bcdb6800",
"usec_per_query_median": 33.26
},
"speedup": 1.95550210463019,
"workload_id": "loaded_resource_target_resolution"
},
{
"inspected_reduction_percent": 97.5694444444444,
"linear": {
"candidates_inspected_average": 180.0,
"elapsed_usec_median": 235611,
"elapsed_usec_samples": [
235062,
235300,
235372,
235611,
235870,
237526,
237936
],
"queries_per_second_median": 1697.71360420354,
"selected_checksum": "2ac9d359d20de54b79d31141abeb59715a94dfb03e21d86aa6fcf6116223a3f6",
"usec_per_query_median": 589.0275
},
"query_count": 400,
"resource_count": 180,
"sample_count": 7,
"schema_version": 1,
"spatial": {
"candidates_inspected_average": 4.375,
"elapsed_usec_median": 15533,
"elapsed_usec_samples": [
14610,
14791,
15121,
15533,
15653,
15814,
15971
],
"queries_per_second_median": 25751.6255713642,
"selected_checksum": "2ac9d359d20de54b79d31141abeb59715a94dfb03e21d86aa6fcf6116223a3f6",
"usec_per_query_median": 38.8325
},
"speedup": 15.1684156312367,
"workload_id": "loaded_resource_target_resolution"
},
{
"inspected_reduction_percent": 99.7530555555556,
"linear": {
"candidates_inspected_average": 1800.0,
"elapsed_usec_median": 2579199,
"elapsed_usec_samples": [
2573545,
2576251,
2577722,
2579199,
2579974,
2580177,
2581969
],
"queries_per_second_median": 155.086908765086,
"selected_checksum": "67c6bdbb5e371c9e9658808fb215aeeba1f31f71fa731f0b9b1aaae599628013",
"usec_per_query_median": 6447.9975
},
"query_count": 400,
"resource_count": 1800,
"sample_count": 7,
"schema_version": 1,
"spatial": {
"candidates_inspected_average": 4.445,
"elapsed_usec_median": 18775,
"elapsed_usec_samples": [
18226,
18389,
18653,
18775,
18903,
19067,
19130
],
"queries_per_second_median": 21304.9267643142,
"selected_checksum": "67c6bdbb5e371c9e9658808fb215aeeba1f31f71fa731f0b9b1aaae599628013",
"usec_per_query_median": 46.9375
},
"speedup": 137.374114513981,
"workload_id": "loaded_resource_target_resolution"
}
],
"exclusions": [
"fixture_construction",
"simulation_tick",
"serialization",
"rendering",
"navigation_pathfinding"
],
"godot_version": "4.7-stable (official)",
"host_label": "Apple_M1_Max_64_GB",
"inclusions": [
"loaded_resource_candidate_discovery",
"authoritative_availability_and_scoring",
"reservation_and_release"
],
"resource_counts": [
18,
180,
1800
],
"resource_spacing": 20.0,
"schema_version": 1,
"workload_id": "loaded_resource_target_resolution"
}