Files
aetherbound-guild/runtime/tests/p9_8/content_authority_test.gd
T

174 lines
8.0 KiB
GDScript

extends SceneTree
const Content = preload("res://content/p9_8_content.gd")
var failures: Array[String] = []
var rejected_cases := 0
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var first: Dictionary = Content.snapshot()
var second: Dictionary = Content.snapshot()
var validation: Dictionary = Content.validate(first)
expect(validation.ok, "canonical content validates: %s" % str(validation.errors))
expect(first == second, "construction is deterministic")
expect(String(first.content_hash).length() == 64, "content hash is SHA-256")
expect(first.base_professions.size() == 10 and first.promotions.size() == 20, "ten reachable bases have two promotion branches")
expect(first.recruits.size() == 20 and first.equipment.size() == 8 and first.equipment_offers.size() == 25, "five Markets have base and Artifact-extra offers")
expect(first.enemies.size() == 12 and first.routes.size() == 15 and first.boards.size() == 5, "complete Windglass enemy and route authority is present")
expect(first.artifacts.size() == 3 and first.artifact_drafts.size() == 1 and first.bosses.size() == 1, "Artifact draft and Stag authority are exact")
verify_boards(first)
verify_promotions(first)
verify_artifact_and_boss(first)
verify_localization(first)
verify_negative_boundaries(first)
if failures.is_empty():
print("ABG_P9_8_CONTENT_AUTHORITY_OK markets=5 routes=15 recruits=20 equipment=8 equipment_offers=25 enemies=12 promotions=20 artifacts=3 draft=2of3 boss=BO-R1-01 capacities=8,8,9,9,10 rewards=34..56 hashes=deterministic locale_hash_drift=0 negative_cases=%d" % rejected_cases)
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_boards(content: Dictionary) -> void:
var capacities: Array[int] = []
var rewards: Array[int] = []
for market_index in Content.MARKET_INDICES:
var board: Dictionary = content.boards[market_index]
expect(board.recruit_offer_ids.size() == 3 and not String(board.extra_recruit_offer_id).is_empty(), "Market %d has 3+1 Recruit offers" % market_index)
expect(board.equipment_offer_ids.size() == 4 and not String(board.extra_equipment_offer_id).is_empty(), "Market %d has 4+1 equipment offers" % market_index)
expect(board.battle_offer_ids.size() == 3, "Market %d has three route offers" % market_index)
var risks: Array[int] = []
for route_id in board.battle_offer_ids:
var route: Dictionary = content.routes[route_id]
risks.append(int(route.risk))
rewards.append(int(route.reward))
expect(risks == [0, 1, 2], "Market %d routes expose risk 0/1/2" % market_index)
capacities.append(int(board.capacity_after_clear))
expect(capacities == [8, 8, 9, 9, 10], "capacity follows clears 8/10/12")
expect(rewards == [34, 41, 48, 36, 43, 50, 38, 45, 52, 40, 47, 54, 42, 49, 56], "all route rewards follow one formula")
func verify_promotions(content: Dictionary) -> void:
for profession_id in Content.BASE_PROFESSION_IDS:
var branches: Array[String] = []
for promotion_id in content.promotions:
if String(content.promotions[promotion_id].parent_id) == profession_id:
branches.append(String(promotion_id))
expect(branches.size() == 2, "%s has exactly two promotion directions" % profession_id)
func verify_artifact_and_boss(content: Dictionary) -> void:
var draft: Dictionary = content.artifact_drafts["P98-ARTIFACT-DRAFT-1"]
expect(draft.offer_ids == ["AR-001", "AR-002"] and String(draft.unoffered_id) == "AR-007", "saved run's two-of-three Artifact draft is fixed")
expect(int(draft.apply_from_market) == 9, "Artifact changes only the next Market")
var boss: Dictionary = content.bosses["BO-R1-01"]
expect(boss.pane_order == ["projectile", "spell", "forced_movement"], "Stag exposes all three pane families")
expect(int(boss.footprints) == 2 and int(boss.timeout_tick) == 1500, "Stag footprint and timeout are exact")
expect(String(boss.plans[0].id) == "rotate" and String(boss.plans[1].id) == "brace", "Stag has rotate and brace plans")
func verify_localization(content: Dictionary) -> void:
var before_hash := String(content.content_hash)
for family_name in ["recruits", "artifacts", "promotions", "bosses"]:
for row_id in content[family_name]:
var row: Dictionary = content[family_name][row_id]
expect(not Content.localized_name(row, "zh_CN").is_empty(), "%s has zh_CN name" % row_id)
expect(not Content.localized_name(row, "en").is_empty(), "%s has en name" % row_id)
expect(String(content.content_hash) == before_hash, "locale reads do not mutate content")
func verify_negative_boundaries(canonical: Dictionary) -> void:
var missing := canonical.duplicate(true)
missing.erase("bosses")
expect_rejected(missing, "missing family", "missing:bosses")
var bad_parent := canonical.duplicate(true)
bad_parent.promotions["PF-A01"].parent_id = "PF-UNKNOWN"
refresh_content_hash(bad_parent)
expect_rejected(bad_parent, "unknown promotion parent", "promotion_parent:PF-A01")
var missing_branch := canonical.duplicate(true)
missing_branch.promotions.erase("PF-A02")
refresh_content_hash(missing_branch)
expect_rejected(missing_branch, "missing promotion branch", "promotions_count")
var wrong_reward := canonical.duplicate(true)
wrong_reward.routes["P98-R10-Q1"].reward = 999
refresh_content_hash(wrong_reward)
expect_rejected(wrong_reward, "wrong reward", "route_reward:P98-R10-Q1")
var unknown_enemy := canonical.duplicate(true)
unknown_enemy.routes["P98-R12-Q2"].enemy_ids[0] = "EN-UNKNOWN"
refresh_content_hash(unknown_enemy)
expect_rejected(unknown_enemy, "unknown enemy", "route_enemy:P98-R12-Q2:EN-UNKNOWN")
var bad_extra := canonical.duplicate(true)
bad_extra.boards[9].extra_recruit_offer_id = bad_extra.boards[9].recruit_offer_ids[0]
refresh_board_hash(bad_extra, 9)
refresh_content_hash(bad_extra)
expect_rejected(bad_extra, "duplicate extra Recruit", "board_extra_recruit:9")
var bad_capacity := canonical.duplicate(true)
bad_capacity.boards[10].capacity_after_clear = 8
refresh_board_hash(bad_capacity, 10)
refresh_content_hash(bad_capacity)
expect_rejected(bad_capacity, "wrong capacity", "board_capacity:10")
var bad_draft := canonical.duplicate(true)
bad_draft.artifact_drafts["P98-ARTIFACT-DRAFT-1"].offer_ids[1] = "AR-001"
refresh_content_hash(bad_draft)
expect_rejected(bad_draft, "duplicate Artifact offer", "artifact_draft_offers")
var bad_panes := canonical.duplicate(true)
bad_panes.bosses["BO-R1-01"].pane_order = ["projectile", "spell"]
refresh_content_hash(bad_panes)
expect_rejected(bad_panes, "missing boss pane", "boss_panes")
var bad_recruit_hash := canonical.duplicate(true)
bad_recruit_hash.recruits["P98-M8-R1"].name_en = "Changed"
refresh_content_hash(bad_recruit_hash)
expect_rejected(bad_recruit_hash, "Recruit hash drift", "recruit_hash:P98-M8-R1")
var bad_offer_hash := canonical.duplicate(true)
bad_offer_hash.equipment_offers["P98-M8-EQ-005"].list_price = 13
refresh_content_hash(bad_offer_hash)
expect_rejected(bad_offer_hash, "equipment offer hash drift", "equipment_offer_hash:P98-M8-EQ-005")
var bad_board_hash := canonical.duplicate(true)
bad_board_hash.boards[12].market = 11
refresh_content_hash(bad_board_hash)
expect_rejected(bad_board_hash, "board hash drift", "board_hash:12")
func expect_rejected(content: Dictionary, label: String, expected_error: String) -> void:
var result: Dictionary = 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 rejected for %s; got %s" % [label, expected_error, str(result.errors)])
if not result.ok and found:
rejected_cases += 1
func refresh_board_hash(content: Dictionary, market_index: int) -> void:
var board: Dictionary = content.boards[market_index]
board.erase("board_hash")
board.board_hash = JSON.stringify(board).sha256_text()
func refresh_content_hash(content: Dictionary) -> void:
content.erase("content_hash")
content.content_hash = JSON.stringify(content).sha256_text()
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.8 content authority: %s" % message)