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:
Takeshi
2026-10-19 17:20:04 +09:00
co-authored by Claude Opus 5.5
parent c366012446
commit ae0f998ff4
18 changed files with 424 additions and 17 deletions
@@ -33,6 +33,14 @@
"decision": "2×1・裏層。建築350G、資材4、工期60分、清掃員最低1人/上限2人、顧客0人・利用料なし、維持費2G/日。清潔0〜100・初期100、75以下で予約、現地8分で25回復。定義済み汚れ量を使い未定義値を補完しない。",
"origin": "user_reply",
"reference": "../original-completion-2026-09-29/execution/CLEANING_APPROVED_FOUNDATION.md"
},
{
"id": "A06",
"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のみ保持し、超過・不正値は拒否。炉の人物適格性・収率の実接続は別承認。",
"origin": "user_reply_in_conversation",
"approved_at_jst": "2026-10-19",
"reference": "execution/mana-proposal/PROPOSAL.md"
}
],
"pending": [
@@ -0,0 +1,20 @@
# P06-01:A06 マナ有限残量契約の実装(2026-10-19)
ユーザーが MP-01-r02 推薦案を採用しました(decisions.json A06)。内容は初期100、容量300、世界樹の生成20/ゲーム内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_network_service.gd`:`get_total_consumer_demand()` を追加しました。登録済みの全 consumer の需要を合計します。無効化・停止中の consumer も含みます。
- `autoload/simulation_runtime.gd`:MANA phase の最初で残量を確定してから、局所配電と全館ゲートを評価します。phase の順序は変えていません。新しいセーブは `mana_reserve` を持ちます。Operations の巻き戻し対象にも含めました。
- 旧セーブの移行:`mana_reserve` が無いセーブは、残量が0〜300の整数であれば、その値を保持して開始します。明示的な0は停止のまま保持します。300超・負値・非整数は拒否し、現在の状態を保持します。
## 試験
- 新規:`tests/s2/test_mana_reserve_cycle.gd`(提案書の比較表の推薦列の値、炉判定の3例、改ざん拒否)、`tests/s2/test_runtime_mana_reserve.gd`(Runtimeでの境界処理、保存往復、旧0の移行、容量超過の拒否、枯渇と復帰)。
- 既存の停電試験は「マナを0にすれば停電が続く」前提でした。A06では世界樹が次の境界で復帰させるため、停電を継続させる試験だけ `tests/helpers/mana_outage.gd` に置き換えました。これは配電を受けない無効 consumer で需要を足す方法です。
## 未了
- 炉の人物適格性・収率の実接続、未定義13施設の需要、D05 の詳細は別承認です。
- 全館の実プレイ、性能、Windows での受入は未実施です。原88の正式完了数は変わりません。
@@ -84,7 +84,7 @@
"passed": true
},
{
"name": "all5_confirmed_decisions",
"name": "all6_confirmed_decisions",
"passed": true
},
{
@@ -238,7 +238,7 @@
"build_atomic_requirements.py": "6d6ea9e4216372dbd0f9fed3f885a4481b21a3f539a26f64cf98a6830af984c5",
"build_facility_value_contracts.py": "35027b23ce75ce9e4e0cdfc5132fd2eb771ba9f598d97750d7ecf9ecb257f769",
"current_evidence_links.json": "db1837bcc8497201134297e312f4b266a297102285307ec3075d4e23632e26c2",
"decisions.json": "ad0673051b0fa601e525f05128e0b11e2dddd9d4bae6a3e0170bbf47da35efe9",
"decisions.json": "927065d9f840e68667d10998d26eb5d04f5591d9f24082106d082e51d5af5c96",
"facility_value_contracts.json": "447388ba03a29bfac25f2bcd068665aedbb07ba7641ce86f2db93d44f4c9f326",
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
@@ -251,7 +251,7 @@
"source_atomic_coverage.json": "2ab7b9e6a2536ef248d4f7ccd695bf75a082b47018aca7a5a1b6b706fe1907bc",
"validate_atomic_requirements.py": "5b2d0b7d72cb124830fbb0a12d1434e6db698148cc5263d33fd5bd7c2953a1e0",
"validate_facility_value_contracts.py": "91640e2a76cd6cc9186f7b85c0bf379b24b6cac0d48962815235ee628e7ae43e",
"validate_plan.py": "23678227d9b3c556f1bd903f4b5b3147c4a82ce5085e10055481026803451b80",
"validate_plan.py": "e31316aff6c814d1e1660a97b91cc0c0016d2b722c4d4092a71859a85bd0e65d",
"validator_update_checks.json": "f997d0f3de2cea29550840b1d5cb23647d64f81329de46758551e2b6c8fecc27",
"work_packages.json": "1913634b33ca25bec6d5a0a6a4d3d9258b7fd4c9c79c9ab426f21ac2b11c6e81"
}
@@ -172,7 +172,7 @@ def main():
pending = {d["id"] for d in decisions["pending"]}
check("all10_pending_decision_groups", pending == {f"D{i:02d}" for i in range(1, 11)})
check("known_decision_references", all(set(p["decisions"]) <= pending for p in packages))
check("all5_confirmed_decisions", {d["id"] for d in decisions["confirmed"]} == {f"A{i:02d}" for i in range(1, 6)})
check("all6_confirmed_decisions", {d["id"] for d in decisions["confirmed"]} == {f"A{i:02d}" for i in range(1, 7)})
check("confirmed_decision_references_exist", all((PLAN / d["reference"]).is_file() for d in decisions["confirmed"] if "reference" in d))
state_counts = dict(collections.Counter(p["state"] for p in packages))
check("valid_package_states", all(p["state"] in states for p in packages))