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

161 lines
7.3 KiB
GDScript

extends SceneTree
const Content = preload("res://content/p9_7_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 byte-equivalent and deterministic")
expect(String(first.content_hash).length() == 64, "content hash is SHA-256")
expect(first.professions.size() == 6, "six new base professions are present")
expect(first.recruits.size() == 9, "nine deterministic Recruit records are present")
expect(first.equipment.size() == 4 and first.equipment_offers.size() == 12, "four item rows produce twelve board offers")
expect(first.enemies.size() == 6 and first.routes.size() == 9 and first.boards.size() == 3, "enemy, route and board counts are exact")
verify_boards(first)
verify_localization_is_presentation_only(first)
verify_negative_boundaries(first)
if failures.is_empty():
print("ABG_P9_7_CONTENT_AUTHORITY_OK professions=6 recruits=9 traits=8 equipment=4 equipment_offers=12 enemies=6 routes=9 boards=3 board_shape=3,4,3 risks=0,1,2 rewards=28,35,42,30,37,44,32,39,46 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 expected_recruits := {
5: ["PF-B02", "PF-B03", "PF-B06"],
6: ["PF-B08", "PF-B09", "PF-B11"],
7: ["PF-B02", "PF-B09", "PF-B07"],
}
var expected_rewards := [28, 35, 42, 30, 37, 44, 32, 39, 46]
var actual_rewards: Array[int] = []
for market_index in [5, 6, 7]:
var board: Dictionary = content.boards[market_index]
expect(board.recruit_offer_ids.size() == 3, "Market %d has three Recruit offers" % market_index)
expect(board.equipment_offer_ids.size() == 4, "Market %d has four equipment offers" % market_index)
expect(board.battle_offer_ids.size() == 3, "Market %d has three battle offers" % market_index)
var professions: Array[String] = []
var risks: Array[int] = []
for recruit_id in board.recruit_offer_ids:
var recruit: Dictionary = content.recruits[recruit_id]
professions.append(String(recruit.profession_id))
expect(recruit.starting_equipment is Dictionary and not String(recruit.starting_equipment.instance_id).is_empty(), "%s has one equipment instance" % recruit_id)
expect(int(recruit.list_price) == 18 and int(recruit.level) == 2, "%s has exact level and list price" % recruit_id)
for route_id in board.battle_offer_ids:
var route: Dictionary = content.routes[route_id]
risks.append(int(route.risk))
actual_rewards.append(int(route.reward))
expect(professions == expected_recruits[market_index], "Market %d Recruit professions are fixed" % market_index)
expect(risks == [0, 1, 2], "Market %d exposes steady, pressured and elite" % market_index)
expect(actual_rewards == expected_rewards, "all nine rewards follow the authority formula")
func verify_localization_is_presentation_only(content: Dictionary) -> void:
var before_hash := String(content.content_hash)
for recruit_id in content.recruits:
var recruit: Dictionary = content.recruits[recruit_id]
expect(not Content.localized_name(recruit, "zh_CN").is_empty(), "%s has zh_CN name" % recruit_id)
expect(not Content.localized_name(recruit, "en").is_empty(), "%s has en name" % recruit_id)
expect(String(content.content_hash) == before_hash, "locale reads cannot mutate content hash")
func verify_negative_boundaries(canonical: Dictionary) -> void:
var missing := canonical.duplicate(true)
missing.erase("boards")
expect_rejected(missing, "missing top-level family", "missing:boards")
var bad_profession := canonical.duplicate(true)
bad_profession.recruits["P97-M5-R1"].profession_id = "PF-UNKNOWN"
refresh_recruit_hash(bad_profession, "P97-M5-R1")
refresh_content_hash(bad_profession)
expect_rejected(bad_profession, "unknown Recruit profession", "recruit_profession:P97-M5-R1")
var missing_item := canonical.duplicate(true)
missing_item.recruits["P97-M5-R2"].starting_equipment.catalog_id = "EQ-999"
refresh_recruit_hash(missing_item, "P97-M5-R2")
refresh_content_hash(missing_item)
expect_rejected(missing_item, "unknown starting equipment", "recruit_equipment:P97-M5-R2")
var wrong_reward := canonical.duplicate(true)
wrong_reward.routes["P97-R5-Q1"].reward = 999
refresh_content_hash(wrong_reward)
expect_rejected(wrong_reward, "wrong battle reward", "route_reward:P97-R5-Q1")
var unknown_enemy := canonical.duplicate(true)
unknown_enemy.routes["P97-R6-Q2"].enemy_ids[0] = "EN-UNKNOWN"
refresh_content_hash(unknown_enemy)
expect_rejected(unknown_enemy, "unknown route enemy", "route_enemy:P97-R6-Q2:EN-UNKNOWN")
var duplicate_board_ref := canonical.duplicate(true)
duplicate_board_ref.boards[5].recruit_offer_ids[1] = duplicate_board_ref.boards[5].recruit_offer_ids[0]
refresh_board_hash(duplicate_board_ref, 5)
refresh_content_hash(duplicate_board_ref)
expect_rejected(duplicate_board_ref, "duplicate board Recruit", "board_reference:recruit_offer_ids")
var hash_drift := canonical.duplicate(true)
hash_drift.recruits["P97-M7-R3"].name_en = "Changed After Hash"
refresh_content_hash(hash_drift)
expect_rejected(hash_drift, "Recruit canonical hash drift", "recruit_hash:P97-M7-R3")
var wrong_price := canonical.duplicate(true)
wrong_price.equipment["EQ-021"].list_price = 13
refresh_content_hash(wrong_price)
expect_rejected(wrong_price, "equipment price drift", "equipment_price:EQ-021")
var bad_seed := canonical.duplicate(true)
bad_seed.recruits["P97-M6-R1"].generation_seed = "not-a-seed"
refresh_recruit_hash(bad_seed, "P97-M6-R1")
refresh_content_hash(bad_seed)
expect_rejected(bad_seed, "invalid generation seed", "recruit_seed:P97-M6-R1")
var wrong_market := canonical.duplicate(true)
wrong_market.recruits["P97-M7-R1"].market = 6
refresh_recruit_hash(wrong_market, "P97-M7-R1")
refresh_content_hash(wrong_market)
expect_rejected(wrong_market, "Recruit assigned to wrong Market", "board_recruit_market:7:P97-M7-R1")
func expect_rejected(content: Dictionary, label: String, expected_error: String) -> void:
var result: Dictionary = Content.validate(content)
var expected_found := false
for error in result.errors:
if String(error).begins_with(expected_error):
expected_found = true
expect(not result.ok and expected_found, "%s is rejected for %s; got %s" % [label, expected_error, str(result.errors)])
if not result.ok and expected_found:
rejected_cases += 1
func refresh_recruit_hash(content: Dictionary, recruit_id: String) -> void:
var recruit: Dictionary = content.recruits[recruit_id]
recruit.erase("canonical_output_hash")
recruit.canonical_output_hash = JSON.stringify(recruit).sha256_text()
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.7 content authority: %s" % message)