extends SceneTree const Controller = preload("res://entry/risk_commitment_page_controller.gd") var failures: Array[String] = [] func _initialize() -> void: call_deferred("run_test") func run_test() -> void: var controller := Controller.new() var company := _fixture_company() var formation := _fixture_formation() var option: Dictionary = formation.risk_options[1] var facts := _fixture_consequences() var blocked_dependency := {"ok": false, "code": "INSUFFICIENT_COIN", "coin": 2, "cost": 4} var valid_dependency := {"ok": true, "code": "READY", "coin": 38, "cost": 4} var binding := {"selected_risk_id": "risk-2", "snapshot_hash": "snapshot-hash-001", "option_hash": "option-hash-001", "contract_hash": "contract-hash-001", "stale": false} var blocked: Dictionary = controller.snapshot(company, formation, option, facts, blocked_dependency, binding, false, "en") expect(blocked.ok and blocked.page_id == "risk_commitment", "snapshot identifies Risk Commitment") expect(blocked.company.owned_count == 4 and blocked.company.line_capacity == 4, "snapshot copies Company capacity") expect(blocked.contract.contract_id == "CTR-R1-01" and blocked.contract.coin == 38, "snapshot copies Contract facts") expect(blocked.risk.risk_option_id == "risk-2" and blocked.risk.option_hash == "option-hash-001", "snapshot copies selected risk identity") expect(blocked.consequences.count == 6 and blocked.consequences.facts[0].text == "THREAT 3 · 2 WAVES", "snapshot copies six consequence facts") expect(blocked.party.snapshot_id == "snapshot-001" and blocked.party.front_to_rear_ids == ["r-1", "r-2", "r-3", "r-4"], "snapshot copies frozen Party identity") expect(blocked.party.ordered_recruits[0].recruit.recruit_id == "r-1" and blocked.party.ordered_recruits[0].relation.first_threat_target, "snapshot binds Recruit and relation facts") expect(blocked.dependency.code == "INSUFFICIENT_COIN" and blocked.binding.state == "valid", "snapshot copies dependency and binding state") expect(blocked.confirmation.state == "required" and not blocked.commands.begin_enabled and blocked.commands.acknowledge_enabled, "blocked state requires acknowledgement/dependency") var ready: Dictionary = controller.snapshot(company, formation, option, facts, valid_dependency, binding, true, "zh_CN") expect(ready.ok and ready.confirmation.acknowledged and ready.commands.begin_enabled, "acknowledged valid state enables Begin Battle") expect(not ready.commands.acknowledge_enabled, "acknowledged valid state locks duplicate acknowledgement") var stale_binding := binding.duplicate(true) stale_binding.stale = true var stale: Dictionary = controller.snapshot(company, formation, option, facts, valid_dependency, stale_binding, true, "en") expect(stale.ok and stale.binding.state == "stale" and not stale.commands.begin_enabled, "stale binding blocks Begin Battle") var missing_option: Dictionary = controller.snapshot(company, formation, {}, facts, valid_dependency, binding) expect(not missing_option.ok and missing_option.code == "COMMITMENT_OPTION_REQUIRED", "missing option is rejected") var stale_option := option.duplicate(true) stale_option.risk_option_id = "missing" var rejected_option: Dictionary = controller.snapshot(company, formation, stale_option, facts, valid_dependency, binding) expect(not rejected_option.ok and rejected_option.code == "COMMITMENT_OPTION_STALE", "unknown option is rejected") var no_snapshot := _fixture_formation() no_snapshot.accepted_party_snapshot = {} var rejected_snapshot: Dictionary = controller.snapshot(company, no_snapshot, option, facts, valid_dependency, binding) expect(not rejected_snapshot.ok and rejected_snapshot.code == "COMMITMENT_PARTY_SNAPSHOT_REQUIRED", "missing Party snapshot is rejected") var short_facts: Array = facts.slice(0, 5) var rejected_facts: Dictionary = controller.snapshot(company, formation, option, short_facts, valid_dependency, binding) expect(not rejected_facts.ok and rejected_facts.code == "COMMITMENT_CONSEQUENCES_INVALID", "six consequence boundary is enforced") var missing: Dictionary = controller.snapshot({}, formation, option, facts, valid_dependency, binding) expect(not missing.ok and missing.code == "COMPANY_REQUIRED", "missing Company is rejected") var copied_option: Dictionary = ready.risk.option copied_option.known_threat.pressure_tags.append("changed") ready.consequences.facts[0].text = "changed" ready.party.relations[0].protected_by_recruit_ids.append("changed") expect(formation.risk_options[1].known_threat.pressure_tags == ["surge"], "selected risk facts are deep copied") expect(facts[0].text == "THREAT 3 · 2 WAVES", "consequence facts are deep copied") expect(formation.relations[0].protected_by_recruit_ids.is_empty(), "Party relation facts are deep copied") if failures.is_empty(): print("ABG_M24_RISK_COMMITMENT_CONTROLLER_OK company=4 party=4 option=selected consequences=6 dependency=blocked,ready confirmation=normalized binding=stale_guard copies=true") quit(0) else: for failure in failures: push_error(failure) quit(1) func _fixture_company() -> Dictionary: return { "company_id": "company-001", "owned_recruits": [ {"recruit_id": "r-1", "profession_id": "PF-B01", "rendered_name_snapshot": {"display_name": "Ari"}}, {"recruit_id": "r-2", "profession_id": "PF-B05", "rendered_name_snapshot": {"display_name": "Bram"}}, {"recruit_id": "r-3", "profession_id": "PF-B07", "rendered_name_snapshot": {"display_name": "Cera"}}, {"recruit_id": "r-4", "profession_id": "PF-B03", "rendered_name_snapshot": {"display_name": "Dain"}}, ], "active_contract": { "contract_id": "CTR-R1-01", "run_id": "run-001", "difficulty": "stormbound", "coin": 38, "line_capacity": 4, "contract_hash": "contract-hash-001", }, } func _fixture_formation() -> Dictionary: return { "status": "ready", "orientation": "rear_left_front_right", "front_position": 0, "line_capacity": 4, "saved_front_to_rear_ids": ["r-1", "r-2", "r-3", "r-4"], "relations": [ {"position": 0, "recruit_id": "r-1", "first_threat_target": true, "protected_by_recruit_ids": []}, {"position": 1, "recruit_id": "r-2", "first_threat_target": false, "protected_by_recruit_ids": ["r-1"]}, {"position": 2, "recruit_id": "r-3", "first_threat_target": false, "protected_by_recruit_ids": ["r-1"]}, {"position": 3, "recruit_id": "r-4", "first_threat_target": false, "protected_by_recruit_ids": []}, ], "coverage": {"protection": 2, "long_reach": 2, "sustain": 1, "control": 1}, "risk_options": [ {"risk_option_id": "risk-1", "risk": 0, "option_hash": "option-hash-000", "known_threat": {"enemy_count": 2, "wave_count": 1, "pressure_tags": []}, "uncertainty_id": "disclosed", "entry_cost": 0, "reward_coin": 20, "reward_class_id": "standard_supplies", "recovery_exposure_id": "low"}, {"risk_option_id": "risk-2", "risk": 1, "option_hash": "option-hash-001", "known_threat": {"enemy_count": 3, "wave_count": 2, "pressure_tags": ["surge"]}, "uncertainty_id": "one_intent_unresolved", "entry_cost": 4, "reward_coin": 27, "reward_class_id": "enhanced_choice", "recovery_exposure_id": "moderate"}, {"risk_option_id": "risk-3", "risk": 2, "option_hash": "option-hash-002", "known_threat": {"enemy_count": 4, "wave_count": 3, "pressure_tags": ["elite"]}, "uncertainty_id": "elite_response_unresolved", "entry_cost": 8, "reward_coin": 34, "reward_class_id": "elite_cache", "recovery_exposure_id": "high"}, ], "accepted_party_snapshot": { "snapshot_id": "snapshot-001", "snapshot_hash": "snapshot-hash-001", "order_hash": "order-hash-001", "front_to_rear_ids": ["r-1", "r-2", "r-3", "r-4"], "relations": [ {"position": 0, "recruit_id": "r-1", "first_threat_target": true, "protected_by_recruit_ids": []}, {"position": 1, "recruit_id": "r-2", "first_threat_target": false, "protected_by_recruit_ids": ["r-1"]}, {"position": 2, "recruit_id": "r-3", "first_threat_target": false, "protected_by_recruit_ids": ["r-1"]}, {"position": 3, "recruit_id": "r-4", "first_threat_target": false, "protected_by_recruit_ids": []}, ], "coverage": {"protection": 2, "long_reach": 2, "sustain": 1, "control": 1}, }, } func _fixture_consequences() -> Array: return [ {"id": "threat", "label": "THREAT", "text": "THREAT 3 · 2 WAVES", "value": "3 · 2 WAVES"}, {"id": "uncertainty", "label": "UNCERTAINTY", "text": "ONE INTENT UNRESOLVED", "value": "ONE INTENT"}, {"id": "entry", "label": "ENTRY COIN", "text": "4 · 34 LEFT", "value": "4 · 34 LEFT"}, {"id": "reward", "label": "REWARD", "text": "27 · ENHANCED", "value": "27 · ENHANCED"}, {"id": "recovery", "label": "RECOVERY", "text": "MODERATE", "value": "MODERATE"}, {"id": "retreat", "label": "RETREAT", "text": "NEXT STAGE", "value": "NEXT STAGE"}, ] func expect(condition: bool, message: String) -> void: if not condition: failures.append("M24 Risk Commitment Controller: %s" % message)