P06-01: Fix review r01 findings for A06 reserve
Settle Mana raised above capacity between boundaries as recorded overflow instead of rejecting saves or latching a fault, exclude destroyed facilities from upkeep demand, and keep the depletion drill's outage sustained. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
65534cfd15
commit
f247d19325
@@ -99,6 +99,7 @@ test-s6:
|
|||||||
|
|
||||||
test-mvp:
|
test-mvp:
|
||||||
$(GODOT) --headless --path . --script res://tests/mvp/test_premium_room_guest_flow.gd
|
$(GODOT) --headless --path . --script res://tests/mvp/test_premium_room_guest_flow.gd
|
||||||
|
$(GODOT) --headless --path . --script res://tests/mvp/test_runtime_mana_depletion.gd
|
||||||
$(GODOT) --headless --path . --script res://tests/mvp/test_premium_room.gd
|
$(GODOT) --headless --path . --script res://tests/mvp/test_premium_room.gd
|
||||||
$(GODOT) --headless --path . --script res://tests/mvp/test_original_room_zones.gd
|
$(GODOT) --headless --path . --script res://tests/mvp/test_original_room_zones.gd
|
||||||
$(GODOT) --headless --path . --script res://tests/mvp/test_upkeep_loss_streak.gd
|
$(GODOT) --headless --path . --script res://tests/mvp/test_upkeep_loss_streak.gd
|
||||||
|
|||||||
@@ -866,18 +866,18 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
|||||||
# (0 stays stopped) only within capacity; nothing is silently truncated.
|
# (0 stays stopped) only within capacity; nothing is silently truncated.
|
||||||
var restored_mana_reserve := ManaReserveCycle.new()
|
var restored_mana_reserve := ManaReserveCycle.new()
|
||||||
var saved_game_resources: Variant = snapshot[&"game_state"].get(&"resources", null)
|
var saved_game_resources: Variant = snapshot[&"game_state"].get(&"resources", null)
|
||||||
if not saved_game_resources is Dictionary \
|
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
|
return false
|
||||||
|
var saved_mana: Variant = saved_game_resources.get(&"mana", saved_game_resources.get("mana", null))
|
||||||
if runtime_data.has(&"mana_reserve"):
|
if runtime_data.has(&"mana_reserve"):
|
||||||
if not restored_mana_reserve.restore_snapshot(runtime_data[&"mana_reserve"]) \
|
# Between boundaries Mana may exceed capacity; the next boundary records
|
||||||
|
# and discards the excess, so only a valid integer amount is required.
|
||||||
|
if not ManaReserveCycle.stored_amount_is_valid(saved_mana) \
|
||||||
|
or not restored_mana_reserve.restore_snapshot(runtime_data[&"mana_reserve"]) \
|
||||||
or restored_mana_reserve.get_minute() > restored_minute:
|
or restored_mana_reserve.get_minute() > restored_minute:
|
||||||
return false
|
return false
|
||||||
var last_reserve_flow := restored_mana_reserve.get_last_flow()
|
elif not ManaReserveCycle.amount_is_valid(saved_mana) \
|
||||||
if not last_reserve_flow.is_empty() \
|
or not restored_mana_reserve.start_at(restored_minute):
|
||||||
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
|
return false
|
||||||
var restored_economy := EconomyService.new()
|
var restored_economy := EconomyService.new()
|
||||||
if not restored_economy.restore_snapshot(runtime_data[&"economy"]):
|
if not restored_economy.restore_snapshot(runtime_data[&"economy"]):
|
||||||
@@ -10741,6 +10741,16 @@ func _reservation_references_are_valid(
|
|||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
## A06 upkeep demand: every owned consumer, open or not, except destroyed
|
||||||
|
## facilities. Removed facilities are already unregistered from the network.
|
||||||
|
func _mana_reserve_demand() -> int:
|
||||||
|
var destroyed: Array[StringName] = []
|
||||||
|
for facility: FacilityInstance in facility_service.get_instances():
|
||||||
|
if facility.suspension_reason == &"DESTROYED":
|
||||||
|
destroyed.append(facility.instance_id)
|
||||||
|
return mana_network_service.get_total_consumer_demand(destroyed)
|
||||||
|
|
||||||
|
|
||||||
func _advance_mana(absolute_minute: int) -> void:
|
func _advance_mana(absolute_minute: int) -> void:
|
||||||
if _operations_tick_blocked:
|
if _operations_tick_blocked:
|
||||||
return
|
return
|
||||||
@@ -10760,7 +10770,7 @@ func _advance_mana(absolute_minute: int) -> void:
|
|||||||
# A06: generation and upkeep demand settle before local distribution and the
|
# A06: generation and upkeep demand settle before local distribution and the
|
||||||
# whole-tower gate, so the network and projection see the committed reserve.
|
# whole-tower gate, so the network and projection see the committed reserve.
|
||||||
var reserve_flow := mana_reserve.advance_to(
|
var reserve_flow := mana_reserve.advance_to(
|
||||||
absolute_minute, int(_state.get_resource(&"mana")), mana_network_service.get_total_consumer_demand()
|
absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand()
|
||||||
)
|
)
|
||||||
if not bool(reserve_flow.get(&"ok", false)):
|
if not bool(reserve_flow.get(&"ok", false)):
|
||||||
_latch_infrastructure_failure(
|
_latch_infrastructure_failure(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
{
|
{
|
||||||
"id": "A06",
|
"id": "A06",
|
||||||
"title": "マナ有限残量契約(MP-01-r02推薦案)",
|
"title": "マナ有限残量契約(MP-01-r02推薦案)",
|
||||||
"decision": "初期100・容量300・世界樹生成20/ゲーム内10分。10分境界ごとにB+G+F−Dを計算し、需要を先に支払い、超過は明示破棄、0で全館停止。需要は登録済み全consumerのmaintenance_mana合計(閉店・停止中も計上)。炉の投入は自然生成と需要支払後のbase_after=min(C,max(0,B+G−D))で空き100以上のとき受理。現Runtimeのphase順序を保持しMANA内で処理。旧セーブの残量は0〜300のみ保持し、超過・不正値は拒否。炉の人物適格性・収率の実接続は別承認。",
|
"decision": "初期100・容量300・世界樹生成20/ゲーム内10分。10分境界ごとにB+G+F−Dを計算し、需要を先に支払い、超過は明示破棄、0で全館停止。需要は保有施設のmaintenance_mana合計(閉店・停止中も計上、撤去・破壊済みは除外)。境界間に返却等で容量を超えた分は次の境界でoverflowとして記録・破棄。炉の投入は自然生成と需要支払後のbase_after=min(C,max(0,B+G−D))で空き100以上のとき受理。現Runtimeのphase順序を保持しMANA内で処理。受入契約なしの旧セーブの残量は0〜300のみ保持し、超過・不正値は拒否。炉の人物適格性・収率の実接続は別承認。",
|
||||||
"origin": "user_reply_in_conversation",
|
"origin": "user_reply_in_conversation",
|
||||||
"approved_at_jst": "2026-10-19",
|
"approved_at_jst": "2026-10-19",
|
||||||
"reference": "execution/mana-proposal/PROPOSAL.md"
|
"reference": "execution/mana-proposal/PROPOSAL.md"
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
|
|
||||||
## 製品への反映
|
## 製品への反映
|
||||||
|
|
||||||
- `scripts/mana/mana_reserve_cycle.gd`:承認値と境界処理です。10分境界ごとに `available=B+G+F`、`consumed=min(available,D)`、`after=min(C,available−consumed)` を計算します。容量を超えた分は overflow として記録し、破棄します。同じ境界は二度処理しません。境界を飛ばした場合は、各境界を順番に処理します。保存データは検証が通ってから反映し、不正なら何も変えません。炉の受理判定は `furnace_offer_accepted`(base_after 後の空きが100以上)です。
|
- `scripts/mana/mana_reserve_cycle.gd`:承認値と境界処理です。10分境界ごとに `available=B+G+F`、`consumed=min(available,D)`、`after=min(C,available−consumed)` を計算します。容量を超えた分は overflow として記録し、破棄します。イベント費用の返却などで境界の間に容量を超えた場合も、次の境界で同じく処理します。同じ境界は二度処理しません。境界を飛ばした場合は、各境界を順番に処理します。保存データは検証が通ってから反映し、不正なら何も変えません。炉の受理判定は `furnace_offer_accepted`(base_after 後の空きが100以上)です。
|
||||||
- `scripts/mana/mana_network_service.gd`:`get_total_consumer_demand()` を追加しました。登録済みの全 consumer の需要を合計します。無効化・停止中の consumer も含みます。
|
- `scripts/mana/mana_network_service.gd`:`get_total_consumer_demand(excluded_ids)` を追加しました。登録済みの consumer の需要を合計します。無効化・停止中の consumer も含みます。Runtime の `_mana_reserve_demand()` が、破壊済み(DESTROYED)施設を除外して呼び出します。撤去済みの施設はネットワークから登録解除されます。
|
||||||
- `autoload/simulation_runtime.gd`:MANA phase の最初で残量を確定してから、局所配電と全館ゲートを評価します。phase の順序は変えていません。新しいセーブは `mana_reserve` を持ちます。Operations の巻き戻し対象にも含めました。
|
- `autoload/simulation_runtime.gd`:MANA phase の最初で残量を確定してから、局所配電と全館ゲートを評価します。phase の順序は変えていません。新しいセーブは `mana_reserve` を持ちます。Operations の巻き戻し対象にも含めました。
|
||||||
|
- 現行セーブは整数の残量を受け付けます。境界の間に容量を超えた値も読み込めます。
|
||||||
- 旧セーブの移行:`mana_reserve` が無いセーブは、残量が0〜300の整数であれば、その値を保持して開始します。明示的な0は停止のまま保持します。300超・負値・非整数は拒否し、現在の状態を保持します。
|
- 旧セーブの移行:`mana_reserve` が無いセーブは、残量が0〜300の整数であれば、その値を保持して開始します。明示的な0は停止のまま保持します。300超・負値・非整数は拒否し、現在の状態を保持します。
|
||||||
|
|
||||||
## 試験
|
## 試験
|
||||||
@@ -18,3 +19,16 @@
|
|||||||
|
|
||||||
- 炉の人物適格性・収率の実接続、未定義13施設の需要、D05 の詳細は別承認です。
|
- 炉の人物適格性・収率の実接続、未定義13施設の需要、D05 の詳細は別承認です。
|
||||||
- 全館の実プレイ、性能、Windows での受入は未実施です。原88の正式完了数は変わりません。
|
- 全館の実プレイ、性能、Windows での受入は未実施です。原88の正式完了数は変わりません。
|
||||||
|
|
||||||
|
## 独立レビュー r01 の指摘と修正
|
||||||
|
|
||||||
|
候補 65534cf への判定は FAIL でした。修正した内容は次のとおりです。
|
||||||
|
|
||||||
|
1. 境界の間にマナが増えた場合の不整合を直しました。以前は、読込を拒否するか、毎分 INVALID_FLOW を起こして全館が停止していました。現在は、次の境界で overflow として記録・破棄します。
|
||||||
|
2. `test_runtime_mana_depletion` は停電が続く前提でした。`ManaOutage` に置き換え、旧セーブも `mana_reserve` を外して作るようにしました。また、Makefile の test-mvp に追加しました。
|
||||||
|
3. 需要に破壊済みの施設が含まれていました。提案書の「撤去・破壊済みは除外」に合わせて除外し、A06 の記録文も同じ内容に訂正しました。
|
||||||
|
|
||||||
|
## 未検証として残す点
|
||||||
|
|
||||||
|
- 世界樹の生成を0にする経路が無いため、提案書の比較表にある「生成0→0→通常」は適用外です。
|
||||||
|
- 炉の receipt と配電投影の保存は、炉の実接続が別承認なので対象外です。
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
"build_atomic_requirements.py": "6d6ea9e4216372dbd0f9fed3f885a4481b21a3f539a26f64cf98a6830af984c5",
|
"build_atomic_requirements.py": "6d6ea9e4216372dbd0f9fed3f885a4481b21a3f539a26f64cf98a6830af984c5",
|
||||||
"build_facility_value_contracts.py": "35027b23ce75ce9e4e0cdfc5132fd2eb771ba9f598d97750d7ecf9ecb257f769",
|
"build_facility_value_contracts.py": "35027b23ce75ce9e4e0cdfc5132fd2eb771ba9f598d97750d7ecf9ecb257f769",
|
||||||
"current_evidence_links.json": "db1837bcc8497201134297e312f4b266a297102285307ec3075d4e23632e26c2",
|
"current_evidence_links.json": "db1837bcc8497201134297e312f4b266a297102285307ec3075d4e23632e26c2",
|
||||||
"decisions.json": "927065d9f840e68667d10998d26eb5d04f5591d9f24082106d082e51d5af5c96",
|
"decisions.json": "7771ef179b73f061363685a218f7b0d300847fc5881ce0c8b108ca62b2076dfe",
|
||||||
"facility_value_contracts.json": "447388ba03a29bfac25f2bcd068665aedbb07ba7641ce86f2db93d44f4c9f326",
|
"facility_value_contracts.json": "447388ba03a29bfac25f2bcd068665aedbb07ba7641ce86f2db93d44f4c9f326",
|
||||||
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
|
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
|
||||||
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
|
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
|
||||||
|
|||||||
@@ -231,11 +231,12 @@ func get_storage_current(node_id: StringName) -> int:
|
|||||||
return int(_nodes[node_id][&"current"])
|
return int(_nodes[node_id][&"current"])
|
||||||
|
|
||||||
|
|
||||||
## Upkeep demand of every registered consumer, including disabled ones.
|
## Upkeep demand of every registered consumer, including disabled ones,
|
||||||
func get_total_consumer_demand() -> int:
|
## except the given consumer ids (for example destroyed facilities).
|
||||||
|
func get_total_consumer_demand(excluded_ids: Array = []) -> int:
|
||||||
var total := 0
|
var total := 0
|
||||||
for node_id: StringName in _nodes:
|
for node_id: StringName in _nodes:
|
||||||
if int(_nodes[node_id][&"kind"]) == NodeKind.CONSUMER:
|
if int(_nodes[node_id][&"kind"]) == NodeKind.CONSUMER and not node_id in excluded_ids:
|
||||||
total += int(_nodes[node_id][&"demand"])
|
total += int(_nodes[node_id][&"demand"])
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,12 @@ func get_last_flow() -> Dictionary:
|
|||||||
|
|
||||||
## Processes every boundary in (last boundary, minute]. Boundaries already
|
## Processes every boundary in (last boundary, minute]. Boundaries already
|
||||||
## processed are no-ops, so per-minute callers and same-minute retries are safe.
|
## processed are no-ops, so per-minute callers and same-minute retries are safe.
|
||||||
|
## Mana may rise between boundaries (event refunds, commands); anything above
|
||||||
|
## capacity is discarded and recorded as overflow at the next boundary.
|
||||||
func advance_to(minute: Variant, before: Variant, demand: Variant) -> Dictionary:
|
func advance_to(minute: Variant, before: Variant, demand: Variant) -> Dictionary:
|
||||||
if _minute < 0:
|
if _minute < 0:
|
||||||
return _failure(&"NOT_STARTED")
|
return _failure(&"NOT_STARTED")
|
||||||
if typeof(minute) != TYPE_INT or not _amount(before) or before > CAPACITY \
|
if typeof(minute) != TYPE_INT or not _amount(before) \
|
||||||
or typeof(demand) != TYPE_INT or demand < 0 or demand > LIMIT or minute < _minute:
|
or typeof(demand) != TYPE_INT or demand < 0 or demand > LIMIT or minute < _minute:
|
||||||
return _failure(&"INVALID_FLOW")
|
return _failure(&"INVALID_FLOW")
|
||||||
var boundary: int = minute - minute % INTERVAL_MINUTES
|
var boundary: int = minute - minute % INTERVAL_MINUTES
|
||||||
@@ -69,9 +71,14 @@ static func plan(before: int, demand: int, furnace: int, start: int, end: int) -
|
|||||||
&"consumed": consumed, &"shortage": demand - consumed,
|
&"consumed": consumed, &"shortage": demand - consumed,
|
||||||
&"overflow": maxi(remaining - CAPACITY, 0), &"after": mini(remaining, CAPACITY)}
|
&"overflow": maxi(remaining - CAPACITY, 0), &"after": mini(remaining, CAPACITY)}
|
||||||
|
|
||||||
|
## Legacy saves without a reserve receipt must already fit the approved capacity.
|
||||||
static func amount_is_valid(value: Variant) -> bool:
|
static func amount_is_valid(value: Variant) -> bool:
|
||||||
return _amount(value) and value <= CAPACITY
|
return _amount(value) and value <= CAPACITY
|
||||||
|
|
||||||
|
## A current save may hold more than capacity until the next boundary settles it.
|
||||||
|
static func stored_amount_is_valid(value: Variant) -> bool:
|
||||||
|
return _amount(value)
|
||||||
|
|
||||||
func create_snapshot() -> Dictionary:
|
func create_snapshot() -> Dictionary:
|
||||||
return {&"version": VERSION, &"policy_id": POLICY_ID, &"capacity": CAPACITY,
|
return {&"version": VERSION, &"policy_id": POLICY_ID, &"capacity": CAPACITY,
|
||||||
&"generation": WORLD_TREE_GENERATION, &"interval_minutes": INTERVAL_MINUTES,
|
&"generation": WORLD_TREE_GENERATION, &"interval_minutes": INTERVAL_MINUTES,
|
||||||
@@ -97,7 +104,7 @@ func restore_snapshot(source: Variant) -> bool:
|
|||||||
for key: Variant in flow:
|
for key: Variant in flow:
|
||||||
if not _amount(flow[key]):
|
if not _amount(flow[key]):
|
||||||
return false
|
return false
|
||||||
if flow["to_minute"] != source["minute"] or flow["before"] > CAPACITY \
|
if flow["to_minute"] != source["minute"] \
|
||||||
or flow["generation"] != WORLD_TREE_GENERATION \
|
or flow["generation"] != WORLD_TREE_GENERATION \
|
||||||
or flow["from_minute"] + INTERVAL_MINUTES != flow["to_minute"]:
|
or flow["from_minute"] + INTERVAL_MINUTES != flow["to_minute"]:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
extends SceneTree
|
extends SceneTree
|
||||||
|
const ManaOutage = preload("res://tests/helpers/mana_outage.gd")
|
||||||
var checks := 0
|
var checks := 0
|
||||||
var failures := 0
|
var failures := 0
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
@@ -31,11 +32,12 @@ func _run() -> void:
|
|||||||
runtime.free(); clock.free(); state.free(); quit(failures); return
|
runtime.free(); clock.free(); state.free(); quit(failures); return
|
||||||
# Only reserve depletion is a diagnostic input; no numeric generation policy
|
# Only reserve depletion is a diagnostic input; no numeric generation policy
|
||||||
# is asserted by this test. Everything else advances through Runtime ticks.
|
# is asserted by this test. Everything else advances through Runtime ticks.
|
||||||
state.set_resource(&"mana", 0)
|
ManaOutage.hold(runtime, state)
|
||||||
# Pre-fix saves legitimately contain zero reserve with old powered flags.
|
# Pre-fix saves legitimately contain zero reserve with old powered flags.
|
||||||
var legacy: Dictionary = runtime.create_snapshot()
|
var legacy: Dictionary = runtime.create_snapshot()
|
||||||
var legacy_data: Dictionary = legacy[&"systems"][&"runtime"]
|
var legacy_data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||||
legacy_data[&"runtime_schema_version"] = 9
|
legacy_data[&"runtime_schema_version"] = 9
|
||||||
|
legacy_data.erase(&"mana_reserve")
|
||||||
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_data)
|
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_data)
|
||||||
var source_copy := legacy.duplicate(true)
|
var source_copy := legacy.duplicate(true)
|
||||||
var old_reception_id := reception.instance_id
|
var old_reception_id := reception.instance_id
|
||||||
@@ -65,7 +67,7 @@ func _run() -> void:
|
|||||||
var room_id := room.instance_id
|
var room_id := room.instance_id
|
||||||
var reception_id := reception.instance_id
|
var reception_id := reception.instance_id
|
||||||
runtime.facility_service.set_operational_status(room_id, {&"suspension_reason": &"DESTROYED"}, clock.absolute_minute)
|
runtime.facility_service.set_operational_status(room_id, {&"suspension_reason": &"DESTROYED"}, clock.absolute_minute)
|
||||||
state.set_resource(&"mana", 1)
|
ManaOutage.release(runtime, state, 1)
|
||||||
clock.advance_minutes(1)
|
clock.advance_minutes(1)
|
||||||
room = runtime.facility_service.get_instance(room_id)
|
room = runtime.facility_service.get_instance(room_id)
|
||||||
reception = runtime.facility_service.get_instance(reception_id)
|
reception = runtime.facility_service.get_instance(reception_id)
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ func _initialize() -> void:
|
|||||||
var saved: Dictionary = cycle.create_snapshot()
|
var saved: Dictionary = cycle.create_snapshot()
|
||||||
expect(not cycle.advance_to(370, 57, 63)[&"processed"] and cycle.create_snapshot() == saved, "same boundary never consumed twice")
|
expect(not cycle.advance_to(370, 57, 63)[&"processed"] and cycle.create_snapshot() == saved, "same boundary never consumed twice")
|
||||||
expect(not cycle.advance_to(360, 57, 63)[&"ok"], "time reversal rejected")
|
expect(not cycle.advance_to(360, 57, 63)[&"ok"], "time reversal rejected")
|
||||||
expect(not cycle.advance_to(380, 301, 0)[&"ok"] and cycle.create_snapshot() == saved, "over-capacity reserve rejected without change")
|
expect(not cycle.advance_to(380, -1, 0)[&"ok"] and cycle.create_snapshot() == saved, "negative reserve rejected without change")
|
||||||
|
var high = Cycle.new()
|
||||||
|
high.start_at(360)
|
||||||
|
flow = high.advance_to(370, 525, 16)[&"flow"]
|
||||||
|
expect(flow[&"after"] == 300 and flow[&"overflow"] == 229, "amount raised above capacity between boundaries settles with recorded overflow")
|
||||||
|
var high_restored = Cycle.new()
|
||||||
|
expect(high_restored.restore_snapshot(high.create_snapshot()), "over-capacity before survives snapshot validation")
|
||||||
var skipped: Dictionary = cycle.advance_to(400, 57, 63)
|
var skipped: Dictionary = cycle.advance_to(400, 57, 63)
|
||||||
expect(skipped[&"boundaries"] == 3 and skipped[&"after"] == 0 and skipped[&"flow"][&"shortage"] == 43, "skipped boundaries settle in order")
|
expect(skipped[&"boundaries"] == 3 and skipped[&"after"] == 0 and skipped[&"flow"][&"shortage"] == 43, "skipped boundaries settle in order")
|
||||||
var restored = Cycle.new()
|
var restored = Cycle.new()
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ func _run() -> void:
|
|||||||
_test_generation_and_demand()
|
_test_generation_and_demand()
|
||||||
_test_save_round_trip_and_migration()
|
_test_save_round_trip_and_migration()
|
||||||
_test_depletion_stops_tower()
|
_test_depletion_stops_tower()
|
||||||
|
_test_between_boundary_increase()
|
||||||
|
_test_destroyed_facility_demand()
|
||||||
_cleanup()
|
_cleanup()
|
||||||
if _failures == 0:
|
if _failures == 0:
|
||||||
print("PASS: %d checks" % _checks)
|
print("PASS: %d checks" % _checks)
|
||||||
@@ -100,6 +102,46 @@ func _test_depletion_stops_tower() -> void:
|
|||||||
_expect(_state.get_resource(&"mana") == maxi(0, 20 - demand), "world tree recovers the reserve at the next boundary")
|
_expect(_state.get_resource(&"mana") == maxi(0, 20 - demand), "world tree recovers the reserve at the next boundary")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_between_boundary_increase() -> void:
|
||||||
|
_clock.advance_minutes(10 - (_clock.absolute_minute % 10) + 3)
|
||||||
|
_state.add_resource(&"mana", 400)
|
||||||
|
var high: int = _state.get_resource(&"mana")
|
||||||
|
_expect(high > Cycle.CAPACITY, "mana can rise above capacity between boundaries (event refund / command)")
|
||||||
|
var saved: Dictionary = _runtime.create_snapshot()
|
||||||
|
_expect(_runtime.restore_snapshot(saved.duplicate(true)) and _state.get_resource(&"mana") == high, "over-capacity mid-interval save restores")
|
||||||
|
_clock.advance_minutes(1)
|
||||||
|
_expect(not _runtime.get_operations_report()[&"runtime_failure_latched"], "over-capacity amount does not latch a runtime fault")
|
||||||
|
_clock.advance_minutes(10 - (_clock.absolute_minute % 10))
|
||||||
|
var flow: Dictionary = _runtime.mana_reserve.get_last_flow()
|
||||||
|
_expect(_state.get_resource(&"mana") == Cycle.CAPACITY and flow[&"before"] == high, "next boundary settles to capacity")
|
||||||
|
_expect(flow[&"overflow"] == high + 20 - int(flow[&"consumed"]) - Cycle.CAPACITY, "excess is recorded as overflow")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_destroyed_facility_demand() -> void:
|
||||||
|
var build = _runtime.submit_command(
|
||||||
|
&"build_facility",
|
||||||
|
{&"definition_id": &"cafeteria", &"origin": Vector3i(-5, 0, WorldService.LAYER_FRONT), &"rotation": 0},
|
||||||
|
&"player", &"", 50, -1, &"a06.build.cafeteria"
|
||||||
|
)
|
||||||
|
_expect(build.is_success(), "cafeteria construction starts")
|
||||||
|
_clock.advance_minutes(180)
|
||||||
|
var cafeteria: FacilityInstance = null
|
||||||
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||||||
|
if facility.definition_id == &"cafeteria":
|
||||||
|
cafeteria = facility
|
||||||
|
_expect(cafeteria != null, "cafeteria completes")
|
||||||
|
if cafeteria == null:
|
||||||
|
return
|
||||||
|
var with_cafeteria: int = _runtime.call("_mana_reserve_demand")
|
||||||
|
_expect(with_cafeteria == _runtime.mana_network_service.get_total_consumer_demand(), "owned facility demand counts")
|
||||||
|
_runtime.facility_service.set_operational_status(cafeteria.instance_id, {&"business_open": false}, _clock.absolute_minute)
|
||||||
|
_expect(_runtime.call("_mana_reserve_demand") == with_cafeteria, "closed facility still pays upkeep demand")
|
||||||
|
_runtime.facility_service.set_operational_status(cafeteria.instance_id, {&"suspension_reason": &"DESTROYED"}, _clock.absolute_minute)
|
||||||
|
_expect(_runtime.call("_mana_reserve_demand") == with_cafeteria - 2, "destroyed facility demand is excluded")
|
||||||
|
_clock.advance_minutes(10 - (_clock.absolute_minute % 10))
|
||||||
|
_expect(int(_runtime.mana_reserve.get_last_flow()[&"demand"]) == with_cafeteria - 2, "boundary uses demand without destroyed facility")
|
||||||
|
|
||||||
|
|
||||||
func _expect(condition: bool, label: String) -> void:
|
func _expect(condition: bool, label: String) -> void:
|
||||||
_checks += 1
|
_checks += 1
|
||||||
if condition:
|
if condition:
|
||||||
|
|||||||
Reference in New Issue
Block a user