108 lines
4.3 KiB
GDScript
108 lines
4.3 KiB
GDScript
extends SceneTree
|
|
|
|
const Activation = preload("res://fg4/contract2_activation_service.gd")
|
|
const Fixture = preload("res://tests/fg4/contract1_fixture.gd")
|
|
const State = preload("res://fg4/run_state.gd")
|
|
|
|
var failures: Array[String] = []
|
|
var rejected_cases := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
verify_exact_activation_and_preservation()
|
|
verify_activation_boundaries()
|
|
if failures.is_empty():
|
|
print("ABG_FG4_CONTRACT2_ACTIVATION_OK source=CLEAR-RG03-C01 contract=CTR-R3-02 phase=contract2_market market=1 board=3,4,3 prior_routes=36 company,growth,hp,equipment,economy,artifact,receipts=exact duplicate=true rejected_cases=%d" % rejected_cases)
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func verify_exact_activation_and_preservation() -> void:
|
|
var before := Fixture.new().contract2_handoff_snapshot()
|
|
expect(not before.is_empty(), "Contract-1 close fixture exists")
|
|
var service := Activation.new(before)
|
|
expect(service.valid and service.state.current_run.phase == "contract2_handoff", "handoff restores")
|
|
if not service.valid:
|
|
return
|
|
var handoff_hash := String(service.state.current_run.market_board.handoff_hash)
|
|
var revision_before := service.state.revision
|
|
var preserved_before := preserved_projection(service.state.snapshot())
|
|
var activated := service.activate_contract(
|
|
"fg4-contract2-activate",
|
|
revision_before,
|
|
handoff_hash
|
|
)
|
|
expect(activated.ok and not activated.duplicate, "Contract 2 activates exactly once")
|
|
if not activated.ok:
|
|
return
|
|
var run: Dictionary = service.state.current_run
|
|
expect(run.contract_id == "CTR-R3-02" and run.phase == "contract2_market", "Contract 2 identity and phase are exact")
|
|
expect(run.market_index == 1 and run.round_index == 1 and run.cleared_rounds == 36, "Market and lineage are exact")
|
|
expect(run.market_board.recruit_slots.size() == 3 and run.market_board.equipment_offers.size() == 4 and run.market_board.battle_offers.size() == 3, "Market 1 has exact 3/4/3 board")
|
|
expect(run.market_board.spark_disclosure.repeat_threshold == 3, "repeat threshold is disclosed before commitment")
|
|
expect(preserved_projection(service.state.snapshot()) == preserved_before, "activation preserves Company and carried consequences byte-equivalent")
|
|
var duplicate := service.activate_contract(
|
|
"fg4-contract2-activate",
|
|
revision_before,
|
|
handoff_hash
|
|
)
|
|
expect(duplicate.ok and duplicate.duplicate and duplicate.revision == activated.revision, "duplicate activation is inert")
|
|
var roundtrip := State.new()
|
|
expect(roundtrip.restore_snapshot(service.state.snapshot()) and roundtrip.snapshot() == service.state.snapshot(), "activated state roundtrips exactly")
|
|
|
|
|
|
func verify_activation_boundaries() -> void:
|
|
var before := Fixture.new().contract2_handoff_snapshot()
|
|
if before.is_empty():
|
|
return
|
|
var service := Activation.new(before)
|
|
var original := service.state.snapshot()
|
|
expect_rejected(
|
|
service.activate_contract("fg4-contract2-bad-hash", service.state.revision, "0".repeat(64)),
|
|
"handoff hash tamper",
|
|
"handoff_hash_mismatch"
|
|
)
|
|
expect(service.state.snapshot() == original, "rejected activation does not mutate state")
|
|
var invalid := before.duplicate(true)
|
|
invalid.current_run.market_board.handoff_hash = "0".repeat(64)
|
|
expect(not State.new().restore_snapshot(invalid), "tampered handoff state rejects before activation")
|
|
rejected_cases += 1
|
|
|
|
|
|
func preserved_projection(snapshot_value: Dictionary) -> Dictionary:
|
|
var run: Dictionary = snapshot_value.current_run
|
|
return {
|
|
"run_id": run.run_id,
|
|
"run_seed": run.run_seed,
|
|
"company": run.company,
|
|
"deployed": run.deployed_recruit_ids,
|
|
"reserve": run.reserve_recruit_ids,
|
|
"recruit_records": run.recruit_records,
|
|
"inventory": run.inventory,
|
|
"equipped": run.equipped_by_recruit,
|
|
"coin": run.coin,
|
|
"credit": run.replacement_credit,
|
|
"artifacts": run.active_artifact_ids,
|
|
"history": run.route_history,
|
|
"guild_profile": snapshot_value.guild_profile,
|
|
}
|
|
|
|
|
|
func expect_rejected(result: Dictionary, label: String, code: String) -> void:
|
|
var rejected := not bool(result.get("ok", false)) and String(result.get("code", "")) == code
|
|
expect(rejected, "%s rejects with %s" % [label, code])
|
|
if rejected:
|
|
rejected_cases += 1
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-4 Contract-2 activation: %s" % message)
|