563 lines
26 KiB
GDScript
563 lines
26 KiB
GDScript
extends SceneTree
|
|
|
|
const Adapter = preload("res://campaign/fg4_adapter.gd")
|
|
const Contract1Battle = preload("res://fg4/contract1_battle_service.gd")
|
|
const Contract2Battle = preload("res://fg4/contract2_battle_service.gd")
|
|
const FG3Battle = preload("res://fg3/contract2_battle_service.gd")
|
|
const FG3Execution = preload("res://fg3/contract2_execution_service.gd")
|
|
const FG3Fixture = preload("res://tests/fg3/contract1_fixture.gd")
|
|
const SaveV10 = preload("res://fg3/save_v10_service.gd")
|
|
const SaveV11 = preload("res://fg4/save_v11_service.gd")
|
|
|
|
const PROFILES := [
|
|
{
|
|
"id": "normal",
|
|
"artifact": "AR-011",
|
|
"contract1_risks": [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
|
|
"contract2_risks": [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
|
|
"boss1_plan": "vent_and_brace",
|
|
"boss2_plan": "vary_tags",
|
|
"decision_seconds": 6.0,
|
|
},
|
|
{
|
|
"id": "active",
|
|
"artifact": "AR-012",
|
|
"contract1_risks": [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
"contract2_risks": [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
"boss1_plan": "strong_side_burst",
|
|
"boss2_plan": "safe_promotion",
|
|
"decision_seconds": 3.0,
|
|
},
|
|
{
|
|
"id": "efficient",
|
|
"artifact": "AR-015",
|
|
"contract1_risks": [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
|
|
"contract2_risks": [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
|
|
"boss1_plan": "vent_and_brace",
|
|
"boss2_plan": "safe_promotion",
|
|
"decision_seconds": 2.0,
|
|
},
|
|
{
|
|
"id": "adversarial",
|
|
"artifact": "AR-011",
|
|
"contract1_risks": [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
"contract2_risks": [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
"boss1_plan": "vent_and_brace",
|
|
"boss2_plan": "vary_tags",
|
|
"decision_seconds": 8.0,
|
|
},
|
|
{
|
|
"id": "returning",
|
|
"artifact": "AR-015",
|
|
"contract1_risks": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
"contract2_risks": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
"boss1_plan": "vent_and_brace",
|
|
"boss2_plan": "safe_promotion",
|
|
"decision_seconds": 7.0,
|
|
},
|
|
]
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
var results: Array[Dictionary] = []
|
|
for profile in PROFILES:
|
|
results.append(simulate_profile(profile))
|
|
verify_results(results)
|
|
if failures.is_empty():
|
|
var total_decisions := 0
|
|
var longest_gap := 0.0
|
|
var recoveries := 0
|
|
var reloads := 0
|
|
var secondary_choices := 0
|
|
for result in results:
|
|
total_decisions += int(result.decision_count)
|
|
longest_gap = maxf(longest_gap, float(result.max_gap))
|
|
recoveries += int(result.recoveries)
|
|
reloads += int(result.reloads)
|
|
secondary_choices += int(result.secondary_choices)
|
|
print("ABG_FG4_FIVE_PROFILE_OK profiles=normal,active,efficient,adversarial,returning core_decisions=%d per_profile=53 longest_gap=%.1f routes=24 bosses=2 records=4 rg03_records=2 recoveries=%d reloads=%d policy_signatures=5 artifacts=3 route_diversity=24 boss_plan_diversity=2 secondary_choices=%d pressure_families=6 offline_rewards=0 resurrection=0 terminal=RG-04" % [total_decisions, longest_gap, recoveries, reloads, secondary_choices])
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func simulate_profile(profile: Dictionary) -> Dictionary:
|
|
var adapter := Adapter.new()
|
|
var configured := adapter.configure(starting_snapshot(String(profile.artifact)))
|
|
if not bool(configured.get("ok", false)):
|
|
return failed_result(String(profile.id), "configure")
|
|
var labels: Array[String] = []
|
|
var timestamps: Array[float] = []
|
|
var recovery_labels: Array[String] = []
|
|
var recovery_timestamps: Array[float] = []
|
|
var reload_timestamps: Array[float] = []
|
|
var route_risks: Array[int] = []
|
|
var route_ids: Array[String] = []
|
|
var plan_ids: Array[String] = []
|
|
var pressure_families := {}
|
|
var contract1_secondary_choices := 0
|
|
var contract2_secondary_choices := 0
|
|
var clock := 0.0
|
|
var automatic_seconds := 0.0
|
|
var recoveries := 0
|
|
var reloads := 0
|
|
var fallen_ids: Array[String] = []
|
|
var offline_zero := true
|
|
var decision_seconds := float(profile.decision_seconds)
|
|
|
|
for offset in range(12):
|
|
var market_index := 1 + offset
|
|
var risk := int(profile.contract1_risks[offset])
|
|
var route := route_for_risk(adapter.view_state().routes, risk)
|
|
if route.is_empty():
|
|
return failed_result(String(profile.id), "contract1_route_%d" % market_index)
|
|
clock = record_decision(labels, timestamps, clock, "c1_m%d_route_r%d" % [market_index, risk], decision_seconds)
|
|
route_risks.append(risk)
|
|
route_ids.append(String(route.route_id))
|
|
var prepared := adapter.prepare_route(String(route.offer_id))
|
|
if not bool(prepared.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract1_prepare_%d" % market_index)
|
|
if String(profile.id) == "active" and market_index == 12:
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c1_low_hp_wrong_plan", decision_seconds)
|
|
var wrong_started := adapter.start_plan("cooldown_bait")
|
|
if not bool(wrong_started.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract1_recovery_wrong_start")
|
|
var wrong_terminal := adapter.run_battle_to_terminal()
|
|
if not bool(wrong_terminal.get("ok", false)) or String(wrong_terminal.view.battle.outcome) != "defeat":
|
|
return failed_result(String(profile.id), "contract1_recovery_defeat")
|
|
var wrong_seconds := battle_seconds(wrong_terminal)
|
|
automatic_seconds += wrong_seconds
|
|
clock += wrong_seconds + 1.0
|
|
var failed_settlement := adapter.settle_terminal()
|
|
if not bool(failed_settlement.get("ok", false)) or String(failed_settlement.view.mode) != "recovery":
|
|
return failed_result(String(profile.id), "contract1_recovery_settle")
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c1_accept_permanent_loss", decision_seconds)
|
|
var recovered := adapter.recover()
|
|
if not bool(recovered.get("ok", false)) or String(recovered.view.mode) != "market":
|
|
return failed_result(String(profile.id), "contract1_recovery_commit")
|
|
recoveries += 1
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c1_retry_same_route", decision_seconds)
|
|
route = route_for_risk(adapter.view_state().routes, risk)
|
|
prepared = adapter.prepare_route(String(route.offer_id))
|
|
if not bool(prepared.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract1_recovery_retry")
|
|
var prefer_secondary := String(profile.id) in ["active", "efficient"] and market_index % 3 == 0
|
|
var plan_id := choose_route_plan(Contract1Battle, adapter.snapshot(), market_index, prefer_secondary)
|
|
if plan_id == String(Contract1Battle.SECONDARY_PLAN[market_index]):
|
|
contract1_secondary_choices += 1
|
|
clock = record_decision(labels, timestamps, clock, "c1_m%d_plan_%s" % [market_index, plan_id], decision_seconds)
|
|
plan_ids.append(plan_id)
|
|
var terminal := run_victory(adapter, plan_id)
|
|
if not bool(terminal.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract1_battle_%d" % market_index)
|
|
capture_pressure_families(terminal, pressure_families)
|
|
var duration := battle_seconds(terminal)
|
|
automatic_seconds += duration
|
|
clock += duration + 1.0
|
|
if not bool(adapter.settle_terminal().get("ok", false)):
|
|
return failed_result(String(profile.id), "contract1_settle_%d" % market_index)
|
|
if String(profile.id) == "returning" and market_index in [4, 8, 12]:
|
|
var reloaded := reload_adapter(adapter, 210 + reloads)
|
|
if not bool(reloaded.get("ok", false)):
|
|
return failed_result(String(profile.id), "return_market10")
|
|
adapter = reloaded.adapter
|
|
offline_zero = offline_zero and bool(reloaded.offline_zero)
|
|
reloads += 1
|
|
clock += 1.5
|
|
reload_timestamps.append(clock)
|
|
|
|
clock = record_decision(labels, timestamps, clock, "boss1_plan_%s" % String(profile.boss1_plan), decision_seconds)
|
|
var boss1 := run_victory(adapter, String(profile.boss1_plan))
|
|
if not bool(boss1.get("ok", false)):
|
|
return failed_result(String(profile.id), "boss1")
|
|
capture_pressure_families(boss1, pressure_families)
|
|
var boss1_seconds := battle_seconds(boss1)
|
|
automatic_seconds += boss1_seconds
|
|
clock += boss1_seconds + 1.0
|
|
var boss1_settled := adapter.settle_terminal()
|
|
if not bool(boss1_settled.get("ok", false)) or String(boss1_settled.view.mode) != "contract_record":
|
|
return failed_result(String(profile.id), "boss1_settle")
|
|
clock = record_decision(labels, timestamps, clock, "record1_commit", decision_seconds)
|
|
var first_record := adapter.continue_result()
|
|
if not bool(first_record.get("ok", false)) or String(first_record.view.mode) != "contract_handoff":
|
|
return failed_result(String(profile.id), "record1")
|
|
clock = record_decision(labels, timestamps, clock, "contract2_handoff", decision_seconds)
|
|
var handoff := adapter.continue_result()
|
|
if not bool(handoff.get("ok", false)) or String(handoff.view.contract_id) != "CTR-R3-02":
|
|
return failed_result(String(profile.id), "handoff")
|
|
if String(profile.id) == "returning":
|
|
var handoff_reload := reload_adapter(adapter, 210 + reloads)
|
|
if not bool(handoff_reload.get("ok", false)):
|
|
return failed_result(String(profile.id), "return_handoff")
|
|
adapter = handoff_reload.adapter
|
|
offline_zero = offline_zero and bool(handoff_reload.offline_zero)
|
|
reloads += 1
|
|
clock += 1.5
|
|
reload_timestamps.append(clock)
|
|
|
|
for offset in range(12):
|
|
var market_index := offset + 1
|
|
var risk := int(profile.contract2_risks[offset])
|
|
var route := route_for_risk(adapter.view_state().routes, risk)
|
|
if route.is_empty():
|
|
return failed_result(String(profile.id), "contract2_route_%d" % market_index)
|
|
clock = record_decision(labels, timestamps, clock, "c2_m%d_route_r%d" % [market_index, risk], decision_seconds)
|
|
route_risks.append(risk)
|
|
route_ids.append(String(route.route_id))
|
|
var prepared := adapter.prepare_route(String(route.offer_id))
|
|
if not bool(prepared.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract2_prepare_%d" % market_index)
|
|
|
|
if String(profile.id) == "adversarial" and market_index == 1:
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "wrong_plan_focus_foreman", decision_seconds)
|
|
var wrong_started := adapter.start_plan("focus_foreman")
|
|
if not bool(wrong_started.get("ok", false)):
|
|
return failed_result(String(profile.id), "adversarial_wrong_start")
|
|
var wrong_terminal := adapter.run_battle_to_terminal()
|
|
if not bool(wrong_terminal.get("ok", false)) or String(wrong_terminal.view.battle.outcome) != "defeat":
|
|
return failed_result(String(profile.id), "adversarial_defeat")
|
|
var wrong_seconds := battle_seconds(wrong_terminal)
|
|
automatic_seconds += wrong_seconds
|
|
clock += wrong_seconds + 1.0
|
|
fallen_ids.assign(wrong_terminal.view.battle.fallen_recruit_ids)
|
|
var failed_settlement := adapter.settle_terminal()
|
|
if not bool(failed_settlement.get("ok", false)) or String(failed_settlement.view.mode) != "recovery":
|
|
return failed_result(String(profile.id), "adversarial_settle")
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "accept_permanent_loss", decision_seconds)
|
|
var recovered := adapter.recover()
|
|
if not bool(recovered.get("ok", false)) or String(recovered.view.mode) != "market":
|
|
return failed_result(String(profile.id), "adversarial_recover")
|
|
recoveries += 1
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "retry_same_disclosed_route", decision_seconds)
|
|
route = route_for_risk(adapter.view_state().routes, risk)
|
|
prepared = adapter.prepare_route(String(route.offer_id))
|
|
if not bool(prepared.get("ok", false)):
|
|
return failed_result(String(profile.id), "adversarial_retry_prepare")
|
|
elif String(profile.id) in ["active", "efficient", "returning"] and market_index == 12:
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c2_low_hp_wrong_plan", decision_seconds)
|
|
var low_started := adapter.start_plan("focus_foreman")
|
|
if not bool(low_started.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract2_recovery_wrong_start")
|
|
var low_terminal := adapter.run_battle_to_terminal()
|
|
if not bool(low_terminal.get("ok", false)) or String(low_terminal.view.battle.outcome) != "defeat":
|
|
return failed_result(String(profile.id), "contract2_recovery_defeat")
|
|
var low_seconds := battle_seconds(low_terminal)
|
|
automatic_seconds += low_seconds
|
|
clock += low_seconds + 1.0
|
|
var low_settlement := adapter.settle_terminal()
|
|
if not bool(low_settlement.get("ok", false)) or String(low_settlement.view.mode) != "recovery":
|
|
return failed_result(String(profile.id), "contract2_recovery_settle")
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c2_accept_permanent_loss", decision_seconds)
|
|
var low_recovered := adapter.recover()
|
|
if not bool(low_recovered.get("ok", false)) or String(low_recovered.view.mode) != "market":
|
|
return failed_result(String(profile.id), "contract2_recovery_commit")
|
|
recoveries += 1
|
|
clock = record_recovery(recovery_labels, recovery_timestamps, clock, "c2_retry_same_route", decision_seconds)
|
|
route = route_for_risk(adapter.view_state().routes, risk)
|
|
prepared = adapter.prepare_route(String(route.offer_id))
|
|
if not bool(prepared.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract2_recovery_retry")
|
|
|
|
var prefer_secondary := String(profile.id) in ["efficient", "returning"] and market_index % 3 == 1
|
|
var plan_id := choose_route_plan(Contract2Battle, adapter.snapshot(), market_index, prefer_secondary)
|
|
if plan_id == String(Contract2Battle.SECONDARY_PLAN[market_index]):
|
|
contract2_secondary_choices += 1
|
|
clock = record_decision(labels, timestamps, clock, "c2_m%d_plan_%s" % [market_index, plan_id], decision_seconds)
|
|
plan_ids.append(plan_id)
|
|
var terminal := run_victory(adapter, plan_id)
|
|
if not bool(terminal.get("ok", false)):
|
|
return failed_result(String(profile.id), "contract2_battle_%d" % market_index)
|
|
capture_pressure_families(terminal, pressure_families)
|
|
var duration := battle_seconds(terminal)
|
|
automatic_seconds += duration
|
|
clock += duration + 1.0
|
|
if not bool(adapter.settle_terminal().get("ok", false)):
|
|
return failed_result(String(profile.id), "contract2_settle_%d" % market_index)
|
|
if String(profile.id) == "returning" and market_index in [3, 6, 9, 12]:
|
|
var reloaded := reload_adapter(adapter, 210 + reloads)
|
|
if not bool(reloaded.get("ok", false)):
|
|
return failed_result(String(profile.id), "return_contract2_%d" % market_index)
|
|
adapter = reloaded.adapter
|
|
offline_zero = offline_zero and bool(reloaded.offline_zero)
|
|
reloads += 1
|
|
clock += 1.5
|
|
reload_timestamps.append(clock)
|
|
|
|
clock = record_decision(labels, timestamps, clock, "boss2_plan_%s" % String(profile.boss2_plan), decision_seconds)
|
|
var boss2 := run_victory(adapter, String(profile.boss2_plan))
|
|
if not bool(boss2.get("ok", false)):
|
|
return failed_result(String(profile.id), "boss2")
|
|
capture_pressure_families(boss2, pressure_families)
|
|
var boss2_seconds := battle_seconds(boss2)
|
|
automatic_seconds += boss2_seconds
|
|
clock += boss2_seconds + 1.0
|
|
var boss2_settled := adapter.settle_terminal()
|
|
if not bool(boss2_settled.get("ok", false)) or String(boss2_settled.view.mode) != "region_record":
|
|
return failed_result(String(profile.id), "boss2_settle")
|
|
clock = record_decision(labels, timestamps, clock, "record2_commit", decision_seconds)
|
|
var closed := adapter.continue_result()
|
|
if not bool(closed.get("ok", false)) or String(closed.view.mode) != "region_closed":
|
|
return failed_result(String(profile.id), "region_close")
|
|
if String(profile.id) == "returning":
|
|
var terminal_reload := reload_adapter(adapter, 210 + reloads)
|
|
if not bool(terminal_reload.get("ok", false)):
|
|
return failed_result(String(profile.id), "return_terminal")
|
|
adapter = terminal_reload.adapter
|
|
offline_zero = offline_zero and bool(terminal_reload.offline_zero)
|
|
reloads += 1
|
|
clock += 1.5
|
|
reload_timestamps.append(clock)
|
|
|
|
var view := adapter.view_state()
|
|
var result := {
|
|
"id": String(profile.id),
|
|
"failed": false,
|
|
"artifact": String(profile.artifact),
|
|
"boss_plans": [String(profile.boss1_plan), String(profile.boss2_plan)],
|
|
"route_risks": route_risks,
|
|
"route_ids": route_ids,
|
|
"plan_ids": plan_ids,
|
|
"pressure_families": sorted_keys(pressure_families),
|
|
"contract1_secondary_choices": contract1_secondary_choices,
|
|
"contract2_secondary_choices": contract2_secondary_choices,
|
|
"secondary_choices": contract1_secondary_choices + contract2_secondary_choices,
|
|
"labels": labels,
|
|
"timestamps": timestamps,
|
|
"decision_count": timestamps.size(),
|
|
"first_decision": float(timestamps[0]) if not timestamps.is_empty() else 999.0,
|
|
"max_gap": maximum_gap(timestamps),
|
|
"total_seconds": clock,
|
|
"automatic_seconds": automatic_seconds,
|
|
"recoveries": recoveries,
|
|
"recovery_labels": recovery_labels,
|
|
"recovery_timestamps": recovery_timestamps,
|
|
"fallen_ids": fallen_ids,
|
|
"reloads": reloads,
|
|
"reload_timestamps": reload_timestamps,
|
|
"offline_zero": offline_zero,
|
|
"records": int(view.records_count),
|
|
"region_records": int(view.region_records_count),
|
|
"unlocked_rg04": view.unlocked_region_ids.count("RG-04"),
|
|
"phase": String(view.mode),
|
|
"company_size": view.company.size(),
|
|
"coin": int(view.coin),
|
|
"terminal_snapshot": adapter.snapshot(),
|
|
}
|
|
print("FG4_PROFILE_RESULT profile=%s artifact=%s routes=%s bosses=%s decisions=%s recovery=%s reloads=%s first=%.1f max_gap=%.1f auto=%.1f total=%.1f company=%d coin=%d records=%d rg03_records=%d rg04=%d" % [result.id, result.artifact, result.route_risks, result.boss_plans, format_timestamps(labels, timestamps), format_timestamps(recovery_labels, recovery_timestamps), result.reload_timestamps, result.first_decision, result.max_gap, result.automatic_seconds, result.total_seconds, result.company_size, result.coin, result.records, result.region_records, result.unlocked_rg04])
|
|
return result
|
|
|
|
|
|
func run_victory(adapter, plan_id: String) -> Dictionary:
|
|
var started: Dictionary = adapter.start_plan(plan_id)
|
|
if not bool(started.get("ok", false)):
|
|
return {"ok": false, "code": String(started.get("code", "start_failed"))}
|
|
var terminal: Dictionary = adapter.run_battle_to_terminal()
|
|
if not bool(terminal.get("ok", false)) or String(terminal.view.battle.outcome) != "victory":
|
|
return {"ok": false, "code": "victory_required", "terminal": terminal}
|
|
return terminal
|
|
|
|
|
|
func choose_route_plan(battle_script, snapshot: Dictionary, market_index: int, prefer_secondary: bool) -> String:
|
|
var primary := String(battle_script.PRIMARY_PLAN[market_index])
|
|
if not prefer_secondary:
|
|
return primary
|
|
var secondary := String(battle_script.SECONDARY_PLAN[market_index])
|
|
var probe: Dictionary = battle_script.new(snapshot).run_to_terminal(secondary)
|
|
return secondary if bool(probe.get("ok", false)) and String(probe.snapshot.outcome) == "victory" else primary
|
|
|
|
|
|
func capture_pressure_families(terminal: Dictionary, families: Dictionary) -> void:
|
|
for event_value in terminal.get("events", []):
|
|
var kind := String(event_value.get("kind", ""))
|
|
if kind == "pressure_valve_disclosed":
|
|
families["valve"] = true
|
|
elif kind == "construct_state_changed":
|
|
families["construct"] = true
|
|
elif kind == "delayed_heat_forecast":
|
|
families["delayed_heat"] = true
|
|
elif kind == "item_cooldown_forecast":
|
|
families["cooldown"] = true
|
|
elif kind.begins_with("repeat_counter"):
|
|
families["repeat"] = true
|
|
elif kind in ["spark_promoted", "spark_removed", "next_spark_disclosed"]:
|
|
families["spark"] = true
|
|
|
|
|
|
func sorted_keys(values: Dictionary) -> Array[String]:
|
|
var result: Array[String] = []
|
|
for key in values:
|
|
result.append(String(key))
|
|
result.sort()
|
|
return result
|
|
|
|
|
|
func starting_snapshot(artifact_id: String) -> Dictionary:
|
|
var source := FG3Fixture.new().contract2_boss_muster_snapshot(artifact_id)
|
|
var service := FG3Execution.new(source)
|
|
if not service.valid:
|
|
return {}
|
|
var plan_id := (
|
|
"interrupt_pulse_family"
|
|
if artifact_id in ["AR-009", "AR-010", "AR-011", "AR-013"]
|
|
else "durable_links_stagger"
|
|
)
|
|
var prepared := service.commit_boss_plan(
|
|
"fg4-profile-fg3-boss-prepare-%s" % artifact_id,
|
|
service.state.revision,
|
|
String(service.state.current_run.boss_muster.muster_hash),
|
|
plan_id
|
|
)
|
|
if not prepared.ok:
|
|
return {}
|
|
var battle := FG3Battle.new(service.state.snapshot()).run_to_terminal(plan_id)
|
|
if not battle.ok or String(battle.snapshot.outcome) != "victory":
|
|
return {}
|
|
var settled := service.settle_boss(
|
|
"fg4-profile-fg3-boss-settle-%s" % artifact_id,
|
|
service.state.revision,
|
|
battle.snapshot
|
|
)
|
|
if not settled.ok:
|
|
return {}
|
|
var closed := service.close_region(
|
|
"fg4-profile-fg3-close-%s" % artifact_id,
|
|
service.state.revision,
|
|
String(service.state.current_run.pending_boss_result.close_id)
|
|
)
|
|
if not closed.ok:
|
|
return {}
|
|
var encoded := SaveV10.new().encode_snapshot(service.state.snapshot(), 100)
|
|
if not bool(encoded.get("ok", false)):
|
|
return {}
|
|
var migrated := SaveV11.new().migrate_v10_text(String(encoded.text), 100)
|
|
return Dictionary(migrated.get("snapshot", {})).duplicate(true) if bool(migrated.get("ok", false)) else {}
|
|
|
|
|
|
func reload_adapter(adapter, sequence: int) -> Dictionary:
|
|
var encoded := SaveV11.new().encode_snapshot(adapter.snapshot(), sequence)
|
|
if not bool(encoded.get("ok", false)):
|
|
return {"ok": false, "code": "encode"}
|
|
var decoded := SaveV11.new().decode_text(String(encoded.text), sequence)
|
|
if not bool(decoded.get("ok", false)) or decoded.snapshot != adapter.snapshot():
|
|
return {"ok": false, "code": "decode"}
|
|
var replacement := Adapter.new()
|
|
var configured := replacement.configure(decoded.snapshot)
|
|
return {
|
|
"ok": bool(configured.get("ok", false)),
|
|
"code": String(configured.get("code", "configure")),
|
|
"adapter": replacement,
|
|
"offline_zero": decoded.offline_delta == SaveV11.OFFLINE_ZERO,
|
|
}
|
|
|
|
|
|
func route_for_risk(routes: Array, risk: int) -> Dictionary:
|
|
for route_value in routes:
|
|
var route: Dictionary = route_value
|
|
if int(route.get("risk", -1)) == risk:
|
|
return route
|
|
return {}
|
|
|
|
|
|
func battle_seconds(terminal: Dictionary) -> float:
|
|
return float(terminal.get("view", {}).get("battle", {}).get("event_count", 0)) * 0.1
|
|
|
|
|
|
func record_decision(labels: Array[String], timestamps: Array[float], clock: float, label: String, dwell: float) -> float:
|
|
clock += dwell
|
|
labels.append(label)
|
|
timestamps.append(clock)
|
|
return clock
|
|
|
|
|
|
func record_recovery(labels: Array[String], timestamps: Array[float], clock: float, label: String, dwell: float) -> float:
|
|
return record_decision(labels, timestamps, clock, label, dwell)
|
|
|
|
|
|
func maximum_gap(timestamps: Array[float]) -> float:
|
|
var prior := 0.0
|
|
var result := 0.0
|
|
for timestamp in timestamps:
|
|
result = maxf(result, float(timestamp) - prior)
|
|
prior = float(timestamp)
|
|
return result
|
|
|
|
|
|
func format_timestamps(labels: Array[String], timestamps: Array[float]) -> String:
|
|
var rows: Array[String] = []
|
|
for index in range(labels.size()):
|
|
rows.append("%s@%.1f" % [labels[index], timestamps[index]])
|
|
return "[" + ",".join(rows) + "]"
|
|
|
|
|
|
func verify_results(results: Array[Dictionary]) -> void:
|
|
expect(results.size() == 5, "all five profiles return results")
|
|
var signatures := {}
|
|
var artifacts := {}
|
|
var boss1_plans := {}
|
|
var boss2_plans := {}
|
|
var total_contract1_secondary := 0
|
|
var total_contract2_secondary := 0
|
|
var risks_by_slot: Array[Dictionary] = []
|
|
for _index in range(24):
|
|
risks_by_slot.append({})
|
|
for result in results:
|
|
expect(not bool(result.get("failed", true)), "%s completes without harness failure" % String(result.get("id", "unknown")))
|
|
if bool(result.get("failed", true)):
|
|
continue
|
|
expect(int(result.decision_count) == 53, "%s records the exact 53 core decisions" % result.id)
|
|
expect(float(result.first_decision) <= 20.0, "%s reaches the first decision by 20 seconds" % result.id)
|
|
expect(float(result.max_gap) <= 45.0, "%s keeps the longest nonbattle decision gap within 45 seconds" % result.id)
|
|
expect(strictly_increasing(result.timestamps), "%s decision timestamps are strictly increasing" % result.id)
|
|
expect(result.route_ids.size() == 24 and result.route_risks.size() == 24, "%s exercises all 24 ordinary decisions" % result.id)
|
|
expect(result.plan_ids.size() == 24 and result.boss_plans.size() == 2, "%s records all ordinary and Boss plans" % result.id)
|
|
expect(result.pressure_families == ["construct", "cooldown", "delayed_heat", "repeat", "spark", "valve"], "%s observes all six event-bound pressure families" % result.id)
|
|
expect(int(result.records) == 4 and int(result.region_records) == 2 and int(result.unlocked_rg04) == 1 and String(result.phase) == "region_closed", "%s reaches the exact persistent RG-03 close" % result.id)
|
|
expect(int(result.coin) >= 0 and int(result.company_size) >= 1, "%s avoids insolvency and total Company loss" % result.id)
|
|
expect(bool(result.offline_zero), "%s reloads never invent offline value" % result.id)
|
|
var signature := "%s:%s:%s" % [result.artifact, result.route_risks, result.boss_plans]
|
|
signatures[signature] = true
|
|
artifacts[String(result.artifact)] = true
|
|
boss1_plans[String(result.boss_plans[0])] = true
|
|
boss2_plans[String(result.boss_plans[1])] = true
|
|
total_contract1_secondary += int(result.contract1_secondary_choices)
|
|
total_contract2_secondary += int(result.contract2_secondary_choices)
|
|
for index in range(24):
|
|
risks_by_slot[index][int(result.route_risks[index])] = true
|
|
if String(result.id) == "adversarial":
|
|
expect(int(result.recoveries) == 1 and result.fallen_ids.size() == 1, "adversarial play loses one Recruit and actively recovers")
|
|
expect(result.recovery_labels == ["wrong_plan_focus_foreman", "accept_permanent_loss", "retry_same_disclosed_route"], "adversarial recovery names failure, acceptance and retry")
|
|
expect(strictly_increasing(result.recovery_timestamps), "adversarial recovery timestamps are ordered")
|
|
if String(result.id) == "returning":
|
|
expect(int(result.reloads) >= 3 and strictly_increasing(result.reload_timestamps), "returning play reloads at least three stable boundaries")
|
|
expect(signatures.size() == 5, "five profile policies remain strategically distinct")
|
|
expect(artifacts.size() == 3, "five profiles cover all three legal inherited Artifact contexts")
|
|
expect(boss1_plans.size() == 2 and boss2_plans.size() == 2, "both legal Boss responses are exercised")
|
|
expect(total_contract1_secondary > 0 and total_contract2_secondary > 0, "both Contracts exercise a winning secondary response")
|
|
for index in range(risks_by_slot.size()):
|
|
expect(risks_by_slot[index].size() >= 2, "ordinary decision %d changes route risk across profiles" % (index + 1))
|
|
|
|
|
|
func strictly_increasing(values: Array) -> bool:
|
|
for index in range(1, values.size()):
|
|
if float(values[index]) <= float(values[index - 1]):
|
|
return false
|
|
return true
|
|
|
|
|
|
func failed_result(profile_id: String, code: String) -> Dictionary:
|
|
print("FG4_PROFILE_FAILURE profile=%s code=%s" % [profile_id, code])
|
|
return {"id": profile_id, "failed": true, "code": code}
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-4 five-profile simulation: %s" % message)
|