fix(runtime): enforce frozen guild response IDs

This commit is contained in:
2026-08-13 12:42:10 +08:00
parent f9f812a688
commit bd505d6188
4 changed files with 46 additions and 16 deletions
+24 -3
View File
@@ -2,6 +2,7 @@ extends Control
const FirstSessionStateScript = preload("res://domain/p9_1/session_state.gd")
const FirstSessionServiceScript = preload("res://domain/p9_1/session_service.gd")
const P91ContractsScript = preload("res://domain/p9_1/contracts.gd")
const PresentationSettingsScript = preload("res://presentation/presentation_settings.gd")
const BattleClockScript = preload("res://presentation/battle_clock.gd")
const AnimationAdapterScript = preload("res://presentation/animation_adapter.gd")
@@ -264,8 +265,8 @@ var second_result_time: float:
get: return battle_clock_owner.second_result_time
set(value): battle_clock_owner.second_result_time = value
var guild_choice: String:
get: return session_state.guild_choice
set(value): session_state.guild_choice = value
get: return legacy_guild_choice(session_state.guild_response)
set(value): set_legacy_guild_choice(value)
var guild_committed: bool:
get: return session_state.guild_committed
set(value): session_state.guild_committed = value
@@ -2263,12 +2264,32 @@ func open_guild_return() -> void:
func choose_guild_option(id: String) -> void:
if not session_service.choose_guild_response(id)["ok"]:
if not session_service.choose_guild_response(domain_guild_response(id))["ok"]:
return
play_cue("select")
render()
func domain_guild_response(id: String) -> String:
match id:
"retain_item": return "retain_loan"
"recruit_rear": return "add_recruit"
return id
func legacy_guild_choice(id: String) -> String:
match id:
"retain_loan": return "retain_item"
"add_recruit": return "recruit_rear"
return id
func set_legacy_guild_choice(id: String) -> void:
var response := domain_guild_response(id)
if response.is_empty() or response in P91ContractsScript.GUILD_RESPONSE_IDS:
session_state.guild_response = response
func begin_third_battle() -> void:
if not session_service.commit_guild_response()["ok"]:
return
+1 -1
View File
@@ -54,7 +54,7 @@ const ENCOUNTER_5: StringName = &"battle_5"
const AUTO_FORMATION_ORDER: Array[String] = ["Hearthguard", "Wayrunner", "Farstring", "Kindhand"]
const MEMBER_IDS: Array[String] = ["Hearthguard", "Wayrunner", "Farstring", "Kindhand"]
const ADAPTATION_IDS: Array[String] = ["EQ-002", "EQ-061"]
const GUILD_IDS: Array[String] = ["retain_item", "recruit_rear"]
const GUILD_RESPONSE_IDS: Array[String] = ["retain_loan", "add_recruit"]
const ROUTE_IDS: Array[String] = ["R6-01", "R6-02", "R6-03"]
const LEGACY_SCREEN_BY_PAGE := {
+8 -8
View File
@@ -88,7 +88,7 @@ func open_adaptation() -> Dictionary:
func open_guild_response() -> Dictionary:
if state.screen != Contracts.SCREEN_RESULT_2 or not state.adaptation_committed:
return _reject(Contracts.WRONG_SCREEN)
state.guild_choice = ""
state.guild_response = ""
state.guild_committed = false
state.retained_item = ""
state.added_rear = ""
@@ -159,12 +159,12 @@ func choose_guild_response(response_id: String) -> Dictionary:
return _reject(Contracts.WRONG_SCREEN)
if state.guild_committed:
return _reject(Contracts.ALREADY_COMMITTED)
if response_id not in Contracts.GUILD_IDS:
if response_id not in Contracts.GUILD_RESPONSE_IDS:
return _reject(Contracts.INVALID_ID)
var cost := 12 if response_id == "retain_item" else 14
var cost := 12 if response_id == "retain_loan" else 14
if state.coin < cost:
return _reject(Contracts.INSUFFICIENT_COIN)
state.guild_choice = response_id
state.guild_response = response_id
return _success([_selection_event(&"guild_response", response_id)])
func commit_guild_response() -> Dictionary:
@@ -172,14 +172,14 @@ func commit_guild_response() -> Dictionary:
return _reject(Contracts.WRONG_SCREEN)
if state.guild_committed:
return _reject(Contracts.ALREADY_COMMITTED)
if state.guild_choice not in Contracts.GUILD_IDS:
if state.guild_response not in Contracts.GUILD_RESPONSE_IDS:
return _reject(Contracts.SELECTION_REQUIRED)
var cost := 12 if state.guild_choice == "retain_item" else 14
var cost := 12 if state.guild_response == "retain_loan" else 14
if state.coin < cost:
return _reject(Contracts.INSUFFICIENT_COIN)
state.guild_committed = true
state.coin -= cost
if state.guild_choice == "retain_item":
if state.guild_response == "retain_loan":
state.retained_item = state.adaptation_choice
state.added_rear = ""
else:
@@ -190,7 +190,7 @@ func commit_guild_response() -> Dictionary:
state.front_hp = 100
state.enemy_hp = 130
state.phase_impact_committed.clear()
return _change_screen(Contracts.SCREEN_BATTLE_3, &"commit_guild_response", [_transaction_event(&"guild_response", state.guild_choice, -cost)])
return _change_screen(Contracts.SCREEN_BATTLE_3, &"commit_guild_response", [_transaction_event(&"guild_response", state.guild_response, -cost)])
func choose_route(route_id: String) -> Dictionary:
if state.screen != Contracts.SCREEN_ROUTE_BOARD:
+13 -4
View File
@@ -25,7 +25,12 @@ var coin := 0
var first_reward_committed := false
var adaptation_choice := ""
var adaptation_committed := false
var guild_choice := ""
var guild_response: String:
get: return _guild_response
set(value):
if value.is_empty() or value in Contracts.GUILD_RESPONSE_IDS:
_guild_response = value
var _guild_response := ""
var guild_committed := false
var retained_item := ""
var added_rear := ""
@@ -48,6 +53,8 @@ func compat_patch(fields: Dictionary) -> bool:
for field in fields:
if field not in _compat_patch_fields():
return false
if field == &"guild_response" and not String(fields[field]).is_empty() and fields[field] not in Contracts.GUILD_RESPONSE_IDS:
return false
for field in fields:
set(field, fields[field])
return invariants_hold()
@@ -59,7 +66,7 @@ func _compat_patch_fields() -> Array[StringName]:
&"battle_playing", &"front_hp", &"rear_hp", &"enemy_hp", &"guard_events",
&"rear_events", &"victory_events", &"phase_impact_committed", &"coin",
&"first_reward_committed", &"adaptation_choice", &"adaptation_committed",
&"guild_choice", &"guild_committed", &"retained_item", &"added_rear",
&"guild_response", &"guild_committed", &"retained_item", &"added_rear",
&"route_choice", &"route_committed", &"route_reward_committed", &"training_choice",
&"growth_member_id", &"training_committed", &"trained_member", &"trained_member_id",
&"fifth_reward_committed",
@@ -92,7 +99,7 @@ func reset_transient() -> void:
first_reward_committed = false
adaptation_choice = ""
adaptation_committed = false
guild_choice = ""
guild_response = ""
guild_committed = false
retained_item = ""
added_rear = ""
@@ -147,7 +154,7 @@ func snapshot() -> Dictionary:
"resources": {"coin": coin},
"decisions": {
"adaptation": adaptation_choice,
"guild_response": guild_choice,
"guild_response": guild_response,
"route": route_choice,
"growth_member": trained_member_id,
},
@@ -196,6 +203,8 @@ func invariants_hold() -> bool:
return false
if formation_commit_count > 1:
return false
if not guild_response.is_empty() and guild_response not in Contracts.GUILD_RESPONSE_IDS:
return false
if front_hp < 0 or rear_hp < 0 or enemy_hp < 0 or coin < 0:
return false
return true