feat: index loaded resource discovery
This commit is contained in:
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user