P06-01: Implement approved A06 finite Mana reserve (MP-01-r02 recommended)
Initial 100, capacity 300, world tree +20 per 10 game minutes. Each boundary pays registered upkeep demand before storing surplus; overflow is discarded and recorded, 0 stops the whole tower. Furnace offers are judged after generation and demand payment. New saves carry mana_reserve and Operations rollback includes it; legacy saves keep a 0..300 amount and reject others. Outage tests that relied on Mana 0 persisting now hold the outage with a disabled demand sink, since the world tree recovers at the next boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c366012446
commit
ae0f998ff4
@@ -29,6 +29,7 @@ const AutoPurchasePlansType := preload("res://scripts/supply/supply_auto_purchas
|
||||
const CLEANING_RUNTIME_SNAPSHOT_VERSION := 12
|
||||
const WEATHER_RUNTIME_SNAPSHOT_VERSION := 11
|
||||
const FacilityCleanliness = preload("res://scripts/environment/facility_cleanliness_service.gd")
|
||||
const ManaReserveCycle = preload("res://scripts/mana/mana_reserve_cycle.gd")
|
||||
const CleaningBridge = preload("res://scripts/environment/facility_cleaning_bridge.gd")
|
||||
const CleaningPolicy = preload("res://scripts/environment/facility_cleaning_policy.gd")
|
||||
const UPKEEP_RUNTIME_SNAPSHOT_VERSION := 10
|
||||
@@ -108,6 +109,7 @@ var facility_cleanliness := FacilityCleanliness.new()
|
||||
var cleaning_policy := CleaningPolicy.new()
|
||||
var cleaning_bridge := CleaningBridge.new()
|
||||
var facility_upkeep := FacilityUpkeepAccrual.new()
|
||||
var mana_reserve := ManaReserveCycle.new()
|
||||
var upkeep_settlement := FacilityUpkeepSettlement.new()
|
||||
var economy_service: EconomyService
|
||||
var construction_resource_state: LedgerBackedResourceState
|
||||
@@ -539,6 +541,7 @@ func create_snapshot() -> Dictionary:
|
||||
&"path": path_service.create_snapshot(),
|
||||
&"movement_credits": _serialize_movement_credits(),
|
||||
&"mana": mana_network_service.create_snapshot(),
|
||||
&"mana_reserve": mana_reserve.create_snapshot(),
|
||||
&"economy": economy_service.create_snapshot(),
|
||||
&"facility_upkeep": facility_upkeep.create_snapshot(),
|
||||
&"upkeep_settlement": upkeep_settlement.create_snapshot(),
|
||||
@@ -676,6 +679,8 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
runtime_current_keys.append(&"auto_purchase_plans")
|
||||
if runtime_data.has(&"special_peeping_rooms"):
|
||||
runtime_current_keys.append(&"special_peeping_rooms")
|
||||
if runtime_data.has(&"mana_reserve"):
|
||||
runtime_current_keys.append(&"mana_reserve")
|
||||
if not _dictionary_has_exact_canonical_keys(runtime_data, runtime_current_keys) \
|
||||
or typeof(runtime_data.get(&"runtime_schema_version", null)) != TYPE_INT \
|
||||
or typeof(runtime_data.get(&"world_seed", null)) != TYPE_INT \
|
||||
@@ -857,6 +862,23 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
var restored_mana := ManaNetworkService.new()
|
||||
if not restored_mana.restore_snapshot(runtime_data[&"mana"]):
|
||||
return false
|
||||
# A06: saves without the finite reserve keep an explicit stored amount
|
||||
# (0 stays stopped) only within capacity; nothing is silently truncated.
|
||||
var restored_mana_reserve := ManaReserveCycle.new()
|
||||
var saved_game_resources: Variant = snapshot[&"game_state"].get(&"resources", null)
|
||||
if not saved_game_resources is Dictionary \
|
||||
or not ManaReserveCycle.amount_is_valid(saved_game_resources.get(&"mana", saved_game_resources.get("mana", null))):
|
||||
return false
|
||||
if runtime_data.has(&"mana_reserve"):
|
||||
if not restored_mana_reserve.restore_snapshot(runtime_data[&"mana_reserve"]) \
|
||||
or restored_mana_reserve.get_minute() > restored_minute:
|
||||
return false
|
||||
var last_reserve_flow := restored_mana_reserve.get_last_flow()
|
||||
if not last_reserve_flow.is_empty() \
|
||||
and int(saved_game_resources.get(&"mana", saved_game_resources.get("mana", 0))) > int(last_reserve_flow[&"after"]):
|
||||
return false
|
||||
elif not restored_mana_reserve.start_at(restored_minute):
|
||||
return false
|
||||
var restored_economy := EconomyService.new()
|
||||
if not restored_economy.restore_snapshot(runtime_data[&"economy"]):
|
||||
return false
|
||||
@@ -1325,6 +1347,7 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
_movement_credits = (restored_movement_credits as Dictionary).duplicate(true)
|
||||
_connect_path_performance_signal()
|
||||
mana_network_service = restored_mana
|
||||
mana_reserve = restored_mana_reserve
|
||||
weather = restored_weather
|
||||
facility_cleanliness = restored_cleanliness
|
||||
facility_upkeep = restored_upkeep
|
||||
@@ -2054,6 +2077,7 @@ func _build_operations_external_state() -> Dictionary:
|
||||
&"personal_action_points": _personal_action_points,
|
||||
# The remaining aggregates are reachable through Event actions or signals.
|
||||
&"mana": mana_network_service.create_snapshot(),
|
||||
&"mana_reserve": mana_reserve.create_snapshot(),
|
||||
&"logistics_dispatcher": logistics_dispatcher.create_snapshot(),
|
||||
&"logistics_requests": _serialize_logistics_requests(),
|
||||
&"defense": defense_service.create_snapshot(),
|
||||
@@ -2091,7 +2115,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
|
||||
&"excavation", &"spatial_subslots", &"route_projection",
|
||||
&"facility_infrastructure",
|
||||
&"movement_credits", &"domain_events", &"id_generator", &"rng_streams",
|
||||
&"progression", &"hired_staff_candidate_ids", &"mana",
|
||||
&"progression", &"hired_staff_candidate_ids", &"mana", &"mana_reserve",
|
||||
&"logistics_dispatcher", &"defense", &"construction",
|
||||
]
|
||||
for key: StringName in required_dictionary_keys:
|
||||
@@ -2152,6 +2176,7 @@ func _restore_operations_external_state(token: Variant) -> bool:
|
||||
var ids_ok := id_generator.restore_snapshot(saved[&"id_generator"])
|
||||
var progression_ok := progression_service.restore_snapshot(saved[&"progression"])
|
||||
var mana_ok := mana_network_service.restore_snapshot(saved[&"mana"])
|
||||
mana_ok = mana_reserve.restore_snapshot(saved[&"mana_reserve"]) and mana_ok
|
||||
var dispatcher_ok := logistics_dispatcher.restore_snapshot(saved[&"logistics_dispatcher"])
|
||||
var defense_ok := defense_service.restore_snapshot(saved[&"defense"])
|
||||
var construction_ok := construction_service.restore_snapshot(saved[&"construction"])
|
||||
@@ -2258,6 +2283,7 @@ func _operations_external_state_matches(saved: Dictionary) -> bool:
|
||||
or not _strict_variants_equal(id_generator.create_snapshot(), saved[&"id_generator"]) \
|
||||
or not _strict_variants_equal(progression_service.create_snapshot(), saved[&"progression"]) \
|
||||
or not _strict_variants_equal(mana_network_service.create_snapshot(), saved[&"mana"]) \
|
||||
or not _strict_variants_equal(mana_reserve.create_snapshot(), saved[&"mana_reserve"]) \
|
||||
or not _strict_variants_equal(logistics_dispatcher.create_snapshot(), saved[&"logistics_dispatcher"]) \
|
||||
or not _strict_variants_equal(_serialize_logistics_requests(), saved[&"logistics_requests"]) \
|
||||
or not _strict_variants_equal(defense_service.create_snapshot(), saved[&"defense"]) \
|
||||
@@ -5226,6 +5252,8 @@ func _initialize_economy() -> void:
|
||||
upkeep_settlement = FacilityUpkeepSettlement.new()
|
||||
FacilityUpkeepSettlement.prepare_accounts(economy_service)
|
||||
facility_upkeep.start_at(_clock.absolute_minute)
|
||||
mana_reserve = ManaReserveCycle.new()
|
||||
mana_reserve.start_at(_clock.absolute_minute)
|
||||
construction_resource_state = _create_resource_adapter(
|
||||
economy_service,
|
||||
CONSTRUCTION_EXPENSE_ACCOUNT,
|
||||
@@ -10729,6 +10757,21 @@ func _advance_mana(absolute_minute: int) -> void:
|
||||
infrastructure_preflight.get(&"details", {})
|
||||
)
|
||||
return
|
||||
# A06: generation and upkeep demand settle before local distribution and the
|
||||
# whole-tower gate, so the network and projection see the committed reserve.
|
||||
var reserve_flow := mana_reserve.advance_to(
|
||||
absolute_minute, int(_state.get_resource(&"mana")), mana_network_service.get_total_consumer_demand()
|
||||
)
|
||||
if not bool(reserve_flow.get(&"ok", false)):
|
||||
_latch_infrastructure_failure(
|
||||
StringName(reserve_flow.get(&"code", &"MANA_RESERVE_FAILED")),
|
||||
absolute_minute,
|
||||
"The finite Mana reserve could not settle this boundary.",
|
||||
{&"mana": int(_state.get_resource(&"mana"))}
|
||||
)
|
||||
return
|
||||
if bool(reserve_flow.get(&"processed", false)):
|
||||
_state.set_resource(&"mana", int(reserve_flow[&"after"]))
|
||||
var report := mana_network_service.advance_one_minute(absolute_minute)
|
||||
# Finite reserve is an additional gate, even between network evaluations and
|
||||
# for zero-demand facilities. The network remains the local supply authority.
|
||||
|
||||
Reference in New Issue
Block a user