187 lines
9.4 KiB
GDScript
187 lines
9.4 KiB
GDScript
extends SceneTree
|
|
|
|
const Content = preload("res://fg3/content_registry.gd")
|
|
|
|
var failures: Array[String] = []
|
|
var rejected_cases := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
var first := Content.snapshot()
|
|
var second := Content.snapshot()
|
|
var validation := Content.validate(first)
|
|
expect(validation.ok, "canonical registry validates: %s" % str(validation.errors))
|
|
expect(first == second, "catalog loading and enrichment are byte-identical")
|
|
expect(String(first.catalog_hash).length() == 64, "catalog hash is canonical")
|
|
verify_counts_and_identity(first)
|
|
verify_route_and_market_authority(first)
|
|
verify_contract2_authority(first)
|
|
verify_negative_boundaries(first)
|
|
if failures.is_empty():
|
|
print("ABG_FG3_CONTENT_GRAPH_OK new_equipment=16 complete_equipment=40 new_artifacts=4 complete_artifacts=7 enemies=12 bosses=2 contract1_routes=15 contract2_routes=36 markets=5+12 localized=76 source=7_exact negative_cases=%d" % rejected_cases)
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func verify_counts_and_identity(content: Dictionary) -> void:
|
|
expect(content.region.id == "RG-02", "Region identity is exact")
|
|
expect(
|
|
content.contract.id == "CTR-R2-01"
|
|
and content.contract.slice_start_market == 8
|
|
and content.contract.slice_end_market == 13,
|
|
"Contract-1 ending boundary is exact"
|
|
)
|
|
expect(content.contract.boss_executable, "Contract 1 Boss execution is enabled in Step 2")
|
|
expect(content.contract2.id == "CTR-R2-02" and content.contract2.boss_id == "BO-R2-02" and not content.contract2.boss_executable, "Contract 2 boundary is exact and Boss execution stays frozen")
|
|
expect(content.equipment.size() == 16, "EQ-065..080 are exposed")
|
|
expect(content.artifacts.size() == 4, "four missing Artifact rows are exposed")
|
|
expect(content.bosses.size() == 2, "both Siltwake Boss identities are exposed")
|
|
expect(content.routes.size() == 15 and content.markets.size() == 5, "15 routes and five Markets are exposed")
|
|
expect(content.inherited.equipment_ids.size() + content.equipment.size() == 40, "complete Region equipment count is 40")
|
|
expect(content.inherited.enemy_ids.size() == 12, "complete Region enemy count is 12")
|
|
expect(content.inherited.artifact_ids.size() + content.artifacts.size() == 7, "complete Region Artifact count is seven")
|
|
expect(content.source == Content.SOURCE_AUTHORITY, "seven source blobs are exact")
|
|
|
|
var ids := {}
|
|
for singleton in [content.region, content.contract, content.contract2]:
|
|
verify_row(singleton, ids)
|
|
for family in [content.equipment, content.artifacts, content.bosses, content.routes, content.contract2_routes]:
|
|
for row in family:
|
|
verify_row(row, ids)
|
|
expect(ids.size() == 76, "all 76 bounded identities are unique and localized")
|
|
|
|
|
|
func verify_route_and_market_authority(content: Dictionary) -> void:
|
|
var routes := Content.rows_by_id(content, "routes")
|
|
var markets := Content.markets_by_index(content)
|
|
var expected_capacities := {8: 7, 9: 8, 10: 8, 11: 9, 12: 9}
|
|
var expected_levels := {8: 3, 9: 3, 10: 4, 11: 4, 12: 4}
|
|
var enemy_usage := {}
|
|
for enemy_id in content.inherited.enemy_ids:
|
|
enemy_usage[String(enemy_id)] = 0
|
|
for market_index in [8, 9, 10, 11, 12]:
|
|
var market: Dictionary = markets[market_index]
|
|
expect(market.round_index == market_index, "Market %d binds the same Round" % market_index)
|
|
expect(market.cleared_rounds_before == market_index - 1, "Market %d stores clear lineage" % market_index)
|
|
expect(market.line_capacity == expected_capacities[market_index], "Market %d capacity is exact" % market_index)
|
|
expect(market.offer_level == expected_levels[market_index], "Market %d offer level follows the formula" % market_index)
|
|
expect(market.route_ids.size() == 3 and market.required_equipment_ids.size() == 2, "Market %d has exact board authority" % market_index)
|
|
for risk in range(3):
|
|
var route: Dictionary = routes[market.route_ids[risk]]
|
|
expect(route.market_index == market_index and route.risk == risk, "Market %d risk %d identity is exact" % [market_index, risk])
|
|
expect(route.reward_coin == 20 + 2 * market_index + 7 * risk, "Market %d risk %d reward is exact" % [market_index, risk])
|
|
expect(route.wave_order.map(func(wave): return wave.enemy_id) == route.enemy_ids, "Market %d risk %d wave order is exact" % [market_index, risk])
|
|
for enemy_id in route.enemy_ids:
|
|
enemy_usage[String(enemy_id)] += 1
|
|
for enemy_id in enemy_usage:
|
|
expect(enemy_usage[enemy_id] >= 2, "%s appears in at least two distinct route decisions" % enemy_id)
|
|
expect(markets[12].milestone_disclosure == "boss_preparation", "Market 12 discloses Boss preparation")
|
|
|
|
|
|
func verify_contract2_authority(content: Dictionary) -> void:
|
|
var routes := Content.rows_by_id(content, "contract2_routes")
|
|
expect(routes.size() == 36 and content.contract2_markets.size() == 12, "Contract 2 has 36 routes and twelve Markets")
|
|
var enemy_usage := {}
|
|
for enemy_id in content.inherited.enemy_ids:
|
|
enemy_usage[String(enemy_id)] = 0
|
|
for market_index in range(1, 13):
|
|
var market: Dictionary = content.contract2_markets[market_index - 1]
|
|
expect(market.market_index == market_index and market.round_index == market_index, "Contract-2 Market %d identity is exact" % market_index)
|
|
expect(market.line_capacity == 10 and market.offer_level == 1 + int(floor(float(market_index - 1) / 3.0)), "Contract-2 Market %d capacity and offer level are exact" % market_index)
|
|
for risk in range(3):
|
|
var route_id := "RO-R2-C02-M%02d-Q%d" % [market_index, risk]
|
|
var route: Dictionary = routes[route_id]
|
|
expect(route.reward_coin == 20 + 2 * market_index + 7 * risk, "%s reward formula is exact" % route_id)
|
|
expect(route.wave_order.map(func(wave): return wave.enemy_id) == route.enemy_ids, "%s wave order is exact" % route_id)
|
|
for enemy_id in route.enemy_ids:
|
|
enemy_usage[String(enemy_id)] += 1
|
|
for enemy_id in enemy_usage:
|
|
expect(enemy_usage[enemy_id] >= 2, "%s remains exercised in Contract 2" % enemy_id)
|
|
expect(content.contract2_markets[-1].first_order_changer_id == "BO-R2-02", "Market 12 discloses Drowned Bell as first-order changer")
|
|
|
|
|
|
func verify_row(row: Dictionary, ids: Dictionary) -> void:
|
|
expect(not ids.has(row.id), "%s is globally unique" % row.id)
|
|
ids[row.id] = true
|
|
expect(not String(row.name_en).is_empty() and not String(row.name_zh).is_empty(), "%s has bilingual names" % row.id)
|
|
expect(not String(row.preview).is_empty() and row.response_tags.size() >= 3, "%s has preview and response tags" % row.id)
|
|
expect(String(row.execution_key).begins_with("execute.") and String(row.mechanic_signature).length() == 64, "%s has dispatch and signature" % row.id)
|
|
|
|
|
|
func verify_negative_boundaries(canonical: Dictionary) -> void:
|
|
var source_drift := canonical.duplicate(true)
|
|
source_drift.source.economy_blob = "0".repeat(40)
|
|
expect_rejected(source_drift, "source drift", "source:economy_blob")
|
|
|
|
var missing_item := canonical.duplicate(true)
|
|
missing_item.equipment.pop_back()
|
|
expect_rejected(missing_item, "missing item", "family_ids:equipment")
|
|
|
|
var duplicate_route := canonical.duplicate(true)
|
|
duplicate_route.routes[1].id = "RO-R2-M08-Q0"
|
|
expect_rejected(duplicate_route, "duplicate route", "duplicate_global_id:RO-R2-M08-Q0")
|
|
|
|
var unknown_enemy := canonical.duplicate(true)
|
|
unknown_enemy.routes[0].enemy_ids[0] = "EN-R2-99"
|
|
unknown_enemy.routes[0].wave_order[0].enemy_id = "EN-R2-99"
|
|
expect_rejected(unknown_enemy, "unknown enemy", "route_enemy:RO-R2-M08-Q0")
|
|
|
|
var reward_drift := canonical.duplicate(true)
|
|
reward_drift.routes[7].reward_coin += 1
|
|
expect_rejected(reward_drift, "reward drift", "route_reward:RO-R2-M10-Q1")
|
|
|
|
var disabled_first_boss := canonical.duplicate(true)
|
|
disabled_first_boss.bosses[0].executable = false
|
|
expect_rejected(disabled_first_boss, "first Boss disabled", "boss_execution_boundary:BO-R2-01")
|
|
|
|
var executable_second_boss := canonical.duplicate(true)
|
|
executable_second_boss.bosses[1].executable = true
|
|
expect_rejected(executable_second_boss, "second Boss execution leak", "boss_execution_boundary:BO-R2-02")
|
|
|
|
var missing_contract2_route := canonical.duplicate(true)
|
|
missing_contract2_route.contract2_routes.pop_back()
|
|
expect_rejected(missing_contract2_route, "missing Contract-2 route", "contract2_route_count")
|
|
|
|
var artifact_conflict := canonical.duplicate(true)
|
|
artifact_conflict.artifacts[0].conflict_id = "AR-013"
|
|
expect_rejected(artifact_conflict, "Artifact conflict", "artifact_conflict:AR-009")
|
|
|
|
var capacity_drift := canonical.duplicate(true)
|
|
capacity_drift.markets[4].line_capacity = 10
|
|
expect_rejected(capacity_drift, "capacity drift", "market_capacity:12")
|
|
|
|
var level_drift := canonical.duplicate(true)
|
|
level_drift.markets[2].offer_level = 3
|
|
expect_rejected(level_drift, "offer-level drift", "market_offer_level:10")
|
|
|
|
var item_reference := canonical.duplicate(true)
|
|
item_reference.markets[0].required_equipment_ids[0] = "EQ-999"
|
|
expect_rejected(item_reference, "unknown required item", "market_equipment:8:EQ-999")
|
|
|
|
var filler := canonical.duplicate(true)
|
|
filler.bosses[1].rule = "TODO placeholder"
|
|
expect_rejected(filler, "filler Boss", "filler:BO-R2-02:todo")
|
|
|
|
|
|
func expect_rejected(content: Dictionary, label: String, expected_error: String) -> void:
|
|
var result := Content.validate(content)
|
|
var found := false
|
|
for error in result.errors:
|
|
if String(error).begins_with(expected_error):
|
|
found = true
|
|
expect(not result.ok and found, "%s rejects with %s; got %s" % [label, expected_error, str(result.errors)])
|
|
if not result.ok and found:
|
|
rejected_cases += 1
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-3 content registry: %s" % message)
|