style: apply gdformat formatting across the entire project

Auto-format all GDScript files using gdformat from gdtoolkit.
This is a baseline formatting pass to ensure consistent style:

- Normalizes indentation and spacing
- Wraps long lines to 100 characters
- Removes trailing whitespace
- Standardizes blank lines between functions

68 files reformatted, 8 files left unchanged (3rd-party addon files
with parse errors excluded).
This commit is contained in:
2026-07-05 23:06:23 +02:00
parent 28a2e42c41
commit 4463e524aa
69 changed files with 2253 additions and 1647 deletions
+28 -29
View File
@@ -1,5 +1,6 @@
extends SceneTree
class FakeActiveWorld:
extends Node
@@ -12,12 +13,15 @@ class FakeActiveWorld:
func get_activity_target(_action_id: StringName) -> Dictionary:
return {"target_id": "", "position": activity_position}
var failures: Array[String] = []
var travel_requests: Array[Vector3] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_selection_and_execution_are_separate()
_test_target_resolution_and_travel_are_separate()
@@ -31,16 +35,11 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _test_selection_and_execution_are_separate() -> void:
var village := SimVillage.new()
village.debug_logs = false
var npc := SimNPC.new(
700,
"BoundaryWorker",
SimulationIds.PROFESSION_FARMER,
5.0,
5.0
)
var npc := SimNPC.new(700, "BoundaryWorker", SimulationIds.PROFESSION_FARMER, 5.0, 5.0)
npc.hunger = 80.0
var executor := ActionExecutionSystem.new()
executor.advance_npc(npc, village)
@@ -55,12 +54,12 @@ func _test_selection_and_execution_are_separate() -> void:
"Selection should independently choose food retrieval"
)
func _test_target_resolution_and_travel_are_separate() -> void:
var adapter := FakeActiveWorld.new()
adapter.resource_candidates = [{
"target_id": "boundary_bush",
"position": Vector3(3.0, 0.0, 2.0)
}]
adapter.resource_candidates = [
{"target_id": "boundary_bush", "position": Vector3(3.0, 0.0, 2.0)}
]
root.add_child(adapter)
var manager: Node = load("res://simulation/SimulationManager.gd").new()
@@ -70,18 +69,20 @@ func _test_target_resolution_and_travel_are_separate() -> void:
manager.set_process(false)
manager.npc_travel_requested.connect(_on_travel_requested)
var resource_state := ResourceStateRecord.from_dictionary({
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
"node_id": "boundary_bush",
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
"resource_id": String(SimulationIds.RESOURCE_FOOD),
"amount_remaining": 5.0,
"yield_per_action": 1.0,
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
})
var resource_state := ResourceStateRecord.from_dictionary(
{
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
"node_id": "boundary_bush",
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
"resource_id": String(SimulationIds.RESOURCE_FOOD),
"amount_remaining": 5.0,
"yield_per_action": 1.0,
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
}
)
manager.resource_states[resource_state.get_node_id()] = resource_state
var npc: SimNPC = manager.npcs[0]
@@ -91,8 +92,7 @@ func _test_target_resolution_and_travel_are_separate() -> void:
"Target resolver should resolve a resource from adapter facts"
)
_check(
npc.target_id == &"boundary_bush"
and resource_state.get_reserved_by() == npc.id,
npc.target_id == &"boundary_bush" and resource_state.get_reserved_by() == npc.id,
"Target resolver should authoritatively assign and reserve the target"
)
_check(
@@ -113,12 +113,11 @@ func _test_target_resolution_and_travel_are_separate() -> void:
manager.free()
adapter.free()
func _on_travel_requested(
_npc: SimNPC,
target_position: Vector3
) -> void:
func _on_travel_requested(_npc: SimNPC, target_position: Vector3) -> void:
travel_requests.append(target_position)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+4
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var clock := SimulationClock.new(1.0)
_check(clock.advance(0.4) == 0, "Clock should not tick before its interval")
@@ -28,6 +30,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _run_scenario(seed_value: int) -> String:
var manager: Node = load("res://simulation/SimulationManager.gd").new()
manager.simulation_seed = seed_value
@@ -47,6 +50,7 @@ func _run_scenario(seed_value: int) -> String:
manager.free()
return checksum
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+45 -53
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var manager: Node = load("res://simulation/SimulationManager.gd").new()
manager.debug_logs = false
@@ -14,18 +16,20 @@ func _run() -> void:
var pantry: StorageStateRecord = manager.get_pantry()
pantry.withdraw(SimulationIds.RESOURCE_FOOD, 1000.0)
manager._sync_village_food()
var resource := ResourceStateRecord.from_dictionary({
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
"node_id": "food_loop_bush",
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
"resource_id": String(SimulationIds.RESOURCE_FOOD),
"amount_remaining": 10.0,
"yield_per_action": 2.0,
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
})
var resource := ResourceStateRecord.from_dictionary(
{
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
"node_id": "food_loop_bush",
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
"resource_id": String(SimulationIds.RESOURCE_FOOD),
"amount_remaining": 10.0,
"yield_per_action": 2.0,
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
}
)
manager.resource_states[resource.get_node_id()] = resource
var npc: SimNPC = manager.npcs[0]
@@ -38,10 +42,9 @@ func _run() -> void:
manager.simulate_tick()
_check(
is_equal_approx(resource.get_amount_remaining(), 8.0)
and is_equal_approx(
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
2.0
(
is_equal_approx(resource.get_amount_remaining(), 8.0)
and is_equal_approx(npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 2.0)
),
"Gathering should move food from the source into NPC inventory"
)
@@ -58,10 +61,9 @@ func _run() -> void:
manager.notify_npc_arrived(npc.id)
manager.simulate_tick()
_check(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 2.0)
and is_equal_approx(
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
0.0
(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 2.0)
and is_equal_approx(npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0)
),
"Deposit should transfer carried food into the pantry"
)
@@ -75,28 +77,23 @@ func _run() -> void:
manager.notify_npc_arrived(npc.id)
manager.simulate_tick()
_check(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 1.0)
and is_equal_approx(
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
1.0
(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 1.0)
and is_equal_approx(npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 1.0)
),
"Withdraw should transfer pantry food into NPC inventory"
)
manager.simulate_tick()
_check(
npc.current_task == SimulationIds.ACTION_EAT,
"NPC carrying food should select eat"
)
_check(npc.current_task == SimulationIds.ACTION_EAT, "NPC carrying food should select eat")
manager.notify_npc_arrived(npc.id)
var hunger_before_eating := npc.hunger
manager.simulate_tick()
manager.simulate_tick()
_check(
npc.hunger < hunger_before_eating
and is_equal_approx(
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
0.0
(
npc.hunger < hunger_before_eating
and is_equal_approx(npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0)
),
"Eating should consume carried food and reduce hunger"
)
@@ -121,8 +118,10 @@ func _run() -> void:
"Economic events should restore with the simulation"
)
_check(
restored.economic_events.size() == manager.economic_events.size()
and restored.next_event_id == manager.next_event_id,
(
restored.economic_events.size() == manager.economic_events.size()
and restored.next_event_id == manager.next_event_id
),
"Economic event history and its next ID should round-trip"
)
@@ -134,10 +133,12 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _check_economic_event_chain(manager: Node) -> void:
var expected_types: Array[StringName] = [
SimulationIds.EVENT_RESOURCE_EXTRACTED,
@@ -154,28 +155,19 @@ func _check_economic_event_chain(manager: Node) -> void:
for index in expected_types.size():
var event: EconomicEventRecord = manager.economic_events[index]
_check(
int(event.data["event_id"]) == index
and StringName(event.data["event_type"]) == expected_types[index]
and StringName(event.data["item_id"])
== SimulationIds.RESOURCE_FOOD,
(
int(event.data["event_id"]) == index
and StringName(event.data["event_type"]) == expected_types[index]
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
),
"Economic event order, identity, and item should be deterministic"
)
_check(
is_equal_approx(
float(manager.economic_events[0].data["amount"]),
2.0
)
and is_equal_approx(
float(manager.economic_events[1].data["amount"]),
2.0
)
and is_equal_approx(
float(manager.economic_events[2].data["amount"]),
1.0
)
and is_equal_approx(
float(manager.economic_events[3].data["amount"]),
1.0
(
is_equal_approx(float(manager.economic_events[0].data["amount"]), 2.0)
and is_equal_approx(float(manager.economic_events[1].data["amount"]), 2.0)
and is_equal_approx(float(manager.economic_events[2].data["amount"]), 1.0)
and is_equal_approx(float(manager.economic_events[3].data["amount"]), 1.0)
),
"Economic events should record exact transferred quantities"
)
+16 -38
View File
@@ -2,9 +2,11 @@ extends SceneTree
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)
@@ -19,26 +21,21 @@ func _run() -> void:
main_scene.has_node("JajceWorld/TerrainRoot/Terrain3D"),
"Playable runtime should instance the Jajce Terrain3D world"
)
var terrain: Terrain3D = main_scene.get_node(
"JajceWorld/TerrainRoot/Terrain3D"
)
var terrain: Terrain3D = main_scene.get_node("JajceWorld/TerrainRoot/Terrain3D")
_check(
terrain.get_camera() == main_scene.get_node("CameraRig/Camera3D"),
"Runtime Terrain3D should bind to the gameplay camera"
)
_check(
not main_scene.has_node("World")
and not main_scene.has_node("ResourceNodes")
and not main_scene.has_node("ActivityMarkers"),
(
not main_scene.has_node("World")
and not main_scene.has_node("ResourceNodes")
and not main_scene.has_node("ActivityMarkers")
),
"Runtime should not retain duplicate flat-world objects"
)
var resource_root := main_scene.get_node(
"JajceWorld/WorldObjects/ResourceNodes"
)
_check(
resource_root.get_child_count() == 4,
"Runtime should contain four Jajce ResourceNodes"
)
var resource_root := main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes")
_check(resource_root.get_child_count() == 4, "Runtime should contain four Jajce ResourceNodes")
_check(
simulation_manager.resource_states.size() == 4,
"Simulation authority should bind all four Jajce resources"
@@ -48,11 +45,7 @@ func _run() -> void:
await _wait_for_navigation_map(navigation_map)
NavigationServer3D.map_force_update(navigation_map)
var fixed_origins := [
Vector3(-6.0, 0.0, -4.0),
Vector3(0.0, 0.0, 0.0),
Vector3(6.0, 0.0, 4.0)
]
var fixed_origins := [Vector3(-6.0, 0.0, -4.0), Vector3(0.0, 0.0, 0.0), Vector3(6.0, 0.0, 4.0)]
var destinations: Array[Vector3] = []
for marker_path in [
"JajceWorld/LegacyActivityMarkers/PantryMarker",
@@ -67,25 +60,14 @@ func _run() -> void:
for origin in fixed_origins:
for destination in destinations:
var path := NavigationServer3D.map_get_path(
navigation_map,
origin,
destination,
true
)
var path := NavigationServer3D.map_get_path(navigation_map, origin, destination, true)
_check(
not path.is_empty(),
"Navigation path should exist from %s to %s" % [origin, destination]
)
var village := SimVillage.new()
var worker := SimNPC.new(
100,
"BaselineWorker",
SimulationIds.PROFESSION_SCHOLAR,
5.0,
5.0
)
var worker := SimNPC.new(100, "BaselineWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0)
worker.set_task(SimulationIds.ACTION_STUDY, 2.0)
worker.start_working()
var executor := ActionExecutionSystem.new()
@@ -93,13 +75,7 @@ func _run() -> void:
executor.advance_npc(worker, village)
_check(worker.task_complete, "A two-tick working task should complete")
var starving := SimNPC.new(
101,
"BaselineStarving",
SimulationIds.PROFESSION_WANDERER,
5.0,
5.0
)
var starving := SimNPC.new(101, "BaselineStarving", SimulationIds.PROFESSION_WANDERER, 5.0, 5.0)
starving.hunger = 100.0
starving.starvation_death_threshold = 1
executor.advance_npc(starving, village)
@@ -114,6 +90,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _wait_for_navigation_map(navigation_map: RID) -> void:
for attempt in 30:
if (
@@ -125,6 +102,7 @@ func _wait_for_navigation_map(navigation_map: RID) -> void:
await physics_frame
_check(false, "Navigation map did not synchronize within 30 physics frames")
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+20 -33
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var camera := Camera3D.new()
camera.current = true
@@ -24,8 +26,10 @@ func _run() -> void:
)
_check(
terrain.assets.get_texture_count() >= 3,
"Jajce terrain should expose at least three materials (found %s)"
% terrain.assets.get_texture_count()
(
"Jajce terrain should expose at least three materials (found %s)"
% terrain.assets.get_texture_count()
)
)
_check(
absf(terrain.data.get_height(Vector3.ZERO)) < 0.1,
@@ -42,32 +46,22 @@ func _run() -> void:
world.get_node("FoliageRoot").get_child_count() >= 10,
"JajceWorld should include restrained authored foliage clusters"
)
_check(
world.has_node("FortressBlockout"),
"JajceWorld should include the fortress landmark"
)
_check(
world.has_node("WaterRoot/WaterfallBody"),
"WaterRoot should include a waterfall drop"
)
_check(world.has_node("FortressBlockout"), "JajceWorld should include the fortress landmark")
_check(world.has_node("WaterRoot/WaterfallBody"), "WaterRoot should include a waterfall drop")
_check(
world.has_node("WaterRoot/WaterfallMist"),
"WaterRoot should include waterfall mist particles"
)
_check(
world.has_node("WaterRoot/RiverSurface"),
"WaterRoot should include a river surface"
)
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
var environment: Environment = (
world.get_node("WorldEnvironment") as WorldEnvironment
).environment
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
)
_check(
environment.fog_enabled and environment.sky != null,
"Jajce environment should provide sky and restrained valley fog"
)
_check(
world.has_node("VillageRoot/Mill")
and world.has_node("VillageRoot/Bridge"),
world.has_node("VillageRoot/Mill") and world.has_node("VillageRoot/Bridge"),
"VillageRoot should include the mill and bridge blockouts"
)
var smoke_count := 0
@@ -103,11 +97,7 @@ func _run() -> void:
await _wait_for_navigation_map(navigation_map)
NavigationServer3D.map_force_update(navigation_map)
var origins := [
Vector3(-6, 0, -4),
Vector3(0, 0, 0),
Vector3(6, 0, 4)
]
var origins := [Vector3(-6, 0, -4), Vector3(0, 0, 0), Vector3(6, 0, 4)]
var destinations: Array[Vector3] = []
for resource in resources:
destinations.append((resource as ResourceNode).interaction_point.global_position)
@@ -116,12 +106,7 @@ func _run() -> void:
for origin in origins:
for destination in destinations:
var path := NavigationServer3D.map_get_path(
navigation_map,
origin,
destination,
true
)
var path := NavigationServer3D.map_get_path(navigation_map, origin, destination, true)
_check(
not path.is_empty(),
"Jajce navigation path should exist from %s to %s" % [origin, destination]
@@ -143,9 +128,7 @@ func _run() -> void:
)
var selected := ResourceNode.get_by_id(npc.target_id)
if selected != null:
var selected_state: ResourceStateRecord = manager.get_resource_state(
selected.node_id
)
var selected_state: ResourceStateRecord = manager.get_resource_state(selected.node_id)
_check(
selected_state.get_reserved_by() == npc.id,
"Selected Jajce resource should be authoritatively reserved"
@@ -153,7 +136,9 @@ func _run() -> void:
manager.release_resource(selected.node_id, npc.id)
if failures.is_empty():
print("[TEST] Jajce scaffold passed: Terrain3D, 6 textures, shader water, building blockouts, chimney smoke")
print(
"[TEST] Jajce scaffold passed: Terrain3D, 6 textures, shader water, building blockouts, chimney smoke"
)
quit(0)
return
@@ -161,6 +146,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _wait_for_navigation_map(navigation_map: RID) -> void:
for attempt in 30:
if (
@@ -172,6 +158,7 @@ func _wait_for_navigation_map(navigation_map: RID) -> void:
await physics_frame
_check(false, "Jajce navigation map did not synchronize within 30 physics frames")
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+12 -22
View File
@@ -2,9 +2,11 @@ extends SceneTree
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)
@@ -21,25 +23,13 @@ func _run() -> void:
var carried_food: MeshInstance3D = visual.get_node("CarriedFood")
_check(not carried_food.visible, "NPC should begin without a carried-food prop")
npc.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
manager.emit_signal(
"npc_inventory_changed",
npc,
SimulationIds.RESOURCE_FOOD,
1.0
)
manager.emit_signal("npc_inventory_changed", npc, SimulationIds.RESOURCE_FOOD, 1.0)
_check(carried_food.visible, "Carried food should be visible on the NPC")
npc.remove_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
manager.emit_signal(
"npc_inventory_changed",
npc,
SimulationIds.RESOURCE_FOOD,
0.0
)
manager.emit_signal("npc_inventory_changed", npc, SimulationIds.RESOURCE_FOOD, 0.0)
_check(not carried_food.visible, "Deposited or eaten food should be hidden")
var resource_state: ResourceStateRecord = manager.get_resource_state(
&"berry_bush_01"
)
var resource_state: ResourceStateRecord = manager.get_resource_state(&"berry_bush_01")
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
npc.target_id = resource_state.get_node_id()
npc.travel_target_position = Vector3(-6.0, 0.0, -8.0)
@@ -60,9 +50,11 @@ func _run() -> void:
"Unload should synchronize the last active position"
)
_check(
npc.current_task == saved_action
and npc.target_id == saved_target_id
and npc.travel_target_position.is_equal_approx(saved_target_position),
(
npc.current_task == saved_action
and npc.target_id == saved_target_id
and npc.travel_target_position.is_equal_approx(saved_target_position)
),
"Unload should preserve action and target state"
)
_check(
@@ -87,10 +79,7 @@ func _run() -> void:
manager.get_state_checksum() == unloaded_checksum,
"Loading a visual must not mutate simulation state"
)
_check(
resource_state.get_reserved_by() == npc.id,
"Reload should preserve the reservation"
)
_check(resource_state.get_reserved_by() == npc.id, "Reload should preserve the reservation")
var moved_position := authoritative_position + Vector3(0.5, 0.0, 0.25)
reloaded.emit_signal("position_changed", npc.id, moved_position)
@@ -124,6 +113,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+17 -20
View File
@@ -3,9 +3,11 @@ extends SceneTree
var main_scene: Node
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
main_scene = load("res://main.tscn").instantiate()
root.add_child(main_scene)
@@ -14,20 +16,14 @@ func _run() -> void:
var simulation_manager := main_scene.get_node("SimulationManager")
var player := main_scene.get_node("Player")
var bush := main_scene.get_node(
"JajceWorld/WorldObjects/ResourceNodes/BerryBush_01"
) as ResourceNode
var tree := main_scene.get_node(
"JajceWorld/WorldObjects/ResourceNodes/Tree_01"
) as ResourceNode
var bush := (
main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes/BerryBush_01") as ResourceNode
)
var tree := main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes/Tree_01") as ResourceNode
simulation_manager.set_process(false)
var bush_state: ResourceStateRecord = simulation_manager.get_resource_state(
bush.node_id
)
var tree_state: ResourceStateRecord = simulation_manager.get_resource_state(
tree.node_id
)
var bush_state: ResourceStateRecord = simulation_manager.get_resource_state(bush.node_id)
var tree_state: ResourceStateRecord = simulation_manager.get_resource_state(tree.node_id)
bush_state.set_amount_remaining(1.0)
_check(
simulation_manager.reserve_resource(bush.node_id, 999),
@@ -41,11 +37,11 @@ func _run() -> void:
is_equal_approx(bush_state.get_amount_remaining(), 0.0),
"Player should deplete the nearby bush"
)
_check(is_equal_approx(simulation_manager.village.food, food_before + 1.0), "Village should receive exactly the bush's remaining food")
_check(
bush_state.get_reserved_by() == -1,
"Depletion should release the NPC reservation"
is_equal_approx(simulation_manager.village.food, food_before + 1.0),
"Village should receive exactly the bush's remaining food"
)
_check(bush_state.get_reserved_by() == -1, "Depletion should release the NPC reservation")
player.global_position = tree.interaction_point.global_position
var wood_before: float = simulation_manager.village.wood
@@ -53,13 +49,13 @@ func _run() -> void:
player.try_interact()
_check(
is_equal_approx(
tree_state.get_amount_remaining(),
tree_before - tree.yield_per_action
),
is_equal_approx(tree_state.get_amount_remaining(), tree_before - tree.yield_per_action),
"Player should extract the tree's configured yield"
)
_check(is_equal_approx(simulation_manager.village.wood, wood_before + tree.yield_per_action), "Village should receive exactly the extracted wood")
_check(
is_equal_approx(simulation_manager.village.wood, wood_before + tree.yield_per_action),
"Village should receive exactly the extracted wood"
)
if failures.is_empty():
print("[TEST] ResourceNode player parity passed")
@@ -70,6 +66,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+5 -5
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var test_directory := "user://save_slot_test_%s" % Time.get_ticks_usec()
var store := SaveSlotStore.new(test_directory)
@@ -48,17 +50,14 @@ func _run() -> void:
var unchanged_checksum: String = manager.get_state_checksum()
var file := FileAccess.open(final_path, FileAccess.WRITE)
file.store_string("{\"schema\":\"broken\"}")
file.store_string('{"schema":"broken"}')
file.close()
_check(not store.load_into(manager), "Invalid save data should be rejected")
_check(
manager.get_state_checksum() == unchanged_checksum,
"A rejected save must not partially mutate the simulation"
)
_check(
not store.save(manager, "../escape"),
"Slot names must not escape the save directory"
)
_check(not store.save(manager, "../escape"), "Slot names must not escape the save directory")
store.delete_slot()
DirAccess.remove_absolute(ProjectSettings.globalize_path(test_directory))
@@ -70,6 +69,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+18 -33
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_registry_integrity()
_test_definition_backed_behavior()
@@ -20,12 +22,10 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _test_registry_integrity() -> void:
var errors := SimulationDefinitions.validate()
_check(
errors.is_empty(),
"Definition registry should validate: %s" % [errors]
)
_check(errors.is_empty(), "Definition registry should validate: %s" % [errors])
var action_ids: Array[StringName] = []
for definition in SimulationDefinitions.get_actions():
@@ -34,30 +34,19 @@ func _test_registry_integrity() -> void:
SimulationDefinitions.get_action(definition.action_id) == definition,
"Action lookup should return '%s'" % definition.action_id
)
_check(
action_ids.size() == 9,
"Registry should contain all nine executable prototype actions"
)
_check(action_ids.size() == 9, "Registry should contain all nine executable prototype actions")
var profession_ids := SimulationDefinitions.get_profession_ids()
_check(
profession_ids.size() == 5,
"Registry should contain all five prototype professions"
)
_check(profession_ids.size() == 5, "Registry should contain all five prototype professions")
for profession_id in profession_ids:
_check(
SimulationDefinitions.get_profession(profession_id) != null,
"Profession lookup should return '%s'" % profession_id
)
func _test_definition_backed_behavior() -> void:
var npc := SimNPC.new(
500,
"DefinitionWorker",
SimulationIds.PROFESSION_SCHOLAR,
5.0,
5.0
)
var npc := SimNPC.new(500, "DefinitionWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0)
npc.set_task(SimulationIds.ACTION_STUDY)
var study := SimulationDefinitions.get_action(SimulationIds.ACTION_STUDY)
_check(
@@ -68,23 +57,18 @@ func _test_definition_backed_behavior() -> void:
study.preferred_profession_id == SimulationIds.PROFESSION_SCHOLAR,
"Study should reference the stable scholar profession ID"
)
var gather := SimulationDefinitions.get_action(
SimulationIds.ACTION_GATHER_FOOD
)
var gather := SimulationDefinitions.get_action(SimulationIds.ACTION_GATHER_FOOD)
_check(
gather.target_type == SimulationIds.TARGET_RESOURCE
and gather.resource_action_id == SimulationIds.ACTION_GATHER_FOOD,
(
gather.target_type == SimulationIds.TARGET_RESOURCE
and gather.resource_action_id == SimulationIds.ACTION_GATHER_FOOD
),
"Gather-food target metadata should come from its definition"
)
func _test_state_reference_validation() -> void:
var npc := SimNPC.new(
501,
"ValidationWorker",
SimulationIds.PROFESSION_FARMER,
5.0,
5.0
)
var npc := SimNPC.new(501, "ValidationWorker", SimulationIds.PROFESSION_FARMER, 5.0, 5.0)
var valid_data := NPCStateRecord.capture(npc).to_dictionary()
var invalid_profession := valid_data.duplicate(true)
invalid_profession["profession"] = "missing_profession"
@@ -99,6 +83,7 @@ func _test_state_reference_validation() -> void:
"NPC state should reject an unknown action ID"
)
func _test_id_round_trip() -> void:
var manager: Node = load("res://simulation/SimulationManager.gd").new()
manager.debug_logs = false
@@ -116,8 +101,7 @@ func _test_id_round_trip() -> void:
)
for npc in restored.npcs:
_check(
typeof(npc.profession) == TYPE_STRING_NAME,
"Profession should restore as StringName"
typeof(npc.profession) == TYPE_STRING_NAME, "Profession should restore as StringName"
)
_check(
typeof(npc.current_task) == TYPE_STRING_NAME,
@@ -131,6 +115,7 @@ func _test_id_round_trip() -> void:
manager.free()
restored.free()
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+39 -48
View File
@@ -2,9 +2,11 @@ extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
await _test_deterministic_continuation()
await _test_resource_state_round_trip()
@@ -23,6 +25,7 @@ func _run() -> void:
push_error("[TEST] " + failure)
quit(1)
func _test_deterministic_continuation() -> void:
var uninterrupted := _create_manager(91234)
_advance_scenario(uninterrupted, 24)
@@ -63,10 +66,9 @@ func _test_deterministic_continuation() -> void:
before_save.free()
restored.free()
func _test_resource_state_round_trip() -> void:
var resource: ResourceNode = load(
"res://world/resource_nodes/ResourceNode.tscn"
).instantiate()
var resource: ResourceNode = load("res://world/resource_nodes/ResourceNode.tscn").instantiate()
resource.node_id = &"SerializationTestBerry"
resource.initial_amount = 9.0
resource.yield_per_action = 2.0
@@ -78,9 +80,7 @@ func _test_resource_state_round_trip() -> void:
manager.register_resource_node(resource),
"Resource presentation should bind to simulation authority"
)
var resource_state: ResourceStateRecord = manager.get_resource_state(
resource.node_id
)
var resource_state: ResourceStateRecord = manager.get_resource_state(resource.node_id)
_check(resource_state.reserve(42), "Resource should accept the test reservation")
resource_state.extract()
var saved_json: String = manager.serialize_state()
@@ -103,15 +103,10 @@ func _test_resource_state_round_trip() -> void:
is_equal_approx(resource_state.get_amount_remaining(), 7.0),
"Resource amount should round-trip"
)
_check(
resource_state.get_reserved_by() == 42,
"Resource reservation should round-trip"
)
_check(resource_state.get_reserved_by() == 42, "Resource reservation should round-trip")
_check(resource_state.is_enabled(), "Resource enabled state should round-trip")
var rebound: ResourceNode = load(
"res://world/resource_nodes/ResourceNode.tscn"
).instantiate()
var rebound: ResourceNode = load("res://world/resource_nodes/ResourceNode.tscn").instantiate()
rebound.node_id = &"SerializationTestBerry"
rebound.initial_amount = 999.0
root.add_child(rebound)
@@ -128,28 +123,28 @@ func _test_resource_state_round_trip() -> void:
manager.free()
rebound.free()
func _test_schema_rejection() -> void:
var unsupported := JSON.stringify({
"schema": SimulationStateRecord.SCHEMA_NAME,
"schema_version": 999
})
var unsupported := JSON.stringify(
{"schema": SimulationStateRecord.SCHEMA_NAME, "schema_version": 999}
)
_check(
SimulationStateRecord.from_json(unsupported) == null,
"Unsupported schema versions should be rejected"
)
_check(
SimulationStateRecord.from_json("[]") == null,
"Non-record JSON should be rejected"
)
_check(SimulationStateRecord.from_json("[]") == null, "Non-record JSON should be rejected")
func _test_legacy_resource_migration() -> void:
var migrated := ResourceStateRecord.from_dictionary({
"schema_version": 1,
"node_id": "legacy_tree",
"amount_remaining": 6.0,
"reserved_by": 12,
"enabled": true
})
var migrated := ResourceStateRecord.from_dictionary(
{
"schema_version": 1,
"node_id": "legacy_tree",
"amount_remaining": 6.0,
"reserved_by": 12,
"enabled": true
}
)
_check(migrated != null, "ResourceStateRecord v1 should migrate explicitly")
if migrated == null:
return
@@ -158,19 +153,13 @@ func _test_legacy_resource_migration() -> void:
"Migrated resource state should use the current nested schema"
)
_check(
is_equal_approx(migrated.get_amount_remaining(), 6.0)
and migrated.get_reserved_by() == 12,
is_equal_approx(migrated.get_amount_remaining(), 6.0) and migrated.get_reserved_by() == 12,
"Resource migration should preserve mutable authority"
)
func _test_legacy_npc_migration() -> void:
var npc := SimNPC.new(
88,
"LegacyNPC",
SimulationIds.PROFESSION_FARMER,
5.0,
5.0
)
var npc := SimNPC.new(88, "LegacyNPC", SimulationIds.PROFESSION_FARMER, 5.0, 5.0)
var legacy_data := NPCStateRecord.capture(npc).to_dictionary()
legacy_data["schema_version"] = NPCStateRecord.LEGACY_SCHEMA_VERSION
legacy_data.erase("travel_target_position")
@@ -184,6 +173,7 @@ func _test_legacy_npc_migration() -> void:
"Legacy NPC migration should not invent an active travel target"
)
func _test_legacy_world_storage_migration() -> void:
var manager := _create_manager(55)
var legacy_data: Dictionary = manager.create_state_record().to_dictionary()
@@ -193,35 +183,34 @@ func _test_legacy_world_storage_migration() -> void:
_check(migrated != null, "World schema v1 should migrate pantry storage")
if migrated != null:
_check(
migrated.storages.size() == 1
and is_equal_approx(
migrated.storages[0].get_amount(
SimulationIds.RESOURCE_FOOD
),
manager.village.food
(
migrated.storages.size() == 1
and is_equal_approx(
migrated.storages[0].get_amount(SimulationIds.RESOURCE_FOOD),
manager.village.food
)
),
"World migration should preserve legacy village food in pantry"
)
manager.free()
func _test_previous_world_event_migration() -> void:
var manager := _create_manager(56)
var previous_data: Dictionary = manager.create_state_record().to_dictionary()
previous_data["schema_version"] = (
SimulationStateRecord.PREVIOUS_SCHEMA_VERSION
)
previous_data["schema_version"] = (SimulationStateRecord.PREVIOUS_SCHEMA_VERSION)
previous_data.erase("economic_events")
previous_data["simulation"].erase("next_event_id")
var migrated := SimulationStateRecord.from_dictionary(previous_data)
_check(migrated != null, "World schema v2 should add an empty event stream")
if migrated != null:
_check(
migrated.economic_events.is_empty()
and int(migrated.simulation["next_event_id"]) == 0,
migrated.economic_events.is_empty() and int(migrated.simulation["next_event_id"]) == 0,
"World v2 migration should initialize deterministic event identity"
)
manager.free()
func _create_manager(seed_value: int) -> Node:
var manager: Node = load("res://simulation/SimulationManager.gd").new()
manager.simulation_seed = seed_value
@@ -230,6 +219,7 @@ func _create_manager(seed_value: int) -> Node:
manager.set_process(false)
return manager
func _advance_scenario(manager: Node, steps: int) -> void:
for step in steps:
manager.simulate_tick()
@@ -238,6 +228,7 @@ func _advance_scenario(manager: Node, steps: int) -> void:
if npc.task_state == SimNPC.TASK_STATE_TRAVELING:
manager.notify_npc_arrived(npc.id)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)