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

519 lines
25 KiB
GDScript

extends SceneTree
const Adapter = preload("res://campaign/fg4_adapter.gd")
const RG03Suite = preload("res://tests/fg4/five_profile_simulation_test.gd")
const Fixture = preload("res://tests/fg4/contract1_fixture.gd")
const RG04Contract1Battle = preload("res://fg4/rg04_contract1_battle_service.gd")
const RG04Contract2Battle = preload("res://fg4/rg04_contract2_battle_service.gd")
const SaveV11 = preload("res://fg4/save_v11_service.gd")
const PROFILE_EXTENSIONS := [
{
"id": "normal",
"contract1_risks": [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
"contract2_risks": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
"boss1_plan": "steer_action_count",
"boss2_plan": "safe_rule_seed",
},
{
"id": "active",
"contract1_risks": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
"contract2_risks": [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
"boss1_plan": "interrupt_pointer",
"boss2_plan": "reorder_and_interrupt",
},
{
"id": "efficient",
"contract1_risks": [0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 2, 0],
"contract2_risks": [0, 1, 0, 2, 0, 0, 0, 1, 0, 2, 0, 0],
"boss1_plan": "steer_action_count",
"boss2_plan": "reorder_and_interrupt",
},
{
"id": "adversarial",
"contract1_risks": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
"contract2_risks": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
"boss1_plan": "interrupt_pointer",
"boss2_plan": "safe_rule_seed",
},
{
"id": "returning",
"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": "steer_action_count",
"boss2_plan": "safe_rule_seed",
},
]
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var rg03_suite = RG03Suite.new()
var results: Array[Dictionary] = []
for index in range(PROFILE_EXTENSIONS.size()):
var rg03_profile: Dictionary = RG03Suite.PROFILES[index]
var extension: Dictionary = PROFILE_EXTENSIONS[index]
expect(String(rg03_profile.id) == String(extension.id), "profile order stays source-bound")
var rg03_result: Dictionary = rg03_suite.simulate_profile(rg03_profile)
if bool(rg03_result.get("failed", true)):
results.append(rg03_result)
continue
results.append(simulate_rg04(rg03_result, rg03_profile, extension))
rg03_suite.free()
verify_artifact_counterfactuals()
verify_results(results)
if failures.is_empty():
var total_decisions := 0
var longest_gap := 0.0
var recoveries := 0
var reloads := 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)
print("ABG_FG4_MIDPOINT_FIVE_PROFILE_OK profiles=normal,active,efficient,adversarial,returning core_decisions=%d per_profile=107 longest_gap=%.1f ordinary_rounds=48 bosses=4 records=6 rg03_records=2 rg04_records=2 recoveries=%d reloads=%d policy_signatures=5 inherited_artifacts=3 rg04_artifact_reversals=24 boss_artifact_reversals=4 offline_rewards=0 resurrection=0 terminal=RG-05" % [total_decisions, longest_gap, recoveries, reloads])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func simulate_rg04(rg03_result: Dictionary, rg03_profile: Dictionary, extension: Dictionary) -> Dictionary:
var adapter := Adapter.new()
var configured := adapter.configure(Dictionary(rg03_result.terminal_snapshot).duplicate(true))
if not bool(configured.get("ok", false)):
return failed_result(String(extension.id), "rg03_terminal_configure")
var state := {
"adapter": adapter,
"labels": Array(rg03_result.labels).duplicate(),
"timestamps": Array(rg03_result.timestamps).duplicate(),
"recovery_labels": Array(rg03_result.recovery_labels).duplicate(),
"recovery_timestamps": Array(rg03_result.recovery_timestamps).duplicate(),
"reload_timestamps": Array(rg03_result.reload_timestamps).duplicate(),
"route_risks": Array(rg03_result.route_risks).duplicate(),
"route_ids": Array(rg03_result.route_ids).duplicate(),
"plan_ids": Array(rg03_result.plan_ids).duplicate(),
"boss_plans": Array(rg03_result.boss_plans).duplicate(),
"clock": float(rg03_result.total_seconds),
"automatic_seconds": float(rg03_result.automatic_seconds),
"recoveries": int(rg03_result.recoveries),
"rg04_recoveries": 0,
"reloads": int(rg03_result.reloads),
"offline_zero": bool(rg03_result.offline_zero),
"fallen_ids": Array(rg03_result.fallen_ids).duplicate(),
"secondary_counts": [
int(rg03_result.contract1_secondary_choices),
int(rg03_result.contract2_secondary_choices),
0,
0,
],
"pressure_families": {},
"clause_kinds": {},
"redaction_seen": false,
}
var decision_seconds := float(rg03_profile.decision_seconds)
record_decision(state, "rg04_region_activation", decision_seconds)
var activated: Dictionary = state.adapter.continue_result()
if not bool(activated.get("ok", false)) or String(activated.view.phase) != "rg04_market":
return failed_result(String(extension.id), "rg04_activation")
if String(extension.id) == "returning" and not reload_state(state):
return failed_result(String(extension.id), "rg04_activation_reload")
var first_contract := run_routes(
state,
String(extension.id),
"r4c1",
Array(extension.contract1_risks),
RG04Contract1Battle,
decision_seconds
)
if not bool(first_contract.get("ok", false)):
return failed_result(String(extension.id), String(first_contract.get("code", "rg04_contract1")))
record_decision(state, "r4_boss1_plan_%s" % String(extension.boss1_plan), decision_seconds)
state.boss_plans.append(String(extension.boss1_plan))
var boss1 := run_victory(state.adapter, String(extension.boss1_plan))
if not bool(boss1.get("ok", false)):
return failed_result(String(extension.id), "rg04_boss1")
capture_archive_events(boss1, state)
state.automatic_seconds = float(state.automatic_seconds) + battle_seconds(boss1)
state.clock = float(state.clock) + battle_seconds(boss1) + 1.0
var boss1_settled: Dictionary = state.adapter.settle_terminal()
if not bool(boss1_settled.get("ok", false)) or String(boss1_settled.view.mode) != "contract_record":
return failed_result(String(extension.id), "rg04_boss1_settle")
record_decision(state, "rg04_record1_commit", decision_seconds)
var record1: Dictionary = state.adapter.continue_result()
if not bool(record1.get("ok", false)) or String(record1.view.phase) != "rg04_contract2_handoff":
return failed_result(String(extension.id), "rg04_record1")
record_decision(state, "rg04_contract2_handoff", decision_seconds)
var handoff: Dictionary = state.adapter.continue_result()
if not bool(handoff.get("ok", false)) or String(handoff.view.contract_id) != "CTR-R4-02":
return failed_result(String(extension.id), "rg04_contract2_handoff")
if String(extension.id) == "returning" and not reload_state(state):
return failed_result(String(extension.id), "rg04_handoff_reload")
var second_contract := run_routes(
state,
String(extension.id),
"r4c2",
Array(extension.contract2_risks),
RG04Contract2Battle,
decision_seconds
)
if not bool(second_contract.get("ok", false)):
return failed_result(String(extension.id), String(second_contract.get("code", "rg04_contract2")))
record_decision(state, "r4_boss2_plan_%s" % String(extension.boss2_plan), decision_seconds)
state.boss_plans.append(String(extension.boss2_plan))
var boss2 := run_victory(state.adapter, String(extension.boss2_plan))
if not bool(boss2.get("ok", false)):
return failed_result(String(extension.id), "rg04_boss2")
capture_archive_events(boss2, state)
state.automatic_seconds = float(state.automatic_seconds) + battle_seconds(boss2)
state.clock = float(state.clock) + battle_seconds(boss2) + 1.0
var boss2_settled: Dictionary = state.adapter.settle_terminal()
if not bool(boss2_settled.get("ok", false)) or String(boss2_settled.view.mode) != "region_record":
return failed_result(String(extension.id), "rg04_boss2_settle")
record_decision(state, "rg04_record2_commit", decision_seconds)
var closed: Dictionary = state.adapter.continue_result()
if not bool(closed.get("ok", false)) or String(closed.view.phase) != "rg04_region_closed":
return failed_result(String(extension.id), "rg04_region_close")
if String(extension.id) == "returning" and not reload_state(state):
return failed_result(String(extension.id), "rg04_terminal_reload")
var view: Dictionary = state.adapter.view_state()
var final_snapshot: Dictionary = state.adapter.snapshot()
var result := {
"id": String(extension.id),
"failed": false,
"artifact": String(rg03_result.artifact),
"boss_plans": state.boss_plans,
"route_risks": state.route_risks,
"route_ids": state.route_ids,
"plan_ids": state.plan_ids,
"secondary_counts": state.secondary_counts,
"labels": state.labels,
"timestamps": state.timestamps,
"decision_count": state.timestamps.size(),
"first_decision": float(state.timestamps[0]),
"max_gap": maximum_gap(state.timestamps),
"total_seconds": float(state.clock),
"automatic_seconds": float(state.automatic_seconds),
"recoveries": int(state.recoveries),
"rg04_recoveries": int(state.rg04_recoveries),
"recovery_labels": state.recovery_labels,
"recovery_timestamps": state.recovery_timestamps,
"fallen_ids": state.fallen_ids,
"reloads": int(state.reloads),
"reload_timestamps": state.reload_timestamps,
"offline_zero": bool(state.offline_zero),
"rg04_pressure_families": sorted_keys(state.pressure_families),
"clause_kinds": sorted_keys(state.clause_kinds),
"redaction_seen": bool(state.redaction_seen),
"records": int(view.records_count),
"region_records": int(view.region_records_count),
"unlocked_rg05": view.unlocked_region_ids.count("RG-05"),
"phase": String(view.phase),
"cleared_rounds": int(view.cleared_rounds),
"route_history_count": final_snapshot.current_run.route_history.size(),
"company_size": view.company.size(),
"coin": int(view.coin),
}
print("FG4_MIDPOINT_PROFILE_RESULT profile=%s artifact=%s risks=%s bosses=%s decisions=%s recovery=%s reloads=%s first=%.1f max_gap=%.1f total=%.1f company=%d coin=%d records=%d rg04_records=%d rg05=%d" % [result.id, result.artifact, result.route_risks, result.boss_plans, format_timestamps(result.labels, result.timestamps), format_timestamps(result.recovery_labels, result.recovery_timestamps), result.reload_timestamps, result.first_decision, result.max_gap, result.total_seconds, result.company_size, result.coin, result.records, result.region_records, result.unlocked_rg05])
return result
func run_routes(
state: Dictionary,
profile_id: String,
contract_key: String,
risks: Array,
battle_script,
decision_seconds: float
) -> Dictionary:
for offset in range(12):
var market_index := offset + 1
var risk := int(risks[offset])
var route := route_for_risk(state.adapter.view_state().routes, risk)
if route.is_empty():
return {"ok": false, "code": "%s_route_%d" % [contract_key, market_index]}
record_decision(state, "%s_m%d_route_r%d" % [contract_key, market_index, risk], decision_seconds)
state.route_risks.append(risk)
state.route_ids.append(String(route.route_id))
var prepared: Dictionary = state.adapter.prepare_route(String(route.offer_id))
if not bool(prepared.get("ok", false)):
return {"ok": false, "code": "%s_prepare_%d" % [contract_key, market_index]}
if profile_id == "normal" and contract_key == "r4c1" and market_index == 1:
record_recovery(state, "r4c1_wrong_archive_response", decision_seconds)
var wrong_plan := first_wrong_plan(battle_script, market_index)
var wrong_started: Dictionary = state.adapter.start_plan(wrong_plan)
var wrong_terminal: Dictionary = state.adapter.run_battle_to_terminal()
if not bool(wrong_started.get("ok", false)) or not bool(wrong_terminal.get("ok", false)) or String(wrong_terminal.view.battle.outcome) != "defeat":
return {"ok": false, "code": "rg04_recovery_defeat"}
state.automatic_seconds = float(state.automatic_seconds) + battle_seconds(wrong_terminal)
state.clock = float(state.clock) + battle_seconds(wrong_terminal) + 1.0
state.fallen_ids.append_array(Array(wrong_terminal.view.battle.fallen_recruit_ids))
var failed_settlement: Dictionary = state.adapter.settle_terminal()
if not bool(failed_settlement.get("ok", false)) or String(failed_settlement.view.mode) != "recovery":
return {"ok": false, "code": "rg04_recovery_settle"}
record_recovery(state, "r4c1_accept_permanent_loss", decision_seconds)
var recovered: Dictionary = state.adapter.recover()
if not bool(recovered.get("ok", false)) or String(recovered.view.mode) != "market":
return {"ok": false, "code": "rg04_recovery_commit"}
state.recoveries = int(state.recoveries) + 1
state.rg04_recoveries = int(state.rg04_recoveries) + 1
record_recovery(state, "r4c1_retry_same_route", decision_seconds)
route = route_for_risk(state.adapter.view_state().routes, risk)
prepared = state.adapter.prepare_route(String(route.offer_id))
if not bool(prepared.get("ok", false)):
return {"ok": false, "code": "rg04_recovery_retry"}
var prefer_secondary := should_prefer_secondary(profile_id, contract_key, market_index)
var plan_id := choose_route_plan(battle_script, state.adapter.snapshot(), market_index, prefer_secondary)
if plan_id == String(battle_script.SECONDARY_PLAN[market_index]):
var counter_index := 2 if contract_key == "r4c1" else 3
state.secondary_counts[counter_index] = int(state.secondary_counts[counter_index]) + 1
record_decision(state, "%s_m%d_plan_%s" % [contract_key, market_index, plan_id], decision_seconds)
state.plan_ids.append(plan_id)
var terminal := run_victory(state.adapter, plan_id)
if not bool(terminal.get("ok", false)):
return {"ok": false, "code": "%s_battle_%d" % [contract_key, market_index]}
capture_archive_events(terminal, state)
state.automatic_seconds = float(state.automatic_seconds) + battle_seconds(terminal)
state.clock = float(state.clock) + battle_seconds(terminal) + 1.0
if not bool(state.adapter.settle_terminal().get("ok", false)):
return {"ok": false, "code": "%s_settle_%d" % [contract_key, market_index]}
if profile_id == "returning" and market_index in [3, 6, 9, 12] and not reload_state(state):
return {"ok": false, "code": "%s_reload_%d" % [contract_key, market_index]}
return {"ok": true}
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 should_prefer_secondary(profile_id: String, contract_key: String, market_index: int) -> bool:
if profile_id == "active":
return market_index % 2 == (0 if contract_key == "r4c1" else 1)
if profile_id == "efficient":
return market_index % 2 == (1 if contract_key == "r4c1" else 0)
if profile_id == "returning":
return market_index % 4 == 0
return false
func reload_state(state: Dictionary) -> bool:
var sequence := 1000 + int(state.reloads)
var encoded := SaveV11.new().encode_snapshot(state.adapter.snapshot(), sequence)
if not bool(encoded.get("ok", false)):
return false
var decoded := SaveV11.new().decode_text(String(encoded.text), sequence)
if not bool(decoded.get("ok", false)) or decoded.snapshot != state.adapter.snapshot():
return false
var replacement := Adapter.new()
var configured := replacement.configure(decoded.snapshot)
if not bool(configured.get("ok", false)):
return false
state.adapter = replacement
state.offline_zero = bool(state.offline_zero) and decoded.offline_delta == SaveV11.OFFLINE_ZERO
state.reloads = int(state.reloads) + 1
state.clock = float(state.clock) + 1.5
state.reload_timestamps.append(float(state.clock))
return true
func capture_archive_events(terminal: Dictionary, state: Dictionary) -> void:
for event_value in terminal.get("events", []):
var event: Dictionary = event_value
var kind := String(event.get("kind", ""))
var detail: Dictionary = event.get("detail", {})
if kind == "archive_clause_disclosed":
state.clause_kinds[String(detail.get("kind", ""))] = true
elif kind in ["action_copy_resolved", "target_redirect_resolved", "cleanse_trap_resolved", "clause_evaluation_resolved"]:
state.pressure_families[String(detail.get("pressure_family", ""))] = true
elif kind in ["redaction_source_disclosed", "target_clause_omitted", "replay_order_disclosed"]:
state.redaction_seen = true
func verify_artifact_counterfactuals() -> void:
var reversals := 0
for battle_script in [RG04Contract1Battle, RG04Contract2Battle]:
for market_index in range(1, 13):
var primary := String(battle_script.PRIMARY_PLAN[market_index])
var secondary := String(battle_script.SECONDARY_PLAN[market_index])
var odd_primary: int = int(battle_script.route_plan_score(market_index, 1, primary, "AR-023", 0, 1.0))
var odd_secondary: int = int(battle_script.route_plan_score(market_index, 1, secondary, "AR-023", 0, 1.0))
var even_primary: int = int(battle_script.route_plan_score(market_index, 1, primary, "AR-024", 0, 1.0))
var even_secondary: int = int(battle_script.route_plan_score(market_index, 1, secondary, "AR-024", 0, 1.0))
expect(odd_primary > odd_secondary and even_secondary > even_primary, "RG-04 Market %d flips Artifact response" % market_index)
if odd_primary > odd_secondary and even_secondary > even_primary:
reversals += 1
expect(reversals == 24, "all 24 RG-04 Markets have Artifact counterfactuals")
var c1_base := Fixture.new().rg04_boss_muster_snapshot()
expect(boss_outcome(c1_base, "steer_action_count", "AR-024") == "victory", "Walking Index steering survives AR-024")
expect(boss_outcome(c1_base, "interrupt_pointer", "AR-024") == "defeat", "AR-024 reverses Walking Index interrupt")
expect(boss_outcome(c1_base, "interrupt_pointer", "AR-023") == "victory", "Walking Index interrupt wins outside AR-024")
var c2_base := Fixture.new().rg04_contract2_boss_muster_snapshot()
expect(boss_outcome(c2_base, "safe_rule_seed", "AR-024") == "defeat", "AR-024 reverses Redaction Hart seed")
expect(boss_outcome(c2_base, "reorder_and_interrupt", "AR-024") == "victory", "AR-024 favors Redaction Hart reorder")
expect(boss_outcome(c2_base, "safe_rule_seed", "AR-023") == "victory", "AR-023 favors Redaction Hart seed")
expect(boss_outcome(c2_base, "reorder_and_interrupt", "AR-023") == "defeat", "AR-023 reverses Redaction Hart reorder")
func boss_outcome(source: Dictionary, plan_id: String, artifact_id: String) -> String:
var snapshot_value: Dictionary = source.duplicate(true)
snapshot_value.current_run.active_artifact_ids = [artifact_id]
var adapter := Adapter.new()
if not bool(adapter.configure(snapshot_value).get("ok", false)):
return "invalid"
if not bool(adapter.start_plan(plan_id).get("ok", false)):
return "invalid"
var terminal: Dictionary = adapter.run_battle_to_terminal()
return String(terminal.get("view", {}).get("battle", {}).get("outcome", "invalid"))
func verify_results(results: Array[Dictionary]) -> void:
expect(results.size() == 5, "all five combined profiles return results")
var signatures := {}
var artifacts := {}
var bosses_by_slot: Array[Dictionary] = [{}, {}, {}, {}]
var risks_by_slot: Array[Dictionary] = []
var secondary_by_contract := [0, 0, 0, 0]
var total_rg04_recoveries := 0
for _index in range(48):
risks_by_slot.append({})
for result in results:
expect(not bool(result.get("failed", true)), "%s completes both Regions" % String(result.get("id", "unknown")))
if bool(result.get("failed", true)):
continue
expect(int(result.decision_count) == 107, "%s records exact combined decisions" % result.id)
expect(float(result.first_decision) <= 20.0 and float(result.max_gap) <= 45.0, "%s meets decision timing bounds" % result.id)
expect(strictly_increasing(result.timestamps), "%s timestamps strictly increase" % result.id)
expect(result.route_ids.size() == 48 and result.route_risks.size() == 48, "%s executes all 48 ordinary rounds" % result.id)
expect(result.plan_ids.size() == 48 and result.boss_plans.size() == 4, "%s records all route and Boss plans" % result.id)
expect(result.rg04_pressure_families == ["clause", "cleanse_trap", "copy", "redirect"], "%s sees every archive pressure" % result.id)
expect(result.clause_kinds == ["alternate", "preserve", "repeat"] and bool(result.redaction_seen), "%s sees every clause and redaction disclosure" % result.id)
expect(int(result.records) == 6 and int(result.region_records) == 2, "%s writes all six carried Records" % result.id)
expect(int(result.unlocked_rg05) == 1 and String(result.phase) == "rg04_region_closed", "%s reaches exact RG-05 handoff" % result.id)
expect(int(result.cleared_rounds) == 72 and int(result.route_history_count) == 72, "%s preserves the complete route lineage" % result.id)
expect(int(result.company_size) >= 1 and int(result.coin) >= 0, "%s remains solvent with a viable Company" % result.id)
expect(bool(result.offline_zero), "%s reloads with zero 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
for slot in range(4):
bosses_by_slot[slot][String(result.boss_plans[slot])] = true
secondary_by_contract[slot] += int(result.secondary_counts[slot])
for index in range(48):
risks_by_slot[index][int(result.route_risks[index])] = true
total_rg04_recoveries += int(result.rg04_recoveries)
if String(result.id) == "normal":
expect(int(result.rg04_recoveries) == 1, "normal profile proves RG-04 permanent-loss recovery")
if String(result.id) == "returning":
expect(int(result.reloads) >= 12 and strictly_increasing(result.reload_timestamps), "returning profile reloads both Regions")
expect(signatures.size() == 5, "five combined policy signatures are distinct")
expect(artifacts.size() == 3, "combined profiles preserve three inherited Artifact contexts")
for slot in range(4):
expect(bosses_by_slot[slot].size() == 2, "Boss slot %d exercises both legal answers" % (slot + 1))
expect(secondary_by_contract[slot] > 0, "Contract slot %d exercises a winning secondary" % (slot + 1))
for index in range(48):
expect(risks_by_slot[index].size() >= 2, "route decision %d changes risk across profiles" % (index + 1))
expect(total_rg04_recoveries >= 1, "combined profiles include RG-04 recovery")
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 first_wrong_plan(battle_script, market_index: int) -> String:
for plan_id in battle_script.PLAN_IDS:
if plan_id not in [battle_script.PRIMARY_PLAN[market_index], battle_script.SECONDARY_PLAN[market_index]]:
return String(plan_id)
return ""
func battle_seconds(terminal: Dictionary) -> float:
return float(terminal.get("view", {}).get("battle", {}).get("event_count", 0)) * 0.1
func record_decision(state: Dictionary, label: String, dwell: float) -> void:
state.clock = float(state.clock) + dwell
state.labels.append(label)
state.timestamps.append(float(state.clock))
func record_recovery(state: Dictionary, label: String, dwell: float) -> void:
state.clock = float(state.clock) + dwell
state.recovery_labels.append(label)
state.recovery_timestamps.append(float(state.clock))
func maximum_gap(timestamps: Array) -> 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 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 sorted_keys(values: Dictionary) -> Array[String]:
var result: Array[String] = []
for key in values:
if not String(key).is_empty():
result.append(String(key))
result.sort()
return result
func format_timestamps(labels: Array, timestamps: Array) -> String:
var rows: Array[String] = []
for index in range(labels.size()):
rows.append("%s@%.1f" % [labels[index], timestamps[index]])
return "[" + ",".join(rows) + "]"
func failed_result(profile_id: String, code: String) -> Dictionary:
print("FG4_MIDPOINT_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 midpoint five-profile simulation: %s" % message)