feat: derive resource visuals from state
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user