Files
gamedev-the-steward/docs/FEATURE_PRESENTATION_PERFORMANCE.md
2026-09-05 22:54:54 +02:00

257 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 / MSAA | Shadow distance | Volumetric/glow | Grass |
| --- | ---: | ---: | --- | --- |
| High | authored / authored 4× | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
| Balanced (default) | 0.85 cap / 2× cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate |
| Low | 0.70 cap / off | 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 painted cloud panorama, 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).
The current humanoid cast uses the [modular woodland character kit](../assets/characters/README.md):
31 Blender parts, five animal heads, six authored examples and independently
configurable proportions, outfits and accessories. `CharacterAppearanceProfile`
resources are cosmetic. Meshes are shared; `Stout` morph weights and palette
materials are per actor. The reference traveler has 20,070 base triangles across
10 opaque surfaces; Godot imports LODs. The standalone
`tools/CharacterWorkshop.tscn` previews and saves profiles. Existing motion,
profession/inventory/death cues, and shader grass interaction remain owned by
their existing controllers. The appearance scenario verifies imported parts,
independent shape weights, stable identity, extreme proportions and cue clearance.
`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).
## Painterly environment pass — September 2026
The reference study highlighted flat cloud bands, primitive tree silhouettes,
sparse ground cover, opaque green water, and a sun arc offset from daytime
brightness. The current pass addresses those through shared presentation assets:
- The WorldEnvironment uses cool blue ambient fill, warm sunlight, restrained
glow and ordinary aerial fog. The solar arc now peaks at noon and the warm
transitions overlap dawn/dusk. An original painted cloud panorama supplies
layered cumulus shapes; a static sky shader remaps the artwork, blends its
seam/horizon, and tints it from the simulation clock. It adds no TIME-driven
cubemap updates or volumetric cloud pass. Generation provenance and the exact
prompt are in [the asset guide](../assets/painterly/README.md).
- Original Blender broadleaf crowns/trunks and layered conifers replace the
primitive foliage. Seventeen decorative edge trees form a woodland backdrop.
The four village tree placeholders now use `HarvestableTreePresentation`,
retaining their existing IDs, amounts, interaction points and resource count.
Full/low/depleted state and restored crowns still derive from real amounts.
- `PainterlyMeadow` creates at most 1,680 decorative flower clumps in four
MultiMesh batches, with cream daisies, pink cosmos, buttercups and lavender.
Local seeded drifts follow terrain and exclude houses, paths, work sites and
resource approach points. High/Balanced/Low expose at most 1,680/1,092/536
clumps, with shorter distance fading in cheaper profiles. Flowers share the
prevailing breeze; the existing grass and berry contact field remains intact.
Explicit white instance colors preserve the painted GLB palette when custom
instance data is enabled in Compatibility; native review caught and verified
that renderer-specific regression.
Flowers have no collision, harvesting, actor-contact deformation or saved
state. See [mesh sources and budgets](../assets/meadow/README.md).
- The blue/turquoise river follows actual centerline terrain heights, keeps
level cross-sections, and clips dry edges at the shoreline. The pool and its
outlet meet without overlapping coplanar transparent sheets. Broken foam,
subtle flowing strokes and painted reflection patches replace the hard ring
and featureless surface. These reflections are stylized procedural patches
plus ordinary sky specular response, not exact reflected scene geometry,
screen-space reflections, refraction or depth sampling. Water obeys scene
lighting at night and adds no per-frame scripts.
- MSAA follows the existing viewport ownership rules: High preserves authored
4×, Balanced caps at 2×, and Low disables it. Closing an inactive world cannot
overwrite the active world's antialiasing or an external viewport override.
The editable Blender sources live under `art/painterly/` and `art/meadow/`,
protected by `.gdignore`; runtime consumes portable GLBs. The broadleaf is
5,350 triangles / five surfaces and the conifer is 1,580 / two. All flower
clumps together are capped at 362,880 triangles / four surfaces before
quality and distance reduction. No scene collision, navigation bake, terrain
heightmap, simulation rule or save schema was changed.
`tools/capture_painterly_environment.gd -- --label=after` captures the running
main scene with paused simulation, fixed daytime presentation and hidden HUD,
including the normal gameplay camera, valley, lower village view and night.
The valley/near-village `painterly_before_*` and `painterly_after_*`
images are matched cameras; `after_gameplay` fixes the normal rig at its default
yaw/zoom with mouse input disabled, and `after_vista` is an additional review angle.
The tool also records static-camera profile samples in
`painterly_after_metrics.json`; wall-frame latency includes vsync and is not
isolated GPU time or weak-PC proof. Runtime camera defaults are unchanged.
Remaining visual work is primarily authored terrain/shoreline detail and the
blockout-like building/fortress forms. This pass improves atmosphere and
foliage without treating those assets as finished film-quality environments.
Validation for this pass: the full macOS Godot 4.7 gate passed, including all
headless scenarios, the Compatibility quality scenario, and 182 GUT tests /
2,279 assertions. Main-scene headless startup passed the gate's existing exact
diagnostic allowlist. Native Metal daytime/gameplay/valley/night images were
reviewed at 1600×900, with a separate native Compatibility capture confirming
colored petals after the instance-buffer fix. The presentation-quality scenario
also passed with native Compatibility, including white instance-color readback
(the headless dummy renderer cannot provide that readback).
The static M1 Max sample reports approximately 16.7 ms
median wall-frame time in all three profiles; see its JSON for p95, draw calls
and primitives. This includes vsync and does not establish GPU headroom.
Native renderer shutdown still reports the particle shader/material RID
retention previously documented in the storybook/foliage pass; the headless
allowlist was not expanded. Compatibility has different color/tonemapping and
its existing particle/texture shutdown diagnostics. Metal is the color
reference for the saved art-review images.
The native multi-world quality scenario additionally logged a transient macOS
OpenGL cubemap-unloadable diagnostic; the final main-scene captures did not.