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
@@ -0,0 +1,211 @@
extends SceneTree
const BERRY_ID := &"berry_bush_river_02"
const TREE_ID := &"tree_river_resource_01"
const CLUSTER_PATH := "JajceWorld/WorldObjects/ResourceClusters/RiverbankResourceCluster"
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var main_scene: Node = load("res://main.tscn").instantiate()
root.add_child(main_scene)
await process_frame
for _frame in 3:
await physics_frame
var manager = main_scene.get_node("SimulationManager")
manager.set_process(false)
var cluster := main_scene.get_node(CLUSTER_PATH) as RiverbankResourceCluster
var berry := cluster.get_node("ResourceAnchors/BerryBush_River_02") as ResourceNode
var tree := cluster.get_node("ResourceAnchors/Tree_River_Resource_01") as ResourceNode
var berry_visual := berry.get_node("Visual/BerryPatchPresentation") as StylizedBerryPatch
var tree_visual := tree.get_node("Visual/TreePresentation") as HarvestableTreePresentation
_check_initial_presentation(berry_visual, tree_visual)
var berry_state: ResourceStateRecord = manager.get_resource_state(BERRY_ID)
var tree_state: ResourceStateRecord = manager.get_resource_state(TREE_ID)
_check(berry_state != null and tree_state != null, "Authored resources should bind state")
if berry_state == null or tree_state == null:
_finish()
return
for _action in 2:
_check(
is_equal_approx(manager.harvest_resource_node(berry), berry.yield_per_action),
"Player harvesting should extract the configured berry yield"
)
var npc: SimNPC = manager.npcs[0]
for _action in 2:
_complete_npc_gather(manager, npc, tree)
_check(
is_equal_approx(berry_state.get_amount_remaining(), 2.0),
"Two player harvests should leave the authored berry at 40 percent"
)
_check(
berry_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.LOW,
"The berry should derive its low tier from authoritative amount"
)
_check(
berry_visual.get_visible_leaf_count() == 3 and berry_visual.get_visible_berry_count() == 4,
"The low berry should visibly thin to three leaves and four berries"
)
_check(
is_equal_approx(tree_state.get_amount_remaining(), 4.0),
"Two NPC completions should leave the authored tree at half amount"
)
_check(
tree_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.LOW,
"The tree should derive its low tier from authoritative amount"
)
_check(
(
tree_visual.is_standing_visible()
and not tree_visual.is_stump_visible()
and tree_visual.get_visible_canopy_count() == 2
),
"The low tree should remain standing with a visibly thinned crown"
)
_check(
is_equal_approx(npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD), 4.0),
"The low tree state should come from real NPC inventory-producing extraction"
)
var low_snapshot: String = manager.serialize_state()
_check_no_visual_state(low_snapshot)
while berry_state.can_extract():
manager.harvest_resource_node(berry)
while tree_state.can_extract():
_complete_npc_gather(manager, npc, tree)
_check_depleted_presentation(berry_visual, tree_visual)
var depleted_snapshot: String = manager.serialize_state()
_check_no_visual_state(depleted_snapshot)
_check(
manager.restore_state_from_json(low_snapshot),
"Low authoritative resource state should restore"
)
berry_state = manager.get_resource_state(BERRY_ID)
tree_state = manager.get_resource_state(TREE_ID)
_check(
(
is_equal_approx(berry_state.get_amount_remaining(), 2.0)
and berry_visual.get_visible_berry_count() == 4
and berry_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.LOW
),
"Restore should rebuild the low berry visual from amount alone"
)
_check(
(
is_equal_approx(tree_state.get_amount_remaining(), 4.0)
and tree_visual.get_visible_canopy_count() == 2
and not tree_visual.is_stump_visible()
),
"Restore should rebuild the low standing tree from amount alone"
)
_check(
manager.restore_state_from_json(depleted_snapshot),
"Depleted authoritative resource state should restore"
)
_check_depleted_presentation(berry_visual, tree_visual)
_finish()
func _check_initial_presentation(
berry_visual: StylizedBerryPatch, tree_visual: HarvestableTreePresentation
) -> void:
_check(
(
berry_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.FULL
and berry_visual.get_visible_leaf_count() == 5
and berry_visual.get_visible_berry_count() == 8
),
"A full berry source should show all authored leaves and fruit"
)
_check(
(
tree_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.FULL
and tree_visual.is_standing_visible()
and not tree_visual.is_stump_visible()
and tree_visual.get_visible_canopy_count() == 4
),
"A full tree source should show its complete standing crown"
)
func _complete_npc_gather(manager, npc: SimNPC, resource: ResourceNode) -> void:
npc.target_id = resource.node_id
var reserved: bool = manager.reserve_resource(resource.node_id, npc.id)
_check(reserved, "NPC extraction should reserve the authored tree")
if reserved:
manager.call("_complete_resource_gather", npc, SimulationIds.ACTION_GATHER_WOOD)
func _check_depleted_presentation(
berry_visual: StylizedBerryPatch, tree_visual: HarvestableTreePresentation
) -> void:
_check(
(
berry_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.DEPLETED
and berry_visual.get_visible_leaf_count() == 2
and berry_visual.get_visible_berry_count() == 0
),
"Real player depletion should leave a small fruitless berry remnant"
)
_check(
(
tree_visual.get_amount_tier() == ResourceAmountVisual.AmountTier.DEPLETED
and not tree_visual.is_standing_visible()
and tree_visual.is_stump_visible()
and tree_visual.get_visible_canopy_count() == 0
),
"Real NPC depletion should replace the standing tree with its stump"
)
func _check_no_visual_state(snapshot: String) -> void:
var parsed = JSON.parse_string(snapshot)
_check(parsed is Dictionary, "Serialized simulation state should remain valid JSON")
if not parsed is Dictionary:
return
var matched_records := 0
for resource_data in parsed.get("resources", []):
if StringName(resource_data.get("node_id", "")) not in [BERRY_ID, TREE_ID]:
continue
matched_records += 1
for field in resource_data.keys():
var field_name := String(field)
_check(
(
not field_name.contains("visual")
and not field_name.contains("visible")
and not field_name.contains("tier")
and not field_name.contains("stump")
),
"Resource save fields should contain authority, never visual state"
)
_check(matched_records == 2, "Both authored resources should remain in simulation saves")
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _finish() -> void:
if failures.is_empty():
print("[TEST] Jajce resource presentation passed: amount -> low/depleted -> restore")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
@@ -0,0 +1 @@
uid://c7yc6j4js8phj