feat: derive resource visuals from state

This commit is contained in:
Rijad Zuzo
2026-07-17 18:39:14 +02:00
parent 04c5371b6b
commit 2719b0398b
21 changed files with 686 additions and 18 deletions
+10 -7
View File
@@ -787,16 +787,19 @@ Completed:
separate terrain-snapped approach points. Matched captures and a focused
scenario prove three-batch visual cost, nested discovery, and a forced
query beyond the first 24 m range without changing the 18-resource state.
38. `Jajce Resource States 05`: a shared amount-derived presentation contract
now gives that berry and tree honest full, low, and depleted states. Real
player berry harvests thin fruit and leaves; real NPC wood gathers thin the
crown and leave a stump. Low and depleted saves restore the same visuals
using only `amount_remaining`, with matched Metal comparison frames.
Next:
1. Give the authored berry/tree resources state-driven low/depleted visuals
sourced from their real `ResourceStateRecord` amount. Prove NPC and player
extraction plus save/restore rebuild the same presentation without adding
visual fields to simulation state.
2. Apply the workflow to a second forest-edge consumer only after that contract
is honest and reusable. Add livestock only with a real identity and
interaction contract.
1. Move the existing `tree_forest_grove_01` and `tree_forest_grove_02` anchors
into one bounded forest-edge authoring cluster. Reuse the proven tree amount
presentation, preserve both stable IDs, and prove reachable expanding
discovery plus depletion/restore without serializing decorative foliage.
2. Add livestock only with a real identity and interaction contract.
Do not start with GIS data, a full city, a large asset pack, or more NPC
mechanics. The next proof is a beautiful stage for the systems that already
+20 -8
View File
@@ -904,17 +904,29 @@ of eighteen resources, sit above the carved channel, and expose separate
terrain-snapped approach points. A forced village query expands beyond the
initial 24 m range and selects the nested berry exactly by ID.
The immediate next slice should make those two resource-specific visuals
reflect authoritative amount/depletion: thin the berry patch and leave a stump
or equivalent readable tree state after real NPC/player extraction, then prove
save/restore rebuilds the same presentation without persisting visual-only
state. Only after both the berry and tree consume that contract should shared
cluster/depletion tooling be extracted or the workflow copied to a forest
edge. Do not generalize the resource grid to people/buildings/events or
introduce active/abstract LOD before those consumers need it.
The first amount-derived resource presentation slice is complete. A shared
`ResourceAmountVisual` contract reads the loaded `ResourceNode`'s authoritative
amount and derives full, low, or depleted presentation without entering saved
state. Real player berry harvests remove fruit and leaf mass; real NPC wood
gathers thin the crown and ultimately leave a warm-cut stump. Restoring both
low and depleted snapshots rebinds the same nodes and reconstructs the same
visuals from `ResourceStateRecord.amount_remaining` alone.
The immediate next slice should apply that proven contract to the existing
`tree_forest_grove_01` and `tree_forest_grove_02` anchors as one bounded
forest-edge authoring consumer. Preserve both stable IDs, keep decorative
canopy/understory outside simulation state, and prove reachable expanding
discovery plus depletion/restore with the shared tree presentation. Do not
generalize the resource grid to people/buildings/events or introduce
active/abstract LOD before those consumers need it.
Recently completed:
- `Jajce Resource States 05`: the authored river berry/tree now derive full,
low, and depleted visuals from authoritative amounts. Player and NPC
extraction drive the changes, save/restore reconstructs low and depleted
states without visual save fields, and matched Metal frames record the berry
thinning, sparse crown, fruitless remnant, and stump.
- `Jajce Resource Grove 04`: two existing river resources now live as nested
stable-ID anchors inside a restrained decorative bank cluster. Twenty-eight
understory instances use three MultiMesh batches, bespoke berry/tree visuals
@@ -0,0 +1,80 @@
# Jajce Resource States 05
Captured: 2026-07-17
## Files
- [full resources](jajce_resource_states_05_full.png)
- [low resources](jajce_resource_states_05_low.png)
- [depleted resources](jajce_resource_states_05_depleted.png)
- `world/resource_nodes/ResourceAmountVisual.gd`
- `world/jajce/HarvestableTreePresentation.tscn`
- `tests/jajce_resource_presentation_state_test.gd`
The three images use the same 1280×720 cross-river camera, daylight value,
warmup, and Metal renderer. Debug labels are hidden. The lower-left berry patch
and right-hand harvestable tree are shown at their authored full amounts, at
40%/50% respectively, and at zero.
## Amount-derived visual language
`ResourceAmountVisual` finds its owning `ResourceNode` and derives one of three
tiers from `amount_remaining / initial_amount`:
- full keeps all five berry leaf clumps, eight berries, and four tree canopies;
- low keeps three leaf clumps, four berries, and two tree canopies; and
- depleted keeps a two-leaf fruitless remnant and replaces the tree with a
low-poly stump and warm cut face.
The threshold is deliberately simple and readable: a positive source at or
below half amount is low. The berry keeps using two bounded MultiMeshes, so
thinning changes only `visible_instance_count`; it does not create or destroy
nodes during play. The tree wrapper reuses the existing wind-reactive standing
tree and toggles authored crown/stump parts.
## Authority and restore proof
`ResourceStateRecord` remains the only amount authority. `ResourceNode` now
emits its existing `amount_changed` presentation signal when authoritative
state is rebound, including restore. The visual contract listens to that signal
and also refreshes once after scene readiness, covering either bind order.
The focused scenario performs two real player harvests on
`berry_bush_river_02` and two real NPC gather completions on
`tree_river_resource_01`, checks the low presentation and NPC wood inventory,
then fully depletes both through those same production paths. It restores low
and depleted snapshots in turn and verifies the presentation is reconstructed.
The resource records contain no visual, visibility, tier, or stump fields.
The capture tool's optional amount overrides also construct and bind ordinary
`ResourceStateRecord`s. They do not directly manipulate visual children, so
the review images exercise the same runtime contract as gameplay.
## Next slice
Apply this contract to the existing `tree_forest_grove_01` and
`tree_forest_grove_02` stable IDs in one bounded forest-edge cluster. Decorative
canopy and understory should stay presentation-only while the two finite trees
remain reachable, discoverable, depletable, and restore correctly.
## Capture commands
```bash
HOME="$PWD/logs/quality/godot_profile" \
APPDATA="$PWD/logs/quality/godot_profile" \
LOCALAPPDATA="$PWD/logs/quality/godot_profile" \
/Applications/Godot.app/Contents/MacOS/Godot \
--path "$PWD" --script res://tools/capture_jajce_lookdev.gd -- \
--scene=res://world/jajce/JajceResourceStateLookdev.tscn \
--output=res://docs/baselines/jajce_resource_states_05_full.png \
--hide-resource-labels
```
For the low frame, append:
```text
--resource-amount=berry_bush_river_02:2
--resource-amount=tree_river_resource_01:4
```
For the depleted frame, use the same arguments with both amounts set to `0`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cnusf1uofgeam"
path="res://.godot/imported/jajce_resource_states_05_depleted.png-4da1f8d2357fb72267bc5e3167868d55.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/baselines/jajce_resource_states_05_depleted.png"
dest_files=["res://.godot/imported/jajce_resource_states_05_depleted.png-4da1f8d2357fb72267bc5e3167868d55.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6fr86t0glm1p"
path="res://.godot/imported/jajce_resource_states_05_full.png-b7555dd39fe62146f66c1e731a0813d3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/baselines/jajce_resource_states_05_full.png"
dest_files=["res://.godot/imported/jajce_resource_states_05_full.png-b7555dd39fe62146f66c1e731a0813d3.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://lerebtc2de5v"
path="res://.godot/imported/jajce_resource_states_05_low.png-ca9e13189ad1a8dc22e32380ddf38b2f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/baselines/jajce_resource_states_05_low.png"
dest_files=["res://.godot/imported/jajce_resource_states_05_low.png-ca9e13189ad1a8dc22e32380ddf38b2f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1