Files
aetherbound-guild/runtime/tests/p9_9/five_profile_test.gd
T

163 lines
9.3 KiB
GDScript

extends SceneTree
const RunService = preload("res://p9_9/run_service.gd")
const RunState = preload("res://p9_9/run_state.gd")
const BattleService = preload("res://p9_9/battle_service.gd")
const SaveService = preload("res://p9_9/save_service.gd")
const Fixture = preload("res://tests/p9_9/runtime_fixture.gd")
const PROFILES := {
"normal": {"claim": "CH-RG01-CATALOG-01", "draft": ["P99-D1", "P99-D3"], "hires": ["P99-M1-H1", "P99-M1-H2"], "routes": ["P99-R1-Q0", "P99-R2-Q0", "P99-R3-Q0"], "speed": 1, "gap": 2.0},
"active": {"claim": "CH-RG01-THREAT-01", "draft": ["P99-D1", "P99-D3"], "hires": ["P99-M1-H1", "P99-M1-H3"], "routes": ["P99-R1-Q1", "P99-R2-Q1", "P99-R3-Q1"], "speed": 2, "gap": 0.8},
"efficient": {"claim": "CH-RG01-CATALOG-01", "draft": ["P99-D1", "P99-D3"], "hires": ["P99-M1-H1", "P99-M1-H3"], "routes": ["P99-R1-Q2", "P99-R2-Q0", "P99-R3-Q1"], "speed": 2, "gap": 1.0},
"adversarial": {"claim": "CH-RG01-THREAT-01", "draft": ["P99-D2", "P99-D4"], "hires": ["P99-M1-H3", "P99-M1-H4"], "routes": ["P99-R1-Q2", "P99-R2-Q2", "P99-R3-Q2"], "speed": 1, "gap": 3.5},
"returning": {"claim": "CH-RG01-THREAT-01", "draft": ["P99-D1", "P99-D3"], "hires": ["P99-M1-H1", "P99-M1-H2"], "routes": ["P99-R1-Q0", "P99-R2-Q0", "P99-R3-Q2"], "speed": 2, "gap": 1.8},
}
var failures: Array[String] = []
var represented_claims := {}
var represented_pairs := {}
var represented_routes := {}
var timestamp_count := 0
var longest_gap := 0.0
var defeat_recoveries := 0
var interruption_reloads := 0
var invalid_mutations := 0
var save_root := ""
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
save_root = OS.get_environment("ABG_P9_9_PROFILE_SAVE_ROOT")
if save_root.is_empty():
save_root = "/private/tmp/abg-p99-five-profile-default"
DirAccess.make_dir_recursive_absolute(save_root)
for profile_name in ["normal", "active", "efficient", "adversarial", "returning"]:
run_profile(profile_name)
expect(represented_claims.size() == 2, "profiles cover both Charter claims")
expect(represented_pairs.size() >= 2, "profiles cover multiple opening pairs")
expect(represented_routes.size() == 9, "profiles execute all nine routes across counterfactuals")
expect(defeat_recoveries == 1, "exactly one profile defeats and recovers")
expect(interruption_reloads == 1, "returning profile reloads exactly one Encounter")
expect(longest_gap <= 30.0, "no profile waits more than thirty seconds for a decision")
if failures.is_empty():
print("ABG_P9_9_FIVE_PROFILE_OK profiles=normal,active,efficient,adversarial,returning claims=2 opening_pairs=%d routes=9 risks=0,1,2 timestamps=%d longest_gap=%.2f defeat_recoveries=1 interruption_reloads=1 market4=5/5 starvation=0 offline_rewards=0 invalid_mutations=%d" % [represented_pairs.size(), timestamp_count, longest_gap, invalid_mutations])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func run_profile(profile_name: String) -> void:
var profile: Dictionary = PROFILES[profile_name]
represented_claims[String(profile.claim)] = true
represented_pairs["+".join(profile.draft)] = true
var service = RunService.new(Fixture.claimed_profile(String(profile.claim)))
var labels: Array[String] = ["charter_claim"]
var times: Array[float] = [0.0]
for index in profile.draft.size():
append_decision(labels, times, "draft_%d" % index, float(profile.gap))
expect(service.set_provisional("%s-select-%d" % [profile_name, index], String(profile.draft[index]), true).ok, "%s selects draft %d" % [profile_name, index])
append_decision(labels, times, "run_create", float(profile.gap))
expect(service.confirm_run("%s-create" % profile_name).ok, "%s creates run" % profile_name)
for index in profile.hires.size():
append_decision(labels, times, "hire_%d" % index, float(profile.gap))
expect(service.hire("%s-hire-%d" % [profile_name, index], String(profile.hires[index])).ok, "%s hires %d" % [profile_name, index])
var profile_defeats := 0
var profile_interruptions := 0
for round_index in range(1, 4):
var route_id := String(profile.routes[round_index - 1])
if profile_name == "adversarial" and profile_defeats > 0:
route_id = "P99-R%d-Q0" % round_index
represented_routes[route_id] = true
append_decision(labels, times, "round_%d_route" % round_index, float(profile.gap))
expect(service.commit_route("%s-route-%d" % [profile_name, round_index], route_id).ok, "%s Round %d commits" % [profile_name, round_index])
if profile_name == "returning" and round_index == 2:
service = interrupt_and_reload(service, profile_name, route_id, labels, times)
profile_interruptions += 1
var battle_result: Dictionary = run_battle(service)
append_decision(labels, times, "round_%d_%s" % [round_index, battle_result.outcome], float(battle_result.ticks) * 0.08 / float(profile.speed))
expect(service.settle_battle("%s-settle-%d-%d" % [profile_name, round_index, profile_defeats], battle_result.snapshot).ok, "%s Round %d settles" % [profile_name, round_index])
append_decision(labels, times, "round_%d_result" % round_index, float(profile.gap))
if battle_result.outcome == "defeat":
profile_defeats += 1
defeat_recoveries += 1
expect(profile_name == "adversarial" and profile_defeats == 1, "only adversarial may defeat once")
append_decision(labels, times, "round_%d_recover" % round_index, float(profile.gap))
expect(service.recover_defeat("%s-recover-%d" % [profile_name, round_index]).ok, "%s Round %d recovers" % [profile_name, round_index])
var retry_id := "P99-R%d-Q0" % round_index
represented_routes[retry_id] = true
append_decision(labels, times, "round_%d_retry" % round_index, float(profile.gap))
expect(service.commit_route("%s-retry-route-%d" % [profile_name, round_index], retry_id).ok, "%s Round %d retries" % [profile_name, round_index])
var retry: Dictionary = run_battle(service)
append_decision(labels, times, "round_%d_retry_%s" % [round_index, retry.outcome], float(retry.ticks) * 0.08 / float(profile.speed))
expect(retry.outcome == "victory", "%s Round %d lower-risk retry wins" % [profile_name, round_index])
expect(service.settle_battle("%s-retry-settle-%d" % [profile_name, round_index], retry.snapshot).ok, "%s Round %d retry settles" % [profile_name, round_index])
expect(service.state.current_market == 4 and service.state.line_capacity == 5 and service.state.continuation_locked, "%s reaches saved Market 4" % profile_name)
expect(service.state.coin >= 0, "%s never starves below zero" % profile_name)
var before: Dictionary = service.state.snapshot()
var invalid: Dictionary = service.commit_route("%s-invalid-final" % profile_name, "P99-R3-Q0")
expect(not invalid.ok and service.state.snapshot() == before, "%s invalid continuation mutation is zero" % profile_name)
invalid_mutations += 1
var gap := max_gap(times)
longest_gap = maxf(longest_gap, gap)
timestamp_count += times.size()
interruption_reloads += profile_interruptions
expect(profile_defeats == (1 if profile_name == "adversarial" else 0), "%s has declared defeat count" % profile_name)
print("P99_PROFILE_RESULT profile=%s claim=%s pair=%s routes=%s timestamps=%s final_coin=%d settlements=%d max_gap=%.2f" % [profile_name, String(profile.claim), "+".join(profile.draft), ",".join(profile.routes), format_times(labels, times), int(service.state.coin), service.state.battle_settlement_receipts.size(), gap])
func interrupt_and_reload(service, profile_name: String, route_id: String, labels: Array[String], times: Array[float]):
var saves = SaveService.new()
saves.save_path = save_root.path_join("%s.json" % profile_name)
saves.clear()
expect(saves.save_snapshot(service.state.snapshot(), 5000).ok, "returning Encounter saves")
append_decision(labels, times, "round_2_interrupted", 2.5)
var loaded: Dictionary = saves.load_snapshot(5015)
expect(loaded.ok and loaded.battle_interrupted and loaded.offline_coin == 0 and loaded.offline_battle_ticks == 0, "returning load grants no offline progress")
var restored = RunService.new()
expect(restored.restore_snapshot(loaded.snapshot), "returning checkpoint restores")
expect(restored.state.phase == RunState.Phase.MARKET and restored.state.current_market == 2, "returning resumes at Market 2")
expect(restored.commit_route("%s-resume-route-2" % profile_name, route_id).ok, "returning recommits exact route")
interruption_reloads += 0
return restored
func run_battle(service) -> Dictionary:
var battle = BattleService.new(service.state.selected_charter_claim_id)
expect(battle.start_from_market(service.state).ok, "profile battle starts")
var guard := 0
while not battle.state.terminal and guard < 1800:
battle.advance_ticks(1)
guard += 1
expect(guard < 1800 and battle.state.outcome in ["victory", "defeat"], "profile battle terminates")
return {"outcome": battle.state.outcome, "ticks": guard, "snapshot": battle.state.snapshot()}
func append_decision(labels: Array[String], times: Array[float], label: String, delta: float) -> void:
labels.append(label)
times.append(times[-1] + delta)
func max_gap(times: Array[float]) -> float:
var result := 0.0
for index in range(1, times.size()):
result = maxf(result, times[index] - times[index - 1])
return result
func format_times(labels: Array[String], times: Array[float]) -> String:
var result: Array[String] = []
for index in times.size():
result.append("%s@%.2f" % [labels[index], times[index]])
return ",".join(result)
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.9 five profile: %s" % message)