171 lines
5.2 KiB
GDScript
171 lines
5.2 KiB
GDScript
extends SceneTree
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
var manager := _create_manager(913)
|
|
manager.set_process(false)
|
|
var pantry: StorageStateRecord = manager.get_pantry()
|
|
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
|
|
pantry.deposit(SimulationIds.RESOURCE_FOOD, 2.0)
|
|
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
|
|
|
var berry := ResourceNode.new()
|
|
berry.name = "CitizenBerry"
|
|
berry.node_id = &"citizen_berry"
|
|
berry.action_id = SimulationIds.ACTION_GATHER_FOOD
|
|
berry.resource_id = SimulationIds.RESOURCE_FOOD
|
|
berry.initial_amount = 2.0
|
|
berry.yield_per_action = 1.0
|
|
berry.debug_label_enabled = false
|
|
var interaction_point := Marker3D.new()
|
|
interaction_point.name = "InteractionPoint"
|
|
interaction_point.position = Vector3.ZERO
|
|
berry.add_child(interaction_point)
|
|
root.add_child(berry)
|
|
await process_frame
|
|
_check(
|
|
manager.register_resource_node(berry),
|
|
"Player citizen test should bind a real finite ResourceNode"
|
|
)
|
|
|
|
var initial_hunger: float = manager.get_player_state().get_hunger()
|
|
manager.simulate_tick()
|
|
_check(
|
|
is_equal_approx(manager.get_player_state().get_hunger(), initial_hunger + 0.125),
|
|
"Player hunger should advance deterministically on each simulation tick"
|
|
)
|
|
_check(
|
|
is_equal_approx(manager.get_player_state().get_energy(), 100.0 - 0.0625),
|
|
"Player energy should drain by an exact binary fraction each tick"
|
|
)
|
|
|
|
_check(
|
|
is_equal_approx(manager.harvest_resource_node(berry), 1.0),
|
|
"The player should carry the first finite yield"
|
|
)
|
|
_check(
|
|
is_equal_approx(manager.harvest_resource_node(berry), 1.0),
|
|
"The player should carry the second finite yield"
|
|
)
|
|
_check(
|
|
is_equal_approx(
|
|
manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD), 2.0
|
|
),
|
|
"Carried player food should be conserved across harvests"
|
|
)
|
|
|
|
var food_before: float = manager.get_player_state().get_hunger()
|
|
_check(manager.eat_player_food(), "The player should eat carried food")
|
|
_check(
|
|
(
|
|
is_equal_approx(manager.get_player_state().get_hunger(), maxf(food_before - 55.0, 0.0))
|
|
and is_equal_approx(
|
|
manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD), 1.0
|
|
)
|
|
),
|
|
"Eating carried food should ease hunger and consume exactly one unit"
|
|
)
|
|
_check(
|
|
is_equal_approx(manager.deposit_player_inventory(SimulationIds.RESOURCE_FOOD), 1.0),
|
|
"The player should deposit the remaining carried food"
|
|
)
|
|
_check(
|
|
(
|
|
is_equal_approx(
|
|
manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0
|
|
)
|
|
and is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 3.0)
|
|
),
|
|
"Deposit should move carried food into the pantry without creating or destroying it"
|
|
)
|
|
|
|
var food_before_pantry: float = pantry.get_amount(SimulationIds.RESOURCE_FOOD)
|
|
var hunger_before: float = manager.get_player_state().get_hunger()
|
|
_check(manager.eat_player_food(), "The player should eat from the pantry when carrying nothing")
|
|
_check(
|
|
(
|
|
is_equal_approx(
|
|
pantry.get_amount(SimulationIds.RESOURCE_FOOD), food_before_pantry - 1.0
|
|
)
|
|
and is_equal_approx(
|
|
manager.get_player_state().get_hunger(), maxf(hunger_before - 55.0, 0.0)
|
|
)
|
|
),
|
|
"Pantry eating should consume one village food and ease player hunger"
|
|
)
|
|
|
|
manager.simulate_tick()
|
|
var mid_hunger: float = manager.get_player_state().get_hunger()
|
|
var saved_json: String = manager.serialize_state()
|
|
var restored := _create_manager(914)
|
|
_check(
|
|
restored.restore_state_from_json(saved_json),
|
|
"Player state should survive schema-v13 save and restore"
|
|
)
|
|
_check(
|
|
(
|
|
restored.get_state_checksum() == manager.get_state_checksum()
|
|
and is_equal_approx(restored.get_player_state().get_hunger(), mid_hunger)
|
|
),
|
|
"Restore should preserve the deterministic player needs and checksum"
|
|
)
|
|
restored.free()
|
|
|
|
var v12_data: Dictionary = manager.create_state_record().to_dictionary()
|
|
v12_data["schema_version"] = SimulationStateRecord.PLAYER_NEEDS_SCHEMA_VERSION
|
|
v12_data.erase("player")
|
|
var migrated := SimulationStateRecord.from_dictionary(v12_data)
|
|
_check(
|
|
(
|
|
migrated != null
|
|
and migrated.player != null
|
|
and is_equal_approx(migrated.player.get_hunger(), 40.0)
|
|
and is_equal_approx(migrated.player.get_energy(), 100.0)
|
|
and migrated.player.get_carried_total() == 0.0
|
|
),
|
|
"World schema v12 should migrate to v13 with a fresh default player record"
|
|
)
|
|
|
|
berry.queue_free()
|
|
manager.free()
|
|
_finish()
|
|
|
|
|
|
func _create_manager(seed_value: int = 913) -> Node:
|
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
|
manager.simulation_seed = seed_value
|
|
manager.debug_logs = false
|
|
var home_positions: Array[Vector3] = [
|
|
Vector3(0.0, 0.0, 0.0),
|
|
Vector3(2.0, 0.0, 0.0),
|
|
Vector3(10.0, 0.0, 0.0),
|
|
Vector3(12.0, 0.0, 0.0),
|
|
Vector3(30.0, 0.0, 30.0),
|
|
Vector3(40.0, 0.0, 40.0),
|
|
]
|
|
manager.home_positions = home_positions
|
|
root.add_child(manager)
|
|
manager.set_process(false)
|
|
return manager
|
|
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("[TEST] Player citizen loop passed: needs -> carry -> eat -> deposit")
|
|
quit(0)
|
|
return
|
|
for failure in failures:
|
|
push_error("[TEST] " + failure)
|
|
quit(1)
|
|
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append(message)
|