174 lines
8.0 KiB
GDScript
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)
|