fix: enforce storage and tooling invariants

This commit is contained in:
Rijad Zuzo
2026-07-30 13:00:59 +02:00
parent e3aa3440a2
commit e284d3774d
9 changed files with 283 additions and 30 deletions
+86 -7
View File
@@ -24,6 +24,7 @@ func _run() -> void:
_test_relationship_schema_rejection()
_test_opportunity_schema_rejection()
_test_animal_schema_rejection()
_test_storage_schema_rejection()
_test_schema_rejection()
if failures.is_empty():
@@ -154,6 +155,80 @@ func _test_schema_rejection() -> void:
manager.free()
func _test_storage_schema_rejection() -> void:
var valid_storage := (
StorageStateRecord
. create(&"schema_test_storage", {String(SimulationIds.RESOURCE_FOOD): 1.0}, 2.0)
. to_dictionary()
)
var negative_amount := valid_storage.duplicate(true)
negative_amount["amounts"][String(SimulationIds.RESOURCE_FOOD)] = -1.0
_check(
StorageStateRecord.from_dictionary(negative_amount) == null,
"Storage records should reject negative item amounts"
)
var non_finite_amount := valid_storage.duplicate(true)
non_finite_amount["amounts"][String(SimulationIds.RESOURCE_FOOD)] = NAN
_check(
StorageStateRecord.from_dictionary(non_finite_amount) == null,
"Storage records should reject non-finite item amounts"
)
var invalid_capacity := valid_storage.duplicate(true)
invalid_capacity["capacity"] = -1.0
_check(
StorageStateRecord.from_dictionary(invalid_capacity) == null,
"Storage records should reject negative capacity"
)
var over_capacity := valid_storage.duplicate(true)
over_capacity["amounts"][String(SimulationIds.RESOURCE_FOOD)] = 3.0
_check(
StorageStateRecord.from_dictionary(over_capacity) == null,
"Storage records should reject contents above capacity"
)
var empty_item_id := valid_storage.duplicate(true)
empty_item_id["amounts"] = {"": 1.0}
_check(
StorageStateRecord.from_dictionary(empty_item_id) == null,
"Storage records should reject empty item IDs"
)
var storage := StorageStateRecord.from_dictionary(valid_storage)
_check(storage != null, "A valid bounded storage record should still parse")
if storage != null:
_check(
storage.deposit(SimulationIds.RESOURCE_FOOD, NAN) == 0.0,
"Storage deposits should reject non-finite requests"
)
_check(
storage.withdraw(SimulationIds.RESOURCE_FOOD, INF) == 0.0,
"Storage withdrawals should reject non-finite requests"
)
_check(
is_equal_approx(storage.get_amount(SimulationIds.RESOURCE_FOOD), 1.0),
"Rejected storage requests should not mutate authoritative stock"
)
var manager := _create_manager(42)
var mismatched_food: Dictionary = manager.create_state_record().to_dictionary()
mismatched_food["village"]["food"] = float(mismatched_food["village"]["food"]) + 1.0
_check(
SimulationStateRecord.from_dictionary(mismatched_food) == null,
"World records should reject village food that disagrees with pantry authority"
)
var mismatched_wood: Dictionary = manager.create_state_record().to_dictionary()
mismatched_wood["village"]["wood"] = float(mismatched_wood["village"]["wood"]) + 1.0
_check(
SimulationStateRecord.from_dictionary(mismatched_wood) == null,
"World records should reject village wood that disagrees with woodpile authority"
)
manager.free()
func _test_legacy_resource_migration() -> void:
var migrated := ResourceStateRecord.from_dictionary(
{
@@ -205,6 +280,10 @@ func _test_legacy_world_storage_migration() -> void:
var legacy_data: Dictionary = manager.create_state_record().to_dictionary()
legacy_data["schema_version"] = SimulationStateRecord.LEGACY_SCHEMA_VERSION
legacy_data.erase("storages")
var legacy_food := StorageStateRecord.DEFAULT_CAPACITY + 25.0
var legacy_wood := StorageStateRecord.DEFAULT_CAPACITY + 35.0
legacy_data["village"]["food"] = legacy_food
legacy_data["village"]["wood"] = legacy_wood
var migrated := SimulationStateRecord.from_dictionary(legacy_data)
_check(migrated != null, "World schema v1 should migrate authored storage")
if migrated != null:
@@ -219,14 +298,12 @@ func _test_legacy_world_storage_migration() -> void:
(
pantry != null
and woodpile != null
and is_equal_approx(
pantry.get_amount(SimulationIds.RESOURCE_FOOD), manager.village.food
)
and is_equal_approx(
woodpile.get_amount(SimulationIds.RESOURCE_WOOD), manager.village.wood
)
and is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), legacy_food)
and is_equal_approx(woodpile.get_amount(SimulationIds.RESOURCE_WOOD), legacy_wood)
and is_equal_approx(pantry.get_available_capacity(), 0.0)
and is_equal_approx(woodpile.get_available_capacity(), 0.0)
),
"World migration should preserve legacy village resources in their storage"
"World migration should preserve legacy stock above the default storage capacity"
)
manager.free()
@@ -842,6 +919,7 @@ func _test_opportunity_schema_rejection() -> void:
for storage_data in already_supplied["storages"]:
if StringName(storage_data["storage_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY:
storage_data["amounts"][String(SimulationIds.RESOURCE_FOOD)] = 1.0
already_supplied["village"]["food"] = 1.0
_check(
SimulationStateRecord.from_dictionary(already_supplied) == null,
"An open pantry need should reject a save whose target is already supplied"
@@ -985,6 +1063,7 @@ func _test_opportunity_schema_rejection() -> void:
func _build_opportunity_world(manager: Node, resolved: bool) -> Dictionary:
var world: Dictionary = manager.create_state_record().to_dictionary()
world["simulation"]["tick_count"] = 10
world["village"]["food"] = 1.0 if resolved else 0.0
for storage_data in world["storages"]:
if StringName(storage_data["storage_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY:
storage_data["amounts"][String(SimulationIds.RESOURCE_FOOD)] = (