146 lines
8.2 KiB
GDScript
146 lines
8.2 KiB
GDScript
extends SceneTree
|
|
|
|
const MarketService = preload("res://p10_0/market_service.gd")
|
|
const BattleService = preload("res://p10_0/wave_battle_service.gd")
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
verify_fixed_waves_and_determinism()
|
|
verify_risk_and_order_counterfactuals()
|
|
verify_retreat_timing()
|
|
verify_build_counterfactuals()
|
|
if failures.is_empty():
|
|
print("ABG_P10_0_WAVE_BATTLE_OK risks=0,1,2 waves=2,3,4 fixed_tick_ms=100 max_ticks=1500 deterministic=true overlap=true reserve_entry=0 fallen_respawn=0 events=causal retreat_delay=20 counterfactuals=order,profession,trait,item")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func verify_fixed_waves_and_determinism() -> void:
|
|
var service = battle_ready_service(0, false)
|
|
var battle_start: Dictionary = service.state.snapshot()
|
|
var first = BattleService.new(battle_start)
|
|
var second = BattleService.new(battle_start)
|
|
expect(first.valid and second.valid, "committed battle initializes")
|
|
var first_result: Dictionary = first.run_to_terminal()
|
|
var second_result: Dictionary = second.run_to_terminal()
|
|
expect(first_result.ok and first_result.snapshot == second_result.snapshot, "same committed start is byte-deterministic")
|
|
var result: Dictionary = first_result.snapshot
|
|
expect(result.terminal and result.outcome in ["victory", "defeat", "timeout"] and int(result.tick) <= 1500, "battle reaches one bounded terminal")
|
|
expect(int(result.wave_count) == 2 and int(result.next_wave) == 2, "steady offer spawns two fixed waves")
|
|
expect(result.event_log.filter(func(event): return event.kind == "wave_arrived").size() == 2, "each wave emits one arrival event")
|
|
expect(result.event_log.any(func(event): return event.kind == "damage"), "automatic actions emit authoritative damage")
|
|
for reserve_id in result.reserve_ids:
|
|
expect(not result.actors.has(reserve_id), "Reserve %s never enters actor state" % reserve_id)
|
|
for fallen_id in result.fallen_recruit_ids:
|
|
expect(fallen_id not in result.ally_order, "Fallen %s is removed and never respawns" % fallen_id)
|
|
expect(String(result.event_log_hash).length() == 64 and String(result.snapshot_hash).length() == 64, "event and state hashes are complete")
|
|
|
|
|
|
func verify_risk_and_order_counterfactuals() -> void:
|
|
var ticks: Array[int] = []
|
|
var outcomes: Array[String] = []
|
|
for risk in range(3):
|
|
var service = battle_ready_service(risk, false)
|
|
var battle = BattleService.new(service.state.snapshot())
|
|
var result: Dictionary = battle.run_to_terminal().snapshot
|
|
expect(int(result.wave_count) == risk + 2 and result.event_log.filter(func(event): return event.kind == "wave_arrived").size() == risk + 2, "risk %d executes exact wave count" % risk)
|
|
expect(int(result.tick) <= 1500, "risk %d respects 150-second cap" % risk)
|
|
ticks.append(int(result.tick))
|
|
outcomes.append(String(result.outcome))
|
|
|
|
var normal_service = battle_ready_service(1, false)
|
|
var reversed_service = battle_ready_service(1, true)
|
|
var normal: Dictionary = BattleService.new(normal_service.state.snapshot()).run_to_terminal().snapshot
|
|
var reversed: Dictionary = BattleService.new(reversed_service.state.snapshot()).run_to_terminal().snapshot
|
|
expect(normal.ally_order != reversed.ally_order or normal.fallen_recruit_ids != reversed.fallen_recruit_ids or int(normal.tick) != int(reversed.tick), "front-to-rear order changes at least one authoritative result")
|
|
expect(String(normal.snapshot_hash) != String(reversed.snapshot_hash), "order counterfactual has a distinct state hash")
|
|
expect(ticks.size() == 3 and outcomes.size() == 3, "all risk counterfactuals complete")
|
|
|
|
|
|
func verify_retreat_timing() -> void:
|
|
var service = battle_ready_service(2, false)
|
|
var battle = BattleService.new(service.state.snapshot())
|
|
expect(battle.advance_ticks(5).ok and not battle.battle.terminal, "battle remains active before retreat request")
|
|
var requested = battle.begin_retreat(5)
|
|
expect(requested.ok and battle.battle.retreat_completion_tick == 25, "retreat binds a deterministic 20-tick completion")
|
|
expect(not battle.begin_retreat(5).ok, "duplicate retreat request cannot shift completion")
|
|
battle.advance_ticks(20)
|
|
expect(not battle.battle.terminal and battle.battle.tick == 25, "retreat does not complete before its declared tick batch")
|
|
battle.advance_ticks(1)
|
|
expect(battle.battle.terminal and battle.battle.outcome == "retreat", "retreat completes after the exact delayed batch")
|
|
|
|
|
|
func verify_build_counterfactuals() -> void:
|
|
var service = battle_ready_service(1, false)
|
|
var authority: Dictionary = service.state.snapshot()
|
|
var recruit_id := String(authority.current_run.deployed_recruit_ids[0])
|
|
var item_id := String(authority.current_run.equipped_by_recruit[recruit_id])
|
|
|
|
var trait_reach := authority.duplicate(true)
|
|
trait_reach.current_run.recruit_records[recruit_id].trait_id = "TR-001"
|
|
var trait_plain := authority.duplicate(true)
|
|
trait_plain.current_run.recruit_records[recruit_id].trait_id = "TR-002"
|
|
var trait_a: Dictionary = BattleService.new(trait_reach).run_to_terminal().snapshot
|
|
var trait_b: Dictionary = BattleService.new(trait_plain).run_to_terminal().snapshot
|
|
expect(String(trait_a.snapshot_hash) != String(trait_b.snapshot_hash), "mechanical Trait changes the authoritative event/result trace")
|
|
|
|
var damage_item := authority.duplicate(true)
|
|
damage_item.current_run.inventory[item_id].catalog_id = "EQ-027"
|
|
var utility_item := authority.duplicate(true)
|
|
utility_item.current_run.inventory[item_id].catalog_id = "EQ-001"
|
|
var item_a: Dictionary = BattleService.new(damage_item).run_to_terminal().snapshot
|
|
var item_b: Dictionary = BattleService.new(utility_item).run_to_terminal().snapshot
|
|
expect(String(item_a.snapshot_hash) != String(item_b.snapshot_hash), "equipment behavior changes the authoritative event/result trace")
|
|
|
|
var holder := authority.duplicate(true)
|
|
holder.current_run.recruit_records[recruit_id].profession_id = "PF-B01"
|
|
var ranged := authority.duplicate(true)
|
|
ranged.current_run.recruit_records[recruit_id].profession_id = "PF-B05"
|
|
var profession_a: Dictionary = BattleService.new(holder).run_to_terminal().snapshot
|
|
var profession_b: Dictionary = BattleService.new(ranged).run_to_terminal().snapshot
|
|
expect(String(profession_a.snapshot_hash) != String(profession_b.snapshot_hash), "Profession responsibility changes targeting and result")
|
|
|
|
|
|
func battle_ready_service(risk: int, reverse_order: bool):
|
|
var service = created_service()
|
|
var raw: Dictionary = service.state.snapshot()
|
|
raw.current_run.coin = 100
|
|
expect(service.restore_snapshot(raw), "battle fixture Coin state validates")
|
|
for index in range(3):
|
|
var offer: Dictionary = service.state.current_run.market_board.recruit_offers[index]
|
|
expect(service.hire("battle-hire-%d" % index, service.state.revision, String(offer.source_offer_id), int(offer.list_price), false).ok, "battle fixture hires %d" % index)
|
|
var proposal: Array[String] = service.automatic_formation_candidate()
|
|
if reverse_order:
|
|
proposal.reverse()
|
|
expect(service.commit_deployment("battle-deploy", service.state.revision, proposal).ok, "battle fixture deploys four")
|
|
var offer_id := String(service.state.current_run.available_battle_offer_ids[risk])
|
|
expect(service.commit_battle_offer("battle-commit", service.state.revision, offer_id, String(service.state.current_run.market_board.board_hash)).ok, "battle fixture commits risk %d" % risk)
|
|
return service
|
|
|
|
|
|
func created_service():
|
|
var service = MarketService.new()
|
|
expect(service.begin_run_creation("fixture-begin", 0, "CTR-R1-01", "standard").ok, "fixture begin")
|
|
var draft_id := String(service.state.run_creation_draft.draft_id)
|
|
expect(service.materialize_opening_offers("fixture-materialize", service.state.revision, draft_id).ok, "fixture materialize")
|
|
var ids: Array[String] = []
|
|
for offer in service.state.run_creation_draft.opening_offers:
|
|
ids.append(String(offer.source_offer_id))
|
|
expect(service.set_opening_selection("fixture-select", service.state.revision, [ids[0], ids[1]]).ok, "fixture select")
|
|
expect(service.commit_run_creation("fixture-commit", service.state.revision, draft_id, 1, [ids[0], ids[1]], String(service.state.run_creation_draft.opening_offer_set_hash)).ok, "fixture commit")
|
|
return service
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("P10.0 wave battle: %s" % message)
|