extends SceneTree var failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: _test_core_pack() _test_deterministic_enumeration_and_dependencies() _test_duplicate_ids_fail_without_overrides() _test_unknown_references_categories_and_handlers() _finish() func _test_core_pack() -> void: var catalog := ContentCatalog.create_core() _check(catalog.is_valid(), "Core pack should validate: %s" % [catalog.get_errors()]) _check( ( _action_ids(catalog.get_actions()) == [ &"defend", &"deposit_food", &"deposit_wood", &"eat", &"feed_animal", &"gather_food", &"gather_wood", &"patrol", &"rest", &"sleep", &"study", &"wander", &"withdraw_food", ] ), "Core actions should enumerate by stable ID" ) _check( ( _profession_ids(catalog.get_professions()) == [&"farmer", &"guard", &"scholar", &"wanderer", &"woodcutter"] ), "Core professions should enumerate by stable ID" ) _check( ( _item_ids(catalog.get_items()) == [&"food", &"herb", &"item_claw", &"item_sword", &"regional_trade_goods", &"wood"] ), "Core items should include local and regional resources in stable-ID order" ) _check( _enemy_ids(catalog.get_enemies()) == [&"enemy_boar", &"enemy_raider", &"enemy_wolf"], "Core enemies should enumerate by stable ID" ) _check( _animal_ids(catalog.get_animals()) == [&"domestic_goat", &"domestic_sheep"], "Core animals should enumerate through the same content pack" ) var food := catalog.get_item(SimulationIds.RESOURCE_FOOD) var wood := catalog.get_item(SimulationIds.RESOURCE_WOOD) var sword := catalog.get_item(SimulationIds.ITEM_SWORD) var claw := catalog.get_item(SimulationIds.ITEM_CLAW) _check(food != null and food.is_resource(), "Food should be a resource item") _check(wood != null and wood.is_resource(), "Wood should be a resource item") _check( sword != null and sword.is_weapon() and is_equal_approx(sword.damage, 24.0), "Sword should preserve its weapon contract" ) _check( claw != null and claw.is_weapon() and is_equal_approx(claw.reach, 1.2), "Claw should preserve its weapon contract" ) var wolf := catalog.get_enemy(&"enemy_wolf") var boar := catalog.get_enemy(&"enemy_boar") _check( wolf != null and wolf.weapon_id == SimulationIds.ITEM_CLAW, "Enemy references should resolve against catalog items" ) _check( boar != null and boar.behavior_profile_id == wolf.behavior_profile_id, "New enemy types should be able to reuse an existing behavior profile" ) func _test_deterministic_enumeration_and_dependencies() -> void: var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack var addon := _pack(&"addon", "Addon") addon.required_pack_ids = [&"core"] addon.required_handler_ids = [&"test_handler"] addon.items = [_resource_item(&"amber", "Amber")] var packs: Array[SimulationContentPack] = [addon, core] var catalog := ContentCatalog.new() var handlers: Array[StringName] = [&"test_handler"] var errors := catalog.rebuild(packs, handlers) _check(errors.is_empty(), "Known pack and handler dependencies should validate: %s" % [errors]) _check( _pack_ids(catalog.get_packs()) == [&"addon", &"core"], "Pack enumeration should not depend on input order" ) _check( ( _item_ids(catalog.get_items()) == [ &"amber", &"food", &"herb", &"item_claw", &"item_sword", &"regional_trade_goods", &"wood", ] ), "Merged item enumeration should be globally stable-ID sorted" ) var returned_items := catalog.get_items() returned_items.clear() _check( catalog.get_items().size() == 7, "Callers should not be able to mutate the catalog's ordered arrays" ) func _test_duplicate_ids_fail_without_overrides() -> void: var first := _pack(&"first", "First") first.items = [_resource_item(&"shared_item", "First value")] var second := _pack(&"second", "Second") second.items = [_resource_item(&"shared_item", "Second value")] var packs: Array[SimulationContentPack] = [second, first] var catalog := ContentCatalog.new() var errors := catalog.rebuild(packs) _check(_has_error(errors, "Duplicate item_id 'shared_item'"), "Duplicate item IDs should fail") _check( ( not catalog.is_valid() and catalog.get_item(&"shared_item") == null and catalog.get_items().is_empty() ), "An invalid build should publish neither duplicate value nor partial content" ) var duplicate_pack := _pack(&"first", "Duplicate pack") var duplicate_packs: Array[SimulationContentPack] = [first, duplicate_pack] errors = catalog.rebuild(duplicate_packs) _check(_has_error(errors, "Duplicate pack_id 'first'"), "Duplicate pack IDs should fail") first = _pack(&"first", "First") second = _pack(&"second", "Second") first.animals = [_animal(&"shared_animal", &"feed_animal")] second.animals = [_animal(&"shared_animal", &"feed_animal")] var duplicate_animal_packs: Array[SimulationContentPack] = [second, first] errors = catalog.rebuild(duplicate_animal_packs) _check( _has_error(errors, "Duplicate animal_definition_id 'shared_animal'"), "Duplicate animal definitions should fail without an override" ) func _test_unknown_references_categories_and_handlers() -> void: var broken := _pack(&"broken", "Broken") broken.required_pack_ids = [&"missing_pack"] broken.required_handler_ids = [&"missing_handler"] var invalid_item := _resource_item(&"strange_item", "Strange") invalid_item.category = &"unknown_category" var ordinary_resource := _resource_item(&"ordinary_resource", "Ordinary resource") var missing_profession := _action(&"missing_profession") missing_profession.preferred_profession_id = &"unknown_profession" var missing_action := _action(&"missing_action") missing_action.target_type = SimulationIds.TARGET_RESOURCE missing_action.resource_action_id = &"unknown_resource_action" var missing_cost_item := _action(&"missing_cost") missing_cost_item.completion_cost_resource_id = &"unknown_cost_item" missing_cost_item.completion_cost_amount = 1.0 missing_cost_item.selection_policy_id = &"unknown_selection" missing_cost_item.target_policy_id = &"unknown_targeting" missing_cost_item.completion_handler_id = &"unknown_completion" var invalid_effect := ActionEffect.new() invalid_effect.effect_id = &"broken_transfer" invalid_effect.effect_kind = ActionEffect.KIND_INVENTORY_TRANSFER missing_cost_item.effects = [invalid_effect] var weapon_item := _weapon_item(&"test_weapon", "Test weapon") var weapon_cost := _action(&"weapon_cost") weapon_cost.completion_cost_resource_id = weapon_item.item_id weapon_cost.completion_cost_amount = 1.0 var missing_weapon := _enemy(&"missing_weapon_enemy", &"unknown_weapon") var non_weapon := _enemy(&"non_weapon_enemy", ordinary_resource.item_id) var missing_animal_action := _animal(&"unsupported_animal", &"unknown_animal_action") broken.items = [invalid_item, ordinary_resource, weapon_item] broken.actions = [missing_profession, missing_action, missing_cost_item, weapon_cost] broken.enemies = [missing_weapon, non_weapon] broken.animals = [missing_animal_action] var packs: Array[SimulationContentPack] = [broken] var known_handlers: Array[StringName] = [&"known_handler"] var catalog := ContentCatalog.new() var errors := catalog.rebuild(packs, known_handlers) for expected in [ "unknown required pack 'missing_pack'", "unknown required handler 'missing_handler'", "category 'unknown_category' is invalid", "unknown profession 'unknown_profession'", "unknown resource action 'unknown_resource_action'", "unknown completion cost item 'unknown_cost_item'", "unknown selection policy 'unknown_selection'", "unknown target policy 'unknown_targeting'", "unknown completion handler 'unknown_completion'", "needs an item and positive amount", "needs source/destination roles", "non-resource completion cost item 'test_weapon'", "unknown weapon 'unknown_weapon'", "non-weapon item 'ordinary_resource'", "Animal 'unsupported_animal' references unknown action 'unknown_animal_action'", ]: _check(_has_error(errors, expected), "Catalog should report %s: %s" % [expected, errors]) _check(not catalog.is_valid(), "Unknown references should invalidate the complete catalog") func _pack(pack_id: StringName, display_name: String) -> SimulationContentPack: var pack := SimulationContentPack.new() pack.pack_id = pack_id pack.display_name = display_name return pack func _resource_item(item_id: StringName, display_name: String) -> ItemDefinition: var item := ItemDefinition.new() item.item_id = item_id item.display_name = display_name item.category = ItemDefinition.CATEGORY_RESOURCE return item func _weapon_item(item_id: StringName, display_name: String) -> ItemDefinition: var item := ItemDefinition.new() item.item_id = item_id item.display_name = display_name item.category = ItemDefinition.CATEGORY_WEAPON item.equip_slot = SimulationIds.EQUIP_SLOT_HAND item.damage = 1.0 item.reach = 1.0 item.attack_cooldown = 1.0 return item func _action(action_id: StringName) -> ActionDefinition: var action := ActionDefinition.new() action.action_id = action_id action.display_name = String(action_id).capitalize() action.target_type = SimulationIds.TARGET_ACTIVITY return action func _enemy(enemy_id: StringName, weapon_id: StringName) -> EnemyDefinition: var enemy := EnemyDefinition.new() enemy.enemy_id = enemy_id enemy.display_name = String(enemy_id).capitalize() enemy.kind = SimulationIds.COMBATANT_KIND_RAIDER enemy.weapon_id = weapon_id return enemy func _animal(animal_definition_id: StringName, action_id: StringName) -> AnimalDefinition: var animal := AnimalDefinition.new() animal.animal_definition_id = animal_definition_id animal.species_id = &"test_species" animal.display_name = String(animal_definition_id).capitalize() animal.supported_action_ids = [action_id] animal.presentation_cue_id = PresentationCueIds.ANIMAL_DOMESTIC_GOAT return animal func _pack_ids(packs: Array[SimulationContentPack]) -> Array[StringName]: var ids: Array[StringName] = [] for pack in packs: ids.append(pack.pack_id) return ids func _action_ids(actions: Array[ActionDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for action in actions: ids.append(action.action_id) return ids func _profession_ids(professions: Array[ProfessionDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for profession in professions: ids.append(profession.profession_id) return ids func _item_ids(items: Array[ItemDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for item in items: ids.append(item.item_id) return ids func _enemy_ids(enemies: Array[EnemyDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for enemy in enemies: ids.append(enemy.enemy_id) return ids func _animal_ids(animals: Array[AnimalDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for animal in animals: ids.append(animal.animal_definition_id) return ids func _has_error(errors: Array[String], fragment: String) -> bool: for error in errors: if fragment in error: return true return false func _check(condition: bool, message: String) -> void: if not condition: failures.append(message) func _finish() -> void: if failures.is_empty(): print("[TEST] Content catalog passed") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1)