148 lines
8.2 KiB
GDScript
148 lines
8.2 KiB
GDScript
extends SceneTree
|
|
|
|
const DraftService = preload("res://entry/initial_company_draft_service.gd")
|
|
const ContractService = preload("res://entry/guild_contract_service.gd")
|
|
const FormationService = preload("res://entry/formation_risk_service.gd")
|
|
const BattleService = preload("res://entry/battle_service.gd")
|
|
|
|
const DRAFT_PATH := "user://m12_outcome_draft.json"
|
|
const COMPANY_PATH := "user://m12_outcome_company.json"
|
|
const SEED := "2468ace013579bdf2468ace013579bdf"
|
|
const CONTRACT_OP := "abcdef0123456789abcdef0123456789"
|
|
const HIRE_OP_1 := "11112222333344445555666677778888"
|
|
const HIRE_OP_2 := "88887777666655554444333322221111"
|
|
const ORDER_OP := "fedcba9876543210fedcba9876543210"
|
|
const READY_OP := "0123456789abcdef0123456789abcdef"
|
|
const BATTLE_OP := "13579bdf02468ace13579bdf02468ace"
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
_cleanup()
|
|
var company := _create_prepared_company()
|
|
_expect(not company.is_empty(), "fixture prepares the first four-member Company")
|
|
if not company.is_empty():
|
|
var option_id := String(company.active_contract.formation.risk_options[1].risk_option_id)
|
|
var service := _battle_service()
|
|
var started: Dictionary = service.begin_battle(BATTLE_OP, option_id)
|
|
_expect(started.ok, "observation fixture starts one battle receipt")
|
|
if started.ok:
|
|
var opened: Dictionary = service.begin_observation(BATTLE_OP, String(started.battle.snapshot_hash))
|
|
_expect(opened.ok, "observation opens without mutating the outcome")
|
|
for _phase in range(9):
|
|
var preview: Dictionary = service.preview_observation_event(BATTLE_OP)
|
|
_expect(preview.ok, "phase preview remains deterministic")
|
|
if not preview.ok:
|
|
break
|
|
var event: Dictionary = preview.event
|
|
var committed: Dictionary = service.commit_observation_event(BATTLE_OP, String(event.event_id), String(event.event_hash))
|
|
_expect(committed.ok, "phase commit accepts the sourced event")
|
|
var locked: Dictionary = service.load_company()
|
|
_expect(locked.ok and String(locked.company.active_contract.battle_state.status) == "outcome_locked", "nine events stop at an immutable outcome lock")
|
|
var before_settle := _settlement_snapshot(locked.company)
|
|
var settled: Dictionary = service.settle_observation_outcome(BATTLE_OP)
|
|
_expect(settled.ok and settled.code == "outcome_settled", "OUT-001 settlement applies once")
|
|
if settled.ok:
|
|
var result: Dictionary = settled.company.active_contract.battle_state.result
|
|
var draft: Dictionary = settled.company.active_contract.battle_state.reward_draft
|
|
_expect(String(settled.company.active_contract.status) == "result", "settlement enters the Result state")
|
|
_expect(int(result.reward_coin) > 0 and int(result.xp_award) > 0, "settlement applies Coin and survivor XP")
|
|
_expect(String(draft.status) == "open" and draft.options.size() >= 2, "settlement opens a deterministic reward draft")
|
|
var duplicate_settle: Dictionary = service.settle_observation_outcome(BATTLE_OP)
|
|
_expect(duplicate_settle.ok and duplicate_settle.code == "outcome_already_settled", "duplicate settlement returns the original result")
|
|
_expect(duplicate_settle.ok and String(duplicate_settle.company.company_hash) == String(settled.company.company_hash), "duplicate settlement does not duplicate Coin, XP or history")
|
|
_expect(_settlement_snapshot(settled.company).coin > before_settle.coin, "battle Coin is paid before reward choice")
|
|
var chosen: Dictionary = draft.options[0]
|
|
var reward_op := ("%s|test-reward|%s" % [BATTLE_OP, String(chosen.reward_id)]).sha256_text().substr(0, 32)
|
|
var claimed: Dictionary = service.claim_reward(reward_op, String(chosen.reward_id), BATTLE_OP)
|
|
_expect(claimed.ok and claimed.code == "reward_claimed", "OUT-003 claims one sourced reward")
|
|
if claimed.ok:
|
|
var reward_receipts: Array = claimed.company.active_contract.get("reward_receipts", [])
|
|
_expect(reward_receipts.size() == 1, "reward claim writes one receipt")
|
|
var duplicate_claim: Dictionary = service.claim_reward(reward_op, String(chosen.reward_id), BATTLE_OP)
|
|
_expect(duplicate_claim.ok and duplicate_claim.code == "reward_already_applied", "duplicate reward claim returns the original receipt")
|
|
_expect(String(duplicate_claim.company.company_hash) == String(claimed.company.company_hash), "duplicate reward claim does not mutate the Company")
|
|
var recovery_op := ("%s|test-recovery" % BATTLE_OP).sha256_text().substr(0, 32)
|
|
var recovered: Dictionary = service.recover_next_stage(recovery_op)
|
|
_expect(recovered.ok and recovered.code == "recovered", "OUT-004 commits next-stage recovery")
|
|
if recovered.ok:
|
|
_expect(String(recovered.company.active_contract.status) == "market" and not recovered.company.active_contract.has("formation"), "recovery clears stale formation and opens Market")
|
|
_expect(recovered.company.active_contract.get("recovery_receipts", []).size() == 1, "recovery writes one receipt")
|
|
var duplicate_recovery: Dictionary = service.recover_next_stage(recovery_op)
|
|
_expect(duplicate_recovery.ok and duplicate_recovery.code == "recovery_already_applied", "duplicate recovery returns the original receipt")
|
|
_expect(_recruit_ids(recovered.company) == _recruit_ids(company), "recovery preserves every Recruit identity")
|
|
_cleanup()
|
|
if failures.is_empty():
|
|
print("ABG_M12_OUTCOME_RECOVERY_DOMAIN_OK lock=true settlement=once coin_xp=true reward_draft=true reward_claim=once recovery=once duplicate_safe=true identity=true")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func _create_prepared_company() -> Dictionary:
|
|
var draft_service := DraftService.new()
|
|
draft_service.path = DRAFT_PATH
|
|
draft_service.company_path = COMPANY_PATH
|
|
var draft: Dictionary = draft_service.begin_or_load(SEED)
|
|
if not draft.ok:
|
|
return {}
|
|
draft_service.set_selection([String(draft.draft.offers[0].offer_id), String(draft.draft.offers[1].offer_id)])
|
|
var committed: Dictionary = draft_service.commit_company()
|
|
if not committed.ok:
|
|
return {}
|
|
var contract_service := ContractService.new()
|
|
contract_service.company_path = COMPANY_PATH
|
|
var created: Dictionary = contract_service.create_contract(CONTRACT_OP, "CTR-R1-01", "standard")
|
|
if not created.ok:
|
|
return {}
|
|
var offers: Array = created.company.active_contract.market_board.recruit_offers
|
|
contract_service.hire_recruit(HIRE_OP_1, String(offers[0].source_offer_id), 14)
|
|
var hired: Dictionary = contract_service.hire_recruit(HIRE_OP_2, String(offers[1].source_offer_id), 14)
|
|
if not hired.ok:
|
|
return {}
|
|
var formation_service := FormationService.new()
|
|
formation_service.company_path = COMPANY_PATH
|
|
var begun: Dictionary = formation_service.begin_formation()
|
|
var saved: Dictionary = formation_service.save_order(ORDER_OP, begun.formation.base_front_to_rear_ids)
|
|
var ready: Dictionary = formation_service.accept_readiness(READY_OP, String(saved.receipt.order_hash))
|
|
return ready.company if ready.ok else {}
|
|
|
|
|
|
func _battle_service() -> RefCounted:
|
|
var service := BattleService.new()
|
|
service.company_path = COMPANY_PATH
|
|
return service
|
|
|
|
|
|
func _settlement_snapshot(company: Dictionary) -> Dictionary:
|
|
var contract: Dictionary = company.active_contract
|
|
var recruits: Array = []
|
|
for recruit in company.owned_recruits:
|
|
recruits.append({"id": String(recruit.recruit_id), "level": int(recruit.level), "xp": int(recruit.xp), "history": Dictionary(recruit.battle_history).duplicate(true)})
|
|
return {"coin": int(contract.coin), "recruits": recruits, "settlements": Array(contract.get("battle_settlement_receipts", [])).duplicate(true), "rewards": Array(contract.get("reward_receipts", [])).duplicate(true)}
|
|
|
|
|
|
func _recruit_ids(company: Dictionary) -> Array[String]:
|
|
var ids: Array[String] = []
|
|
for recruit in company.get("owned_recruits", []):
|
|
ids.append(String(recruit.recruit_id))
|
|
return ids
|
|
|
|
|
|
func _cleanup() -> void:
|
|
for path in [DRAFT_PATH, "%s.bak" % DRAFT_PATH, "%s.candidate" % DRAFT_PATH, "%s.bak.candidate" % DRAFT_PATH, COMPANY_PATH, "%s.bak" % COMPANY_PATH, "%s.candidate" % COMPANY_PATH, "%s.bak.candidate" % COMPANY_PATH, "%s.corrupt" % COMPANY_PATH]:
|
|
if FileAccess.file_exists(path):
|
|
DirAccess.remove_absolute(path)
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("M12 outcome/recovery: %s" % message)
|