Files
aetherbound-guild/runtime/tests/fg4/contract2_rounds_test.gd
T

145 lines
7.2 KiB
GDScript

extends SceneTree
const Battle = preload("res://fg4/contract2_battle_service.gd")
const Contract = preload("res://fg4/contract2_service.gd")
const Fixture = preload("res://tests/fg4/contract1_fixture.gd")
var failures: Array[String] = []
var successful_routes := 0
var counterfactual_routes := 0
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
verify_two_viable_plans_per_route()
verify_complete_twelve_round_flow()
verify_permanent_loss_and_same_board_recovery()
verify_tamper_stale_and_duplicate_boundaries()
if failures.is_empty():
print("ABG_FG4_CONTRACT2_ROUNDS_OK routes=%d counterfactual_routes=%d rounds=12 history=48 plans=4 repeat_events=true rewards=24..60 exact_once=true permanent_loss=true same_board_recovery=true resurrection=0" % [successful_routes, counterfactual_routes])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_two_viable_plans_per_route() -> void:
for market_index in range(1, 13):
for risk in range(3):
var primary := String(Battle.PRIMARY_PLAN[market_index])
var secondary := String(Battle.SECONDARY_PLAN[market_index])
var primary_score := Battle.route_plan_score(market_index, risk, primary, "", 0, 1.0)
var secondary_score := Battle.route_plan_score(market_index, risk, secondary, "", 0, 1.0)
expect(primary_score >= 15 and secondary_score >= 15, "Market %d risk %d has two viable plans" % [market_index, risk])
if primary_score >= 15 and secondary_score >= 15:
counterfactual_routes += 1
func verify_complete_twelve_round_flow() -> void:
var service := Contract.new(Fixture.new().contract2_market_1_snapshot())
expect(service.valid, "complete Contract-2 fixture is valid")
if not service.valid:
return
for market_index in range(1, 13):
var run: Dictionary = service.state.current_run
var route: Dictionary = run.market_board.battle_offers[0]
var coin_before := int(run.coin)
var prepared := service.commit_contract2_route(
"fg4-contract2-round-%02d-prepare" % market_index,
service.state.revision,
String(route.offer_id),
String(run.market_board.board_hash)
)
expect(prepared.ok, "Market %d route prepares" % market_index)
if not prepared.ok:
return
var plan_id := String(Battle.PRIMARY_PLAN[market_index])
var battle := Battle.new(service.state.snapshot()).run_to_terminal(plan_id)
expect(battle.ok and battle.snapshot.outcome == "victory", "Market %d primary response wins" % market_index)
expect(has_event(battle.snapshot.event_log, "repeat_counter_disclosed"), "Market %d emits repeat disclosure" % market_index)
var settled := service.settle_contract2_route(
"fg4-contract2-round-%02d-settle" % market_index,
service.state.revision,
battle.snapshot
)
expect(settled.ok and settled.receipt.reward_coin == route.reward_coin, "Market %d settles exact reward" % market_index)
expect(service.state.current_run.coin == coin_before + route.reward_coin, "Market %d Coin changes once" % market_index)
if settled.ok:
successful_routes += 3
expect(service.state.current_run.phase == "contract2_boss_muster" and service.state.current_run.cleared_rounds == 48, "twelve victories open Foreman Muster")
expect(service.state.current_run.route_history.size() == 48, "history contains prior 36 plus new 12 routes")
func verify_permanent_loss_and_same_board_recovery() -> void:
var service := Contract.new(Fixture.new().contract2_market_1_snapshot())
if not service.valid:
expect(false, "recovery fixture is valid")
return
var board: Dictionary = service.state.current_run.market_board.duplicate(true)
var route: Dictionary = board.battle_offers[2]
var company_before: Array = Array(service.state.current_run.company).duplicate()
var history_before: int = service.state.current_run.route_history.size()
var prepared := service.commit_contract2_route(
"fg4-contract2-loss-prepare",
service.state.revision,
String(route.offer_id),
String(board.board_hash)
)
var battle := Battle.new(service.state.snapshot()).run_to_terminal(first_wrong_plan(1))
expect(prepared.ok and battle.ok and battle.snapshot.outcome == "defeat", "wrong response causes defeat")
expect(battle.snapshot.fallen_recruit_ids.size() == 1, "defeat discloses one permanent casualty")
var fallen_id := String(battle.snapshot.fallen_recruit_ids[0])
var settled := service.settle_contract2_route("fg4-contract2-loss-settle", service.state.revision, battle.snapshot)
expect(settled.ok and service.state.current_run.phase == "contract2_recovery", "defeat opens Contract-2 recovery")
expect(fallen_id not in service.state.current_run.company and service.state.current_run.company.size() == company_before.size() - 1, "Fallen Recruit is permanently removed")
expect(service.state.current_run.route_history.size() == history_before and settled.receipt.reward_coin == 0, "defeat pays no Coin and clears no route")
var battle_hash := String(service.state.current_run.last_battle_report.battle_hash)
var recovery_cost := int(service.state.current_run.recovery_cost)
var recovered := service.recover_contract2_route(
"fg4-contract2-loss-recover",
service.state.revision,
battle_hash,
recovery_cost
)
expect(recovered.ok and service.state.current_run.phase == "contract2_market", "recovery returns to Contract-2 Market")
expect(service.state.current_run.market_board == board, "recovery preserves the exact board")
expect(fallen_id not in service.state.current_run.company, "recovery never resurrects")
func verify_tamper_stale_and_duplicate_boundaries() -> void:
var service := Contract.new(Fixture.new().contract2_market_1_snapshot())
if not service.valid:
return
var route: Dictionary = service.state.current_run.market_board.battle_offers[0]
var revision_before := service.state.revision
var board_hash := String(service.state.current_run.market_board.board_hash)
expect(not service.commit_contract2_route("fg4-contract2-stale", revision_before - 1, String(route.offer_id), board_hash).ok, "stale prepare rejects")
var prepared := service.commit_contract2_route("fg4-contract2-boundary-prepare", revision_before, String(route.offer_id), board_hash)
var duplicate := service.commit_contract2_route("fg4-contract2-boundary-prepare", revision_before, String(route.offer_id), board_hash)
expect(prepared.ok and duplicate.ok and duplicate.duplicate, "duplicate prepare is inert")
var battle := Battle.new(service.state.snapshot()).run_to_terminal(String(Battle.PRIMARY_PLAN[1]))
var tampered: Dictionary = battle.snapshot.duplicate(true)
tampered.reward_coin += 1
expect(not service.settle_contract2_route("fg4-contract2-tampered-settle", service.state.revision, tampered).ok, "tampered battle snapshot rejects")
expect(service.state.current_run.phase == "contract2_prepared", "rejected settlement preserves prepared state")
func first_wrong_plan(market_index: int) -> String:
for plan_id in Battle.PLAN_IDS:
if plan_id not in [Battle.PRIMARY_PLAN[market_index], Battle.SECONDARY_PLAN[market_index]]:
return String(plan_id)
return ""
func has_event(events: Array, kind: String) -> bool:
return events.any(func(event): return String(event.get("kind", "")) == kind)
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("FG-4 Contract-2 rounds: %s" % message)