161 lines
7.3 KiB
GDScript
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)
|