164 lines
9.2 KiB
Markdown
164 lines
9.2 KiB
Markdown
# Feature slice: presentation, relevance, and weak-PC performance
|
||
|
||
Presentation is deliberately replaceable and bounded. The simulation can run
|
||
headless; loaded scenes provide geometry, navigation, animation, cues, and
|
||
input. This guide records the current performance controls and the evidence we
|
||
do and do not have.
|
||
|
||
## Loaded-world boundary
|
||
|
||
`ActiveWorldAdapter` owns a context identity `(world_id, location_id,
|
||
context_id)`, a `WorldTargetRegistry`, loaded resource spatial index, and
|
||
provider handles for resource/storage/activity/animal nodes. It is the only
|
||
place where a simulation target is paired with loaded geometry. Multiple
|
||
contexts must use separate ancestor containers; duplicate stable IDs are valid
|
||
only when they belong to different explicit contexts.
|
||
|
||
`WorldViewManager` owns active NPC visual instances and forwards target/arrival/
|
||
navigation callbacks. `NpcVisual`, `CreatureVisual`, and `AnimalNode` follow
|
||
authoritative positions. They do not own elapsed travel or saved quantities.
|
||
|
||
## Relevance and navigation budgets
|
||
|
||
`PresentationRelevancePolicy.select_detailed_visuals()` rejects foreign world,
|
||
context, or location candidates before ranking and returns at most 40 detailed
|
||
visuals. Explicit pins include named/important people, player-affecting
|
||
threats, situation/commitment participants, leaders, and unique owners. If
|
||
mandatory pins exceed the cap, the policy fails closed instead of silently
|
||
dropping identities. Decisions include score, distance, pin reasons, and
|
||
rejection traces.
|
||
|
||
`PresentationNavRequestBudget` admits at most two new navigation requests per
|
||
frame. It exposes pending/in-flight IDs and deterministic priority order. The
|
||
budget is a pure policy contract today; wiring it into every production visual
|
||
spawn is a next integration.
|
||
|
||
## Jajce quality profiles
|
||
|
||
`JajceWorld` owns reversible High, Balanced, and Low presentation profiles with
|
||
instance-local mutable environment/grass resources and viewport-scale ownership.
|
||
|
||
| Profile | 3D scale | Shadow distance | Volumetric/glow | Grass |
|
||
| --- | ---: | ---: | --- | --- |
|
||
| High | authored | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
|
||
| Balanced (default) | 0.85 cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate |
|
||
| Low | 0.70 cap | 80 m, orthogonal | volumetric/glow/adjustment disabled; ordinary fog retained | hidden/stopped, emitters off, shader interactors zero |
|
||
|
||
UI remains at native resolution. High/Low switches restore captured authored
|
||
values; viewport scale restoration is guarded by the current quality owner so a
|
||
second world cannot clobber the active owner. Compatibility/mobile feature
|
||
selection never forces unsupported volumetric fog on.
|
||
|
||
## Grass trails and bush contact
|
||
|
||
`GrassInteractionController` samples the existing `grass_interactors` group:
|
||
player, NPCs, hostile creatures and animals. The player keeps one slot; other
|
||
visible actors are ranked by camera distance within the loaded actor container.
|
||
High updates at 0.08 seconds with eight actors; Balanced uses 0.12 seconds and
|
||
four. Low stops updates and immediately releases both grass and bushes.
|
||
|
||
`FoliageTrailMap` maintains a disposable 128×128 RGBA float texture covering a
|
||
64 m square around the camera. Swept footprints part grass to either side and
|
||
lower its tips; the field recovers over eight seconds after contact. Sparse
|
||
history is clipped to the moving window. The 256 KiB texture is reused with
|
||
[`ImageTexture.update`](https://docs.godotengine.org/en/stable/classes/class_imagetexture.html#class-imagetexture-method-update),
|
||
at most 3.125 MiB/s of texture data at the High update rate. The vertex shader
|
||
adds one field lookup per grass vertex; there are no blade collision bodies,
|
||
per-blade scripts, render targets or added grass draw calls.
|
||
|
||
`StylizedBerryPatch` shares the field and contact positions between its leaf
|
||
and berry MultiMeshes. Both bend in world space around the planted bush root,
|
||
including under rotated/scaled parents, and spring back faster than grass.
|
||
The five village berry placeholders now use this same amount-aware presentation
|
||
as the existing riverbank bush. Contact does not change berries, resource
|
||
amounts, navigation, targets or collision geometry.
|
||
|
||
The controller clears history after `SimulationManager.state_restored`, on
|
||
quality changes and on world disposal. Missing/recreated actors, teleports over
|
||
3 m between samples and update gaps over 0.5 seconds start a new footprint rather
|
||
than a connecting trail. Actor foot height gates deformation so bridge traffic
|
||
does not flatten grass below. Trails are visual only and never enter saves.
|
||
The exported actor, primary actor, foliage and simulation paths can be rebound
|
||
when placing the controller in another loaded scene container.
|
||
|
||
`tests/foliage_interaction_test.gd` checks sweep direction, recovery, teleport
|
||
rejection, camera movement, world isolation, restore/quality reset, the primary
|
||
actor slot and shared leaf/fruit bindings. Run `tools/capture_foliage.gd` with
|
||
the native renderer for before/contact/trail/recovery images and controller
|
||
timing in `docs/baselines/foliage_*`. That capture freezes simulation and wind, and moves
|
||
the player presentation through the actual meadow, checking the state checksum
|
||
is unchanged. CPU update timing is not a GPU or weak-PC frame-rate guarantee.
|
||
Native Metal and Compatibility both render the effect; existing particle-shader
|
||
shutdown warnings (and four Compatibility texture leaks) also reproduce in the
|
||
untouched pre-art baseline. The headless quality gate retains its exact allowlist.
|
||
|
||
## Runtime hot paths already bounded
|
||
|
||
The woodland storybook art pass adds portable Blender animal meshes and an
|
||
opaque 35-triangle, seven-blade grass tuft. The existing camera-local grass
|
||
field and quality controls own its instance budget; four loaded path strips
|
||
provide disposable grass-clearance data after material isolation. Animal
|
||
appearance is derived from stable NPC IDs, shares mesh resources, and retains
|
||
profession tint, velocity-driven motion, inventory cues and death through the
|
||
existing presentation controllers. No saved state or navigation changes.
|
||
|
||
Characters and buildings use soft cel bands; tree crowns retain the shared
|
||
breeze with painted color patches. A static procedural cloud sky, cooler
|
||
ambient fill and warm sunlight follow the simulation day/night clock. The
|
||
cloud shader adds no volumetric pass, outline pass or screen-space postprocess.
|
||
See [the asset contract and rebuild commands](../assets/storybook/README.md).
|
||
|
||
`docs/baselines/storybook_*.png` records the native Metal review. The accompanying
|
||
`storybook_render_metrics.json` records a bounded local 1600×900 static-camera
|
||
sample on Apple M1 Max with six NPCs and High/Balanced/Low settings. Wall-frame
|
||
latency includes vsync and does not close the weak-PC evidence gap below.
|
||
|
||
- Player interaction and villager inspection probes are throttled/event-driven;
|
||
they no longer rebuild all candidate data every rendered frame.
|
||
- `LoadedResourceSpatialIndex` bounds finite-resource discovery.
|
||
- `SimulationPopulationView` gives stable-ID all/living/starving indexes once
|
||
per tick.
|
||
- `WorldTargetRegistry` gives O(1) target lookup and deterministic iteration.
|
||
- Creature path failures retain the failed target and use bounded backoff.
|
||
- Hostile hit flashes are health-change driven, not position-change driven.
|
||
- Day/night, status, time dial, storage/debug labels, and grass updates run at
|
||
human-readable rates or only on state changes.
|
||
- Regional scheduled work uses a due-job heap rather than a permanent scan.
|
||
|
||
## What the benchmarks prove
|
||
|
||
- Simulation baseline 03: 600 full-fidelity data-only NPC/combatant records at
|
||
about 90.6 ticks/s on the reviewed Apple M1 Max capture, with matching
|
||
checksums across samples.
|
||
- Loaded Resource Discovery 01: indexed 1,800-anchor queries preserve exact
|
||
selection checksums while reducing resolution work dramatically.
|
||
- Regional scale baseline 01: five settlements, 20 caravans, 2,000 named
|
||
people, 50,000 aggregate residents, 2,025 jobs, budget/checksum parity.
|
||
|
||
These are headless/structural measurements. They do **not** prove 600 rendered
|
||
visuals, a 30-day regional economy, or weak-PC GPU frame time.
|
||
|
||
## Remaining evidence gap
|
||
|
||
The project still needs a recorded reference weak-PC active-world workload at a
|
||
fixed gameplay camera, including High and Low p50/p95/p99 CPU/GPU frame time,
|
||
draw calls/primitives, memory/VRAM, active detailed visuals, and navigation
|
||
queue depth. The CI gate should keep structural profile assertions and the
|
||
two-request policy; hardware measurements belong in a reviewed benchmark
|
||
ledger, not brittle cross-machine CI thresholds.
|
||
|
||
## Performance extension rules
|
||
|
||
1. Measure a representative workload before optimizing.
|
||
2. Keep simulation and presentation results checksum-identical across loaded,
|
||
unloaded, and load/unload runs.
|
||
3. Add indexes, dirty sets, analytical updates, or scheduled jobs before adding
|
||
more per-frame scans.
|
||
4. Keep allocations out of render-frame probes and avoid sorting when a stable
|
||
bounded comparison is sufficient.
|
||
5. Any new visual must declare relevance/pin behavior and obey the navigation
|
||
request budget.
|
||
|
||
See [the benchmark ledger](benchmarks/README.md), [regional contract](REGIONAL_SIMULATION.md),
|
||
and [the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md).
|