Files
aetherbound-guild/runtime/tests/fg3/contract1_boss_test.gd
T

183 lines
9.3 KiB
GDScript

extends SceneTree
const BattleService = preload("res://fg3/contract1_battle_service.gd")
const ContractService = preload("res://fg3/contract1_service.gd")
const Fixture = preload("res://tests/fg3/contract1_fixture.gd")
const SaveV10 = preload("res://fg3/save_v10_service.gd")
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
verify_boss_victory_record_and_handoff()
verify_boss_defeat_and_recovery()
verify_boss_atomic_boundaries()
if failures.is_empty():
print("ABG_FG3_CONTRACT1_BOSS_OK boss=BO-R2-01 plans=2 victory=true defeat=true casualty=permanent recovery=same_muster boss_coin=0 clear=CLEAR-RG02-C01 renown=16 company_record=1 contract2_handoff=true duplicate=true replay=true save_v10=roundtrip")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_boss_victory_record_and_handoff() -> void:
var boss_snapshot := Fixture.new().boss_muster_snapshot()
var service := ContractService.new(boss_snapshot)
expect(service.valid and service.state.current_run.phase == "boss_muster", "real twelve-Round state opens fixed Muster")
if not service.valid:
return
var coin_before := int(service.state.current_run.coin)
var renown_before := int(service.state.guild_profile.persistent_renown)
var records_before: int = service.state.guild_profile.company_records.size()
var muster_hash := String(service.state.current_run.boss_muster.muster_hash)
var prepare_revision := int(service.state.revision)
var prepared := service.commit_boss_plan(
"fg3-boss-victory-prepare",
prepare_revision,
muster_hash,
"follow_index_clear_eggs"
)
expect(prepared.ok and service.state.current_run.phase == "boss_prepared", "follow-index plan prepares")
var duplicate_prepare := service.commit_boss_plan(
"fg3-boss-victory-prepare",
prepare_revision,
muster_hash,
"follow_index_clear_eggs"
)
expect(duplicate_prepare.ok and duplicate_prepare.duplicate, "Boss prepare retry is idempotent")
var battle := BattleService.new(service.state.snapshot()).run_to_terminal("follow_index_clear_eggs")
expect(battle.ok and battle.snapshot.outcome == "victory", "disclosed follow-index response defeats Sluiceback Mother")
expect(event_count(battle.snapshot.event_log, "safe_index_forecast") == 1 and event_count(battle.snapshot.event_log, "egg_positions_disclosed") == 1, "battle exposes safe index and eggs")
expect(event_count(battle.snapshot.event_log, "safe_index_reversed") == 1 and event_count(battle.snapshot.event_log, "eggs_cleared_on_index") == 1, "battle executes phase change and selected response")
if not battle.ok:
return
var settle_revision := int(service.state.revision)
var settled := service.settle_boss(
"fg3-boss-victory-settle",
settle_revision,
battle.snapshot
)
expect(settled.ok and service.state.current_run.phase == "contract_result", "Boss victory stages one Contract result")
expect(service.state.current_run.coin == coin_before and settled.receipt.reward_coin == 0, "Boss grants no spendable Coin")
expect(String(service.state.current_run.pending_boss_result.close_id).length() == 32, "victory creates one exact close identity")
var after_settle := service.state.snapshot()
var duplicate_settle := service.settle_boss(
"fg3-boss-victory-settle",
settle_revision,
battle.snapshot
)
expect(duplicate_settle.ok and duplicate_settle.duplicate and service.state.snapshot() == after_settle, "Boss settlement retry is inert")
var close_id := String(service.state.current_run.pending_boss_result.close_id)
var close_revision := int(service.state.revision)
var closed := service.close_contract(
"fg3-contract1-close",
close_revision,
close_id
)
expect(closed.ok and service.state.current_run.phase == "contract2_handoff", "first Contract closes into same-Run Contract 2 handoff")
expect(service.state.guild_profile.persistent_renown == renown_before + 16 and "CLEAR-RG02-C01" in service.state.guild_profile.contract_clear_ids, "first clear grants exact 16 Renown once")
expect(service.state.guild_profile.company_records.size() == records_before + 1, "close creates exactly one Company Record")
var record: Dictionary = service.state.guild_profile.company_records[-1]
expect(record.contract_id == "CTR-R2-01" and record.boss_id == "BO-R2-01" and record.route_ids.size() == 12, "Company Record binds Contract, Boss and twelve routes")
expect(service.state.current_run.next_contract_id == "CTR-R2-02" and service.state.current_run.market_board.next_contract_id == "CTR-R2-02", "handoff names exact next Contract")
expect(service.state.current_run.company == boss_snapshot.current_run.company, "surviving Company continues without reset")
var after_close := service.state.snapshot()
var duplicate_close := service.close_contract(
"fg3-contract1-close",
close_revision,
close_id
)
expect(duplicate_close.ok and duplicate_close.duplicate and service.state.snapshot() == after_close, "Contract close retry cannot duplicate Renown or Record")
var encoded := SaveV10.new().encode_snapshot(after_close, 90)
var decoded := SaveV10.new().decode_text(String(encoded.text), 90)
expect(encoded.ok and decoded.ok and decoded.snapshot == after_close, "Contract-2 handoff roundtrips through Save-v10")
func verify_boss_defeat_and_recovery() -> void:
var boss_snapshot := Fixture.new().boss_muster_snapshot()
var company_before: Array = Array(boss_snapshot.current_run.company).duplicate()
var deployed_count := mini(4, company_before.size())
boss_snapshot.current_run.deployed_recruit_ids = company_before.slice(0, deployed_count)
boss_snapshot.current_run.reserve_recruit_ids = company_before.slice(deployed_count)
boss_snapshot.current_run.formation_proposal = company_before.slice(0, deployed_count)
var service := ContractService.new(boss_snapshot)
expect(service.valid, "bounded four-member Boss recovery fixture is valid")
if not service.valid:
return
var muster_before: Dictionary = service.state.current_run.boss_muster.duplicate(true)
var coin_before := int(service.state.current_run.coin)
var prepared := service.commit_boss_plan(
"fg3-boss-defeat-prepare",
service.state.revision,
String(muster_before.muster_hash),
"shift_hatch_area"
)
expect(prepared.ok, "legal area-shift plan prepares with a weak line")
var battle := BattleService.new(service.state.snapshot()).run_to_terminal("shift_hatch_area")
expect(battle.ok and battle.snapshot.outcome == "defeat" and battle.snapshot.fallen_recruit_ids.size() == 1, "state-dependent legal plan can fail with one casualty")
if not battle.ok:
return
var fallen_id := String(battle.snapshot.fallen_recruit_ids[0])
var settled := service.settle_boss(
"fg3-boss-defeat-settle",
service.state.revision,
battle.snapshot
)
expect(settled.ok and service.state.current_run.phase == "boss_recovery", "Boss defeat enters explicit recovery")
expect(fallen_id not in service.state.current_run.company and service.state.current_run.coin == coin_before, "Boss defeat permanently removes casualty and grants no Coin")
expect(service.state.current_run.boss_muster == muster_before, "defeat preserves exact disclosed Muster")
expect(service.state.current_run.replacement_credit > int(boss_snapshot.current_run.replacement_credit), "Boss casualty grants bounded Credit")
var recovered := service.recover_boss(
"fg3-boss-defeat-recover",
service.state.revision,
String(battle.snapshot.snapshot_hash),
22
)
expect(recovered.ok and service.state.current_run.phase == "boss_muster", "Boss recovery reopens plan selection")
expect(service.state.current_run.boss_muster == muster_before, "recovery never rerolls Boss facts")
expect(fallen_id not in service.state.current_run.company and service.state.current_run.company.size() == company_before.size() - 1, "recovery never resurrects or substitutes")
expect(service.state.current_run.coin == coin_before - 22 and service.state.battle_checkpoints.is_empty(), "Boss recovery charges exact cost and clears checkpoint")
func verify_boss_atomic_boundaries() -> void:
var service := ContractService.new(Fixture.new().boss_muster_snapshot())
var before := service.state.snapshot()
var invalid := service.commit_boss_plan(
"fg3-boss-invalid-plan",
service.state.revision,
String(service.state.current_run.boss_muster.muster_hash),
"rush_shell"
)
expect(not invalid.ok and invalid.code == "boss_plan_invalid" and service.state.snapshot() == before, "undeclared Boss plan rejects without mutation")
var prepared := service.commit_boss_plan(
"fg3-boss-atomic-prepare",
service.state.revision,
String(service.state.current_run.boss_muster.muster_hash),
"follow_index_clear_eggs"
)
expect(prepared.ok, "atomic Boss fixture prepares")
var battle := BattleService.new(service.state.snapshot()).run_to_terminal("follow_index_clear_eggs")
var prepared_snapshot := service.state.snapshot()
var tampered: Dictionary = battle.snapshot.duplicate(true)
tampered.cause = "forged"
var rejected := service.settle_boss("fg3-boss-tampered", service.state.revision, tampered)
expect(not rejected.ok and rejected.code == "battle_snapshot_mismatch" and service.state.snapshot() == prepared_snapshot, "tampered Boss result rejects against replay")
func event_count(events: Array, kind: String) -> int:
var count := 0
for event in events:
if String(event.get("kind", "")) == kind:
count += 1
return count
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("FG-3 Contract-1 Boss: %s" % message)