feat: route resources through typed content
This commit is contained in:
@@ -47,8 +47,8 @@ func _test_core_pack() -> void:
|
||||
"Core professions should enumerate by stable ID"
|
||||
)
|
||||
_check(
|
||||
_item_ids(catalog.get_items()) == [&"food", &"item_claw", &"item_sword", &"wood"],
|
||||
"Core items should include food, wood, sword, and claw in stable-ID order"
|
||||
_item_ids(catalog.get_items()) == [&"food", &"herb", &"item_claw", &"item_sword", &"wood"],
|
||||
"Core items should include legacy items and herbs in stable-ID order"
|
||||
)
|
||||
_check(
|
||||
_enemy_ids(catalog.get_enemies()) == [&"enemy_boar", &"enemy_raider", &"enemy_wolf"],
|
||||
@@ -96,13 +96,16 @@ func _test_deterministic_enumeration_and_dependencies() -> void:
|
||||
"Pack enumeration should not depend on input order"
|
||||
)
|
||||
_check(
|
||||
_item_ids(catalog.get_items()) == [&"amber", &"food", &"item_claw", &"item_sword", &"wood"],
|
||||
(
|
||||
_item_ids(catalog.get_items())
|
||||
== [&"amber", &"food", &"herb", &"item_claw", &"item_sword", &"wood"]
|
||||
),
|
||||
"Merged item enumeration should be globally stable-ID sorted"
|
||||
)
|
||||
var returned_items := catalog.get_items()
|
||||
returned_items.clear()
|
||||
_check(
|
||||
catalog.get_items().size() == 5,
|
||||
catalog.get_items().size() == 6,
|
||||
"Callers should not be able to mutate the catalog's ordered arrays"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_core_catalog_enumerates_new_authoring_domains_deterministically() -> void:
|
||||
var catalog := ContentCatalog.create_core()
|
||||
assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()])
|
||||
assert_eq(
|
||||
_tag_ids(catalog.get_capability_tags()),
|
||||
[&"item.food", &"item.medicine", &"item.wood", &"resource.harvestable", &"storage.village"]
|
||||
)
|
||||
assert_eq(
|
||||
_resource_ids(catalog.get_resources()),
|
||||
[&"resource_berry_patch", &"resource_herb_patch", &"resource_tree"]
|
||||
)
|
||||
assert_eq(
|
||||
_storage_ids(catalog.get_storages()),
|
||||
[&"village_apothecary", &"village_pantry", &"village_woodpile"]
|
||||
)
|
||||
var report := catalog.get_authoring_report()
|
||||
assert_true(report["valid"])
|
||||
assert_eq(report["resources"], _resource_ids(catalog.get_resources()))
|
||||
assert_eq(report["storages"], _storage_ids(catalog.get_storages()))
|
||||
assert_false(report.has("presentation_assets"))
|
||||
var returned_resources := catalog.get_resources()
|
||||
returned_resources.clear()
|
||||
assert_eq(catalog.get_resources().size(), 3)
|
||||
|
||||
|
||||
func test_herb_route_is_data_only_and_legacy_routes_remain_stable() -> void:
|
||||
var catalog := ContentCatalog.create_core()
|
||||
var routing := StorageRoutingPolicy.new(catalog)
|
||||
var herb_source := catalog.get_resource(&"resource_herb_patch")
|
||||
assert_not_null(herb_source)
|
||||
assert_eq(herb_source.item_id, &"herb")
|
||||
assert_eq(herb_source.extraction_action_id, SimulationIds.ACTION_GATHER_FOOD)
|
||||
assert_eq(routing.resolve_definition(herb_source.item_id).storage_id, &"village_apothecary")
|
||||
assert_eq(
|
||||
routing.resolve_definition(SimulationIds.RESOURCE_FOOD).storage_id,
|
||||
SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
)
|
||||
assert_eq(
|
||||
routing.resolve_definition(SimulationIds.RESOURCE_WOOD).storage_id,
|
||||
SimulationIds.STORAGE_VILLAGE_WOODPILE
|
||||
)
|
||||
assert_null(routing.resolve_definition(&"unknown_item"))
|
||||
|
||||
var apothecary := catalog.get_storage(&"village_apothecary")
|
||||
var nearly_full := apothecary.create_state({"herb": 39.0})
|
||||
var states: Array[StorageStateRecord] = [nearly_full]
|
||||
assert_same(routing.resolve_state(&"herb", 1.0, states), nearly_full)
|
||||
assert_null(routing.resolve_state(&"herb", 2.0, states))
|
||||
|
||||
|
||||
func test_routes_rank_priority_then_stable_storage_id() -> void:
|
||||
var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack
|
||||
var addon := _pack(&"routing_addon")
|
||||
addon.required_pack_ids = [&"core"]
|
||||
var early := _storage(&"a_medicine_store", &"item.medicine", 100)
|
||||
var low_priority := _storage(&"field_medicine_store", &"item.medicine", 10)
|
||||
addon.storages = [low_priority, early]
|
||||
var packs: Array[SimulationContentPack] = [addon, core]
|
||||
var catalog := ContentCatalog.new()
|
||||
assert_true(catalog.rebuild(packs).is_empty(), "%s" % [catalog.get_errors()])
|
||||
|
||||
var candidates := StorageRoutingPolicy.new(catalog).get_candidates(&"herb")
|
||||
assert_eq(
|
||||
_storage_ids(candidates),
|
||||
[&"a_medicine_store", &"village_apothecary", &"field_medicine_store"]
|
||||
)
|
||||
|
||||
|
||||
func test_catalog_rejects_duplicate_content_ids_without_publishing_overrides() -> void:
|
||||
var first := _complete_minimal_pack(&"first")
|
||||
var second := _complete_minimal_pack(&"second")
|
||||
var packs: Array[SimulationContentPack] = [second, first]
|
||||
var catalog := ContentCatalog.new()
|
||||
var duplicated_cues: Array[StringName] = [&"known.cue", &"known.cue"]
|
||||
var errors := catalog.rebuild(packs, [], duplicated_cues)
|
||||
|
||||
for expected in [
|
||||
"Duplicate action_id 'shared_action'",
|
||||
"Duplicate item_id 'shared_item'",
|
||||
"Duplicate tag_id 'shared_tag'",
|
||||
"Duplicate resource_id 'shared_resource'",
|
||||
"Duplicate storage_id 'shared_storage'",
|
||||
"Duplicate supported presentation cue 'known.cue'",
|
||||
]:
|
||||
assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors])
|
||||
assert_false(catalog.is_valid())
|
||||
assert_true(catalog.get_items().is_empty())
|
||||
assert_null(catalog.get_storage(&"shared_storage"))
|
||||
|
||||
|
||||
func test_catalog_rejects_unknown_action_item_tag_handler_and_cue_references() -> void:
|
||||
var pack := _pack(&"broken")
|
||||
var item := _item(&"broken_item", &"unknown_item_tag")
|
||||
item.presentation_cue_id = &"unknown.item.cue"
|
||||
var action := _action(&"broken_action")
|
||||
action.actor_capability_tags = [&"unknown_actor_tag"]
|
||||
action.target_capability_tags = [&"unknown_target_tag"]
|
||||
action.completion_handler_id = &"unknown_handler"
|
||||
action.presentation_cue_id = &"unknown.action.cue"
|
||||
var effect := ActionEffect.new()
|
||||
effect.effect_id = &"broken_effect"
|
||||
effect.effect_kind = ActionEffect.KIND_SCHEDULE_ACTION
|
||||
effect.scheduled_action_id = &"unknown_scheduled_action"
|
||||
effect.item_id = &"unknown_effect_item"
|
||||
action.effects = [effect]
|
||||
var resource := _resource(&"broken_resource", &"unknown_resource_item", &"unknown_action")
|
||||
resource.capability_tag_ids = [&"unknown_resource_tag"]
|
||||
resource.presentation_cue_id = &"unknown.resource.cue"
|
||||
var storage := _storage(&"broken_storage", &"unknown_storage_item_tag", 1)
|
||||
storage.accepted_item_ids = [&"unknown_storage_item"]
|
||||
storage.capability_tag_ids = [&"unknown_storage_tag"]
|
||||
storage.deposit_action_id = &"unknown_deposit_action"
|
||||
storage.presentation_cue_id = &"unknown.storage.cue"
|
||||
pack.items = [item]
|
||||
pack.actions = [action]
|
||||
pack.resources = [resource]
|
||||
pack.storages = [storage]
|
||||
var packs: Array[SimulationContentPack] = [pack]
|
||||
var handlers: Array[StringName] = [&"known_handler"]
|
||||
var cues: Array[StringName] = [&"known.cue"]
|
||||
var errors := ContentCatalog.new().rebuild(packs, handlers, cues)
|
||||
|
||||
for expected in [
|
||||
"unknown tag 'unknown_item_tag'",
|
||||
"unknown presentation cue 'unknown.item.cue'",
|
||||
"unknown tag 'unknown_actor_tag'",
|
||||
"unknown tag 'unknown_target_tag'",
|
||||
"unknown completion handler 'unknown_handler'",
|
||||
"unknown action 'unknown_scheduled_action'",
|
||||
"unknown item 'unknown_effect_item'",
|
||||
"unknown item 'unknown_resource_item'",
|
||||
"unknown action 'unknown_action'",
|
||||
"unknown tag 'unknown_resource_tag'",
|
||||
"unknown item 'unknown_storage_item'",
|
||||
"unknown tag 'unknown_storage_item_tag'",
|
||||
"unknown tag 'unknown_storage_tag'",
|
||||
"unknown action 'unknown_deposit_action'",
|
||||
"unknown presentation cue 'unknown.storage.cue'",
|
||||
]:
|
||||
assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors])
|
||||
|
||||
|
||||
func _complete_minimal_pack(pack_id: StringName) -> SimulationContentPack:
|
||||
var pack := _pack(pack_id)
|
||||
var tag := CapabilityTagDefinition.new()
|
||||
tag.tag_id = &"shared_tag"
|
||||
tag.display_name = "Shared tag"
|
||||
tag.domain = CapabilityTagDefinition.DOMAIN_ITEM
|
||||
var item := _item(&"shared_item", tag.tag_id)
|
||||
var action := _action(&"shared_action")
|
||||
action.target_type = SimulationIds.TARGET_RESOURCE
|
||||
action.resource_action_id = action.action_id
|
||||
var resource := _resource(&"shared_resource", item.item_id, action.action_id)
|
||||
var storage := _storage(&"shared_storage", tag.tag_id, 1)
|
||||
pack.capability_tags = [tag]
|
||||
pack.items = [item]
|
||||
pack.actions = [action]
|
||||
pack.resources = [resource]
|
||||
pack.storages = [storage]
|
||||
return pack
|
||||
|
||||
|
||||
func _pack(pack_id: StringName) -> SimulationContentPack:
|
||||
var pack := SimulationContentPack.new()
|
||||
pack.pack_id = pack_id
|
||||
pack.display_name = String(pack_id).capitalize()
|
||||
return pack
|
||||
|
||||
|
||||
func _item(item_id: StringName, tag_id: StringName) -> ItemDefinition:
|
||||
var item := ItemDefinition.new()
|
||||
item.item_id = item_id
|
||||
item.display_name = String(item_id).capitalize()
|
||||
item.category = ItemDefinition.CATEGORY_RESOURCE
|
||||
item.capability_tag_ids = [tag_id]
|
||||
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 _resource(
|
||||
resource_id: StringName, item_id: StringName, action_id: StringName
|
||||
) -> ResourceDefinition:
|
||||
var resource := ResourceDefinition.new()
|
||||
resource.resource_id = resource_id
|
||||
resource.display_name = String(resource_id).capitalize()
|
||||
resource.item_id = item_id
|
||||
resource.extraction_action_id = action_id
|
||||
return resource
|
||||
|
||||
|
||||
func _storage(
|
||||
storage_id: StringName, accepted_item_tag_id: StringName, priority: int
|
||||
) -> StorageDefinition:
|
||||
var storage := StorageDefinition.new()
|
||||
storage.storage_id = storage_id
|
||||
storage.display_name = String(storage_id).capitalize()
|
||||
storage.accepted_item_tag_ids = [accepted_item_tag_id]
|
||||
storage.routing_priority = priority
|
||||
return storage
|
||||
|
||||
|
||||
func _tag_ids(definitions: Array[CapabilityTagDefinition]) -> Array[StringName]:
|
||||
var ids: Array[StringName] = []
|
||||
for definition in definitions:
|
||||
ids.append(definition.tag_id)
|
||||
return ids
|
||||
|
||||
|
||||
func _resource_ids(definitions: Array[ResourceDefinition]) -> Array[StringName]:
|
||||
var ids: Array[StringName] = []
|
||||
for definition in definitions:
|
||||
ids.append(definition.resource_id)
|
||||
return ids
|
||||
|
||||
|
||||
func _storage_ids(definitions: Array[StorageDefinition]) -> Array[StringName]:
|
||||
var ids: Array[StringName] = []
|
||||
for definition in definitions:
|
||||
ids.append(definition.storage_id)
|
||||
return ids
|
||||
|
||||
|
||||
func _has_error(errors: Array[String], fragment: String) -> bool:
|
||||
for error in errors:
|
||||
if fragment in error:
|
||||
return true
|
||||
return false
|
||||
@@ -0,0 +1 @@
|
||||
uid://deq7osx44nnn3
|
||||
Reference in New Issue
Block a user