From 6a80649a7f7fca78dd24724442fb7252aaf34495 Mon Sep 17 00:00:00 2001 From: Takeshi Date: Mon, 12 Oct 2026 08:49:28 +0900 Subject: [PATCH] Preserve unadopted guest need normalization candidates and finite verification --- .../README.md | 11 + .../manifest.json | 197 ++ .../input.json | 59 + .../source/guest_service.gd | 1752 +++++++++++++++++ .../structural-check.json | 8 + .../input.json | 133 ++ .../source/guest_service.gd | 1730 ++++++++++++++++ ...-local-actual-independent-review-r296.json | 249 +++ ...-preexecution-independent-review-r296.json | 155 ++ ...-02-guest-need-normalization-local-r296.py | 43 + .../actual-root-verification.json | 183 ++ .../custom_definition.gd | 9 + .../execution.json | 262 +++ .../guarded.gd | 5 + .../input.json.gz | Bin 0 -> 227315 bytes .../local.gd | 87 + .../evaluation-fixtures.varbytes.zst | Bin 0 -> 3540 bytes .../measurement/report.json | 37 + .../original_guest_service.gd | 1688 ++++++++++++++++ ...zation-static-independent-review-r295.json | 106 + ...zation-static-independent-review-r298.json | 185 ++ ...ization-static-root-verification-r295.json | 73 + ...ization-static-root-verification-r298.json | 158 ++ 23 files changed, 7130 insertions(+) create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/README.md create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/manifest.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/input.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/structural-check.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/input.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-actual-independent-review-r296.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296.py create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/actual-root-verification.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/custom_definition.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/execution.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/guarded.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/input.json.gz create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/local.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/measurement/report.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/original_guest_service.gd create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-independent-review-r295.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-independent-review-r298.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-root-verification-r295.json create mode 100644 docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-root-verification-r298.json diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/README.md b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/README.md new file mode 100644 index 0000000..d57f2fd --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/README.md @@ -0,0 +1,11 @@ +# P23-02 Guest Need normalization r295–r298 checkpoint + +レビュー済みの未採用候補と有限検証を保存する。r295 は Need map の正規化を共有する候補で、r296 の実 Godot 局所検証は 1619/0。224 個の Need 境界・キー型・readonly の組合せと追加 7 入力、custom definition callback 2 ケースを検証した。233 行の結果 fixture は原結果と byte 完全一致を独立レビューで確認した。結果の所有、公開状態、入力保持も固定された局所条件で確認している。 + +r296 の単一 Need helper 2000 回の有限な測定は原 28,461 µs、候補 41,815 µs。代表セーブ、6 Need、whole、安定した性能差の証拠ではない。この観測で速度利益を示しておらず、r295 は未採用。 + +r298 は同版 Godot 一次ソースに基づき、r295 の共有 guard の全走査を除いた候補。exact service/guest/definition script と Dictionary guard、原 validation、custom fallback、計算・呼出順序を保持する。静的独立レビューのみ支持され、r296 の実行証拠は r298 に適用しない。r298 の実行・whole・性能・採用の判断は未了。r299 は準備中・未実行であり、本 checkpoint に実行証拠として含めない。 + +現在の実装親 r274/library275 を保持する。正式完了の追加は 0。30 パッケージ / 88 成果単位、正式完了 9 / 着手 19 / 予定 60 の定義を維持する。P23-02 の原 17 条件は未受入。必要な最終全回帰、性能予算、通常 OS 入力、同一 Windows 配布版実機などは残件。手動 Ctrl 保持 wheel と左保持 drag は延期し、免除しない。Windows C08/X8 のみをブロック扱いとする。 + +manifest.json は原ファイルと保存コピーのサイズ・SHA256 を固定する。大きな JSON は gzip の復元内容を完全一致で検証した。原参照の絶対パスとレビュー時の r294 台帳文脈を保持する。SDK・engine・stage・全実行証拠を備えた単独実行パッケージではなく、完全な証拠はローカル artifacts に残す。親 checkpoints/.gdignore により保存 GDScript を本体の resource 走査から除外する。 diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/manifest.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/manifest.json new file mode 100644 index 0000000..9c71979 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/manifest.json @@ -0,0 +1,197 @@ +{ + "schema": 1, + "unit": "P23-02", + "status": "unadopted_checkpoint", + "formal_completion_added": 0, + "entries": [ + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/input.json", + "bytes": 4366, + "sha256": "f592b9895d9253be9e7f829cd88888fe166e77e00959570f7d4a7f0c3c169489", + "stored_bytes": 4366, + "stored_sha256": "f592b9895d9253be9e7f829cd88888fe166e77e00959570f7d4a7f0c3c169489", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/structural-check.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/structural-check.json", + "bytes": 211, + "sha256": "20b5510966fce15f43e4682a367666b1e0b1bcfd28042462b10960af0c79a280", + "stored_bytes": 211, + "stored_sha256": "20b5510966fce15f43e4682a367666b1e0b1bcfd28042462b10960af0c79a280", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249", + "stored_bytes": 64563, + "stored_sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-independent-review-r295.json", + "bytes": 9244, + "sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8", + "stored_bytes": 9244, + "stored_sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-root-verification-r295.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-root-verification-r295.json", + "bytes": 3906, + "sha256": "6b015060efcf7a38773c5495f389fe987ec3eb31867e05721620aa32dd1ea9bd", + "stored_bytes": 3906, + "stored_sha256": "6b015060efcf7a38773c5495f389fe987ec3eb31867e05721620aa32dd1ea9bd", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296.py", + "bytes": 5232, + "sha256": "b9059debc1eb3aea9497d5abea646238edec5c73291d170737c29ce9026803eb", + "stored_bytes": 5232, + "stored_sha256": "b9059debc1eb3aea9497d5abea646238edec5c73291d170737c29ce9026803eb", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/input.json.gz", + "bytes": 968619, + "sha256": "3bba39c1ff62fb59a7b844515072dc322728295c5d7680aef4055af5d10494ee", + "stored_bytes": 227315, + "stored_sha256": "fa574f2b3e464a5bc8929b6ddfe423fb0a80c796f1aef9502b2aa9c9a564754c", + "encoding": "gzip" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/local.gd", + "bytes": 7048, + "sha256": "bd9cb24e28bc97c0e8f906b6d2b0d60c14964fdaabbac3ac6561334e434ff97f", + "stored_bytes": 7048, + "stored_sha256": "bd9cb24e28bc97c0e8f906b6d2b0d60c14964fdaabbac3ac6561334e434ff97f", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/guarded.gd", + "bytes": 525, + "sha256": "5db64f0527aee0c74d0dff52103aefeec112824faa82137ab78d546979655658", + "stored_bytes": 525, + "stored_sha256": "5db64f0527aee0c74d0dff52103aefeec112824faa82137ab78d546979655658", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/custom_definition.gd", + "bytes": 281, + "sha256": "d29c18196c89452a0ba6db8ec17d0ac2e75be2b6092533c734288230bc44f0c7", + "stored_bytes": 281, + "stored_sha256": "d29c18196c89452a0ba6db8ec17d0ac2e75be2b6092533c734288230bc44f0c7", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/original_guest_service.gd", + "bytes": 61426, + "sha256": "a70362ac6326266573a873319146d50a540829b751ad4c07746937fef85f5193", + "stored_bytes": 61426, + "stored_sha256": "a70362ac6326266573a873319146d50a540829b751ad4c07746937fef85f5193", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/execution.json", + "bytes": 10788, + "sha256": "d01a859ce75a18084cbec7a081c906e267d1e8c7a42bce55e2539dd3a7ede8c3", + "stored_bytes": 10788, + "stored_sha256": "d01a859ce75a18084cbec7a081c906e267d1e8c7a42bce55e2539dd3a7ede8c3", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/actual-root-verification.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/actual-root-verification.json", + "bytes": 10454, + "sha256": "6d9dc235ac63d6629e4375e5b75516f522f47d4d491b40cda790da14fa65f4fe", + "stored_bytes": 10454, + "stored_sha256": "6d9dc235ac63d6629e4375e5b75516f522f47d4d491b40cda790da14fa65f4fe", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json", + "bytes": 11638, + "sha256": "dc2662c7f9b92de16e9d4d97daf86d3a2eb537f2587cc76946d77e7021907071", + "stored_bytes": 11638, + "stored_sha256": "dc2662c7f9b92de16e9d4d97daf86d3a2eb537f2587cc76946d77e7021907071", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-actual-independent-review-r296.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-actual-independent-review-r296.json", + "bytes": 19007, + "sha256": "ce06bfe9156ed8e1c9ec53519d6cc52b26c963ca0e8b4a263ff70d1a1abf9a23", + "stored_bytes": 19007, + "stored_sha256": "ce06bfe9156ed8e1c9ec53519d6cc52b26c963ca0e8b4a263ff70d1a1abf9a23", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r298/input.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/input.json", + "bytes": 11472, + "sha256": "3e2eaa147e365a74145224d8995bade5b44528d59b5508dc159458555081ed99", + "stored_bytes": 11472, + "stored_sha256": "3e2eaa147e365a74145224d8995bade5b44528d59b5508dc159458555081ed99", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd", + "bytes": 63639, + "sha256": "5895d5da6488c767685b109ef01f9e2b57f41272d49cc9e3c415865769adc11d", + "stored_bytes": 63639, + "stored_sha256": "5895d5da6488c767685b109ef01f9e2b57f41272d49cc9e3c415865769adc11d", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r298.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-independent-review-r298.json", + "bytes": 17223, + "sha256": "940dd5f51b55942f07ef7ad091ec557af4d0d7bb07d48c08b6a98bde570f32b2", + "stored_bytes": 17223, + "stored_sha256": "940dd5f51b55942f07ef7ad091ec557af4d0d7bb07d48c08b6a98bde570f32b2", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-root-verification-r298.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-static-root-verification-r298.json", + "bytes": 9067, + "sha256": "e6f83f624358270199fb6a0d3236db0e20da614443514cba04d66995a671de42", + "stored_bytes": 9067, + "stored_sha256": "e6f83f624358270199fb6a0d3236db0e20da614443514cba04d66995a671de42", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/report.json", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/measurement/report.json", + "bytes": 1128, + "sha256": "fcf08d05ca7109beed8e0c4fc75f422baab72a515467e09debf58c812fb0689d", + "stored_bytes": 1128, + "stored_sha256": "fcf08d05ca7109beed8e0c4fc75f422baab72a515467e09debf58c812fb0689d", + "encoding": "identity" + }, + { + "source": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst", + "checkpoint": "docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst", + "bytes": 3540, + "sha256": "c21dad1a71f30a2c8d4cab558bbfb4543f2daca4487b1fbdbcbb1b1a97b9143d", + "stored_bytes": 3540, + "stored_sha256": "c21dad1a71f30a2c8d4cab558bbfb4543f2daca4487b1fbdbcbb1b1a97b9143d", + "encoding": "identity" + } + ] +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/input.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/input.json new file mode 100644 index 0000000..90d69c2 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/input.json @@ -0,0 +1,59 @@ +{ + "scope": "Source-only guarded reuse of one normalized Need map within exact builtin evaluate_candidate; no production/stage edit or execution", + "parent": "r274/library275", + "pins": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd", + "bytes": 61414, + "sha256": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/input.json", + "bytes": 4511, + "sha256": "6bd46c589bc57ab5afad1d7195f7293ec450aafeed055e394a3a880744666ca3" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp", + "bytes": 54002, + "sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-impact-whole-r293-r04/execution.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-impact-whole-r293-r04/execution.json", + "bytes": 33451, + "sha256": "44d6c26b797a99e7729d1eb77e725330b2b95bd643876eb41cd11237f2488848" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-current-residual-diagnostic-actual-independent-review-r238.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-current-residual-diagnostic-actual-independent-review-r238.json", + "bytes": 36734, + "sha256": "e41634755f116006c91ad251b48eb8b71286a53a6a687d6f371366f3140cc66d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_definition.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_definition.gd", + "bytes": 9374, + "sha256": "082aa3f33b9cb17daf10a2cf8a1dece7b79dd80c2ae21e1a424d1b2ceb52b918" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_runtime.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_runtime.gd", + "bytes": 14267, + "sha256": "57ba99e5d977b5302987f4359a19056f1ab09c8fb7e4762ee8d79cbb9316741a" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249" + } + }, + "original_evaluation_validation_and_order_preserved": true, + "weights_thresholds_prices_rng_tick_save_changed": false, + "compiler_runs": 0, + "engine_runs": 0, + "candidate_adopted": false, + "unit_accepted": false, + "required_next": [ + "independent static review", + "parse and exact builtin/fallback helper/callback boundary equivalence", + "full raw ranked candidate and guest decision/Runtime equality", + "impact and whole same-final-version validation", + "performance gain qualification before adoption" + ] +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd new file mode 100644 index 0000000..b87f284 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd @@ -0,0 +1,1752 @@ +class_name GuestService +extends RefCounted + +## Deterministic guest needs, decisions and lifecycle authority. +## +## Facility, path and economy integrations intentionally cross Dictionary and +## Callable boundaries. GuestService therefore remains independently testable +## and cannot accidentally mutate another service's private state. + +signal guest_spawned(guest: GuestRuntime) +signal guest_state_changed(guest_id: StringName, old_state: int, new_state: int, reason: StringName) +signal guest_queue_abandoned(guest_id: StringName, facility_id: StringName, request_id: StringName) +signal guest_recovery_started(guest_id: StringName, recovery_kind: StringName, target_cell: Vector3i) +signal guest_despawned(guest_id: StringName, persistent_record: Dictionary) + +const GuestDefinitionType = preload("res://scripts/guest/guest_definition.gd") +const GuestRuntimeType = preload("res://scripts/guest/guest_runtime.gd") + +const SNAPSHOT_VERSION := 2 +const MAX_TRANSITIONS_PER_TICK := 4 +const PATH_FAILURE_LIMIT := 3 +const STUCK_REPLAN_MINUTES := 60 +const DEFAULT_WAIT_HARD_LIMIT := 120 +const DEFAULT_EXIT_THRESHOLD := 15 +const PRIMARY_NEEDS: Array[StringName] = [&"hunger", &"sleep", &"leisure", &"intimacy"] + +const NEED_IDS: Array[StringName] = [ + &"hunger", + &"sleep", + &"hygiene", + &"medical", + &"leisure", + &"intimacy", +] +const LEGACY_NEED_IDS: Array[StringName] = [ + &"hunger", + &"rest", + &"fun", + &"social", + &"safety", +] + +var current_minute := -1 +var last_error := "" + +var _definitions: Dictionary = {} +var _guests: Dictionary = {} +var _persistent_records: Dictionary = {} +var _decision_rejections: Dictionary = {} +var _diagnostics: Array[Dictionary] = [] +var _sorted_guest_ids_cache: Array[StringName] = [] +var _guest_order_dirty := true +var _pending_spawn_notifications: Dictionary = {} +var _external_mutation_blocked := false + + +func register_definition(definition: Variant) -> bool: + if definition == null or not definition is GuestDefinition: + return _fail("Guest definition has an invalid type.") + var typed_definition: GuestDefinition = definition + if not typed_definition.is_valid(): + return _fail("Guest definition is invalid.") + if _definitions.has(typed_definition.guest_type_id): + return _fail("Duplicate guest definition: %s" % typed_definition.guest_type_id) + _definitions[typed_definition.guest_type_id] = typed_definition + last_error = "" + return true + + +func create_definition(guest_type_id: StringName, options: Dictionary = {}) -> GuestDefinition: + var definition := GuestDefinitionType.new().configure(guest_type_id, options) + return definition if register_definition(definition) else null + + +func get_definition(guest_type_id: StringName) -> GuestDefinition: + return _definitions.get(guest_type_id) as GuestDefinition + + +func get_definitions() -> Array[GuestDefinition]: + var result: Array[GuestDefinition] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + result.append(_definitions[guest_type_id]) + return result + + +func register_guest(guest: Variant, emit_notification := true) -> bool: + if _external_mutation_blocked: + return _fail("Guest mutation is blocked while committed notifications are delivered.") + if guest == null or not guest is GuestRuntime: + return _fail("Guest runtime has an invalid type.") + var runtime: GuestRuntime = guest + if not runtime.is_valid(): + return _fail("Guest runtime is invalid.") + if not _definitions.has(runtime.guest_type_id): + return _fail("Guest runtime references an unknown definition.") + if _guests.has(runtime.guest_id) or _persistent_records.has(runtime.guest_id): + return _fail("Duplicate guest id: %s" % runtime.guest_id) + _guests[runtime.guest_id] = runtime + _guest_order_dirty = true + if emit_notification: + guest_spawned.emit(runtime) + else: + _pending_spawn_notifications[runtime.guest_id] = true + last_error = "" + return true + + +func spawn_guest( + guest_id: StringName, + guest_type_id: StringName, + absolute_minute: int, + options: Dictionary = {}, + emit_notification := true +) -> GuestRuntime: + var definition := get_definition(guest_type_id) + if definition == null or absolute_minute < 0: + _fail("A known definition and non-negative spawn minute are required.") + return null + var runtime_options := options.duplicate(true) + if not _has_value(runtime_options, &"wallet_gold"): + runtime_options[&"wallet_gold"] = definition.wealth_min + if not _has_value(runtime_options, &"credit_limit_gold"): + runtime_options[&"credit_limit_gold"] = definition.credit_limit_gold + if not _has_value(runtime_options, &"patience_remaining"): + runtime_options[&"patience_remaining"] = definition.base_patience + if not _has_value(runtime_options, &"state"): + runtime_options[&"state"] = GuestRuntimeType.State.SPAWNING + if not _has_value(runtime_options, &"state_entered_at"): + runtime_options[&"state_entered_at"] = absolute_minute + if not _has_value(runtime_options, &"planned_exit_at"): + runtime_options[&"planned_exit_at"] = absolute_minute + 720 + var runtime := GuestRuntimeType.new().configure(guest_id, guest_type_id, runtime_options) + if not register_guest(runtime, emit_notification): + return null + return runtime + + +func commit_guest_spawned_notification(guest_id: StringName) -> bool: + if not _pending_spawn_notifications.has(guest_id) or not _guests.has(guest_id): + return _fail("Guest has no pending spawn notification: %s" % guest_id) + _pending_spawn_notifications.erase(guest_id) + guest_spawned.emit(_guests[guest_id] as GuestRuntime) + last_error = "" + return true + + +func discard_guest_spawned_notification(guest_id: StringName) -> bool: + if not _pending_spawn_notifications.has(guest_id): + return false + _pending_spawn_notifications.erase(guest_id) + return true + + +func has_pending_guest_spawned_notification(guest_id: StringName) -> bool: + return _pending_spawn_notifications.has(guest_id) + + +func set_external_mutation_blocked(blocked: bool) -> void: + _external_mutation_blocked = blocked + + +func has_guest(guest_id: StringName) -> bool: + return _guests.has(guest_id) + + +func get_guest(guest_id: StringName) -> GuestRuntime: + return _guests.get(guest_id) as GuestRuntime + + +func get_guests() -> Array[GuestRuntime]: + var result: Array[GuestRuntime] = [] + for guest_id: StringName in _ordered_guest_ids(): + result.append(_guests[guest_id]) + return result + + +func guest_count() -> int: + return _guests.size() + + +func get_persistent_record(guest_id: StringName) -> Dictionary: + if not _persistent_records.has(guest_id): + return {} + return (_persistent_records[guest_id] as Dictionary).duplicate(true) + + +func get_diagnostics() -> Array[Dictionary]: + return _diagnostics.duplicate(true) + + +func advance_one_minute( + absolute_minute: int, + context_by_guest: Dictionary = {} +) -> Dictionary: + if absolute_minute < 0 or absolute_minute <= current_minute: + return _error_result("Guest minute must advance monotonically.") + var ordered_guest_ids := _ordered_guest_ids() + # The common no-context tick has no external values to validate or isolate. + # Avoid allocating one duplicated context plus a resolved-context entry per + # Guest while retaining the same deterministic order and report contract. + if context_by_guest.is_empty(): + return _advance_with_shared_need_context( + absolute_minute, + ordered_guest_ids, + {} + ) + var resolved_contexts: Dictionary = {} + var default_context: Dictionary = {} + var raw_default: Variant = _value(context_by_guest, &"default", {}) + if raw_default is Dictionary: + default_context = raw_default + # Production supplies one common default context. Validate it once, then + # share the same read-only Dictionary across the population. Any per-Guest + # override keeps using the all-context preflight below, preserving atomic + # rejection and the malformed-override contract. + if context_by_guest.size() == 1 and _has_value(context_by_guest, &"default"): + # Historically an unused default was not validated when the population + # was empty. Preserve that edge case and the first-Guest error wording. + if not ordered_guest_ids.is_empty(): + var default_error := _need_context_error(default_context) + if not default_error.is_empty(): + return _error_result( + "Invalid Need context for %s: %s" % [ + ordered_guest_ids[0], + default_error, + ] + ) + return _advance_with_shared_need_context( + absolute_minute, + ordered_guest_ids, + default_context + ) + for guest_id: StringName in ordered_guest_ids: + var context := default_context + var raw_context: Variant = null + var has_guest_context := false + if context_by_guest.has(guest_id): + has_guest_context = true + raw_context = context_by_guest[guest_id] + else: + var string_guest_id := String(guest_id) + if context_by_guest.has(string_guest_id): + has_guest_context = true + raw_context = context_by_guest[string_guest_id] + if raw_context is Dictionary: + context = raw_context + elif has_guest_context: + # Preserve the original contract: a malformed per-guest override + # suppresses the default context rather than inheriting it. + context = {} + var context_error := _need_context_error(context) + if not context_error.is_empty(): + return _error_result("Invalid Need context for %s: %s" % [guest_id, context_error]) + resolved_contexts[guest_id] = context.duplicate(true) + + ## All contexts are preflighted before mutating any guest, so a malformed + ## batched update cannot partially advance the population or the minute. + var reports: Array[Dictionary] = [] + for guest_id: StringName in ordered_guest_ids: + var context: Dictionary = resolved_contexts[guest_id] + var guest: GuestRuntime = _guests[guest_id] + var definition: GuestDefinition = _definitions.get(guest.guest_type_id) as GuestDefinition + if definition == null: + reports.append(_error_result("Guest definition is missing.")) + else: + reports.append(_update_needs_runtime(guest, definition, context)) + current_minute = absolute_minute + last_error = "" + return { + &"ok": true, + &"minute": absolute_minute, + &"guest_reports": reports, + } + + +func _advance_with_shared_need_context( + absolute_minute: int, + ordered_guest_ids: Array[StringName], + context: Dictionary +) -> Dictionary: + var reports: Array[Dictionary] = [] + for guest_id: StringName in ordered_guest_ids: + var guest: GuestRuntime = _guests[guest_id] + var definition: GuestDefinition = _definitions.get( + guest.guest_type_id + ) as GuestDefinition + if definition == null: + reports.append(_error_result("Guest definition is missing.")) + else: + reports.append(_update_needs_runtime(guest, definition, context)) + current_minute = absolute_minute + last_error = "" + return { + &"ok": true, + &"minute": absolute_minute, + &"guest_reports": reports, + } + + +func update_needs(guest_id: StringName, context: Dictionary = {}) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var definition := get_definition(guest.guest_type_id) + if definition == null: + return _error_result("Guest definition is missing.") + var context_error := _need_context_error(context) + if not context_error.is_empty(): + return _error_result("Invalid Need context: %s" % context_error) + last_error = "" + return _update_needs_runtime(guest, definition, context) + + +func _update_needs_runtime( + guest: GuestRuntime, + definition: GuestDefinition, + context: Dictionary +) -> Dictionary: + var before_hunger := int(guest.needs[&"hunger"]) + var before_sleep := int(guest.needs[&"sleep"]) + var before_hygiene := int(guest.needs[&"hygiene"]) + var before_medical := int(guest.needs[&"medical"]) + var before_leisure := int(guest.needs[&"leisure"]) + var before_intimacy := int(guest.needs[&"intimacy"]) + var delta_hunger := int(definition.need_rates[&"hunger"]) + var delta_sleep := int(definition.need_rates[&"sleep"]) + var delta_hygiene := int(definition.need_rates[&"hygiene"]) + var delta_medical := int(definition.need_rates[&"medical"]) + var delta_leisure := int(definition.need_rates[&"leisure"]) + var delta_intimacy := int(definition.need_rates[&"intimacy"]) + + ## State effects are accumulated first; every Need is clamped exactly once. + if guest.state == GuestRuntimeType.State.MOVING \ + or guest.state == GuestRuntimeType.State.REPLANNING: + delta_hunger += 2 + delta_sleep += 2 + elif guest.state == GuestRuntimeType.State.QUEUING: + delta_hunger += 1 + + if context.is_empty(): + if guest.state != GuestRuntimeType.State.RECEIVING_SERVICE: + delta_leisure += 1 + else: + if bool(_value(context, &"work_like_activity", false)): + delta_sleep += 1 + if bool(_value( + context, + &"unstimulated", + guest.state != GuestRuntimeType.State.RECEIVING_SERVICE + )): + delta_leisure += 1 + delta_hygiene += int(_value(context, &"dirt", 0)) + delta_medical += int(_value(context, &"injury", 0)) + delta_medical += int(_value(context, &"illness", 0)) + if definition.adult_verified: + var intimacy_key := &"intimacy_night" \ + if bool(_value(context, &"is_night", false)) \ + else &"intimacy_day" + delta_intimacy += int(_value(context, intimacy_key, 0)) + var raw_effects: Variant = _value(context, &"need_effects", {}) + var normalized_effects: Dictionary = _normalized_partial_need_map(raw_effects) + delta_hunger += int(normalized_effects.get(&"hunger", 0)) + delta_sleep += int(normalized_effects.get(&"sleep", 0)) + delta_hygiene += int(normalized_effects.get(&"hygiene", 0)) + delta_medical += int(normalized_effects.get(&"medical", 0)) + delta_leisure += int(normalized_effects.get(&"leisure", 0)) + delta_intimacy += int(normalized_effects.get(&"intimacy", 0)) + + var after_hunger := clampi(before_hunger + delta_hunger, 0, 100) + var after_sleep := clampi(before_sleep + delta_sleep, 0, 100) + var after_hygiene := clampi(before_hygiene + delta_hygiene, 0, 100) + var after_medical := clampi(before_medical + delta_medical, 0, 100) + var after_leisure := clampi(before_leisure + delta_leisure, 0, 100) + var after_intimacy := clampi(before_intimacy + delta_intimacy, 0, 100) + guest.needs[&"hunger"] = after_hunger + guest.needs[&"sleep"] = after_sleep + guest.needs[&"hygiene"] = after_hygiene + guest.needs[&"medical"] = after_medical + guest.needs[&"leisure"] = after_leisure + guest.needs[&"intimacy"] = after_intimacy + return { + &"ok": true, + &"guest_id": String(guest.guest_id), + &"before": { + "hunger": before_hunger, + "hygiene": before_hygiene, + "intimacy": before_intimacy, + "leisure": before_leisure, + "medical": before_medical, + "sleep": before_sleep, + }, + &"deltas": { + "hunger": delta_hunger, + "hygiene": delta_hygiene, + "intimacy": delta_intimacy, + "leisure": delta_leisure, + "medical": delta_medical, + "sleep": delta_sleep, + }, + &"after": { + "hunger": after_hunger, + "hygiene": after_hygiene, + "intimacy": after_intimacy, + "leisure": after_leisure, + "medical": after_medical, + "sleep": after_sleep, + }, + } + + +## Returns canonical candidate copies in facility_id order. Supported canonical +## fields are documented by the filtering helpers below; aliases allow existing +## FacilityService projections to be adapted without a hard dependency. +func extract_candidates( + guest_id: StringName, + facilities: Array, + context: Dictionary = {} +) -> Array[Dictionary]: + var guest := get_guest(guest_id) + if guest == null: + _fail("Unknown guest: %s" % guest_id) + return [] + var definition := get_definition(guest.guest_type_id) + if definition == null: + _fail("Guest definition is missing.") + return [] + var ordered: Array[Dictionary] = [] + for raw_candidate: Variant in facilities: + if not raw_candidate is Dictionary: + continue + var candidate: Dictionary = raw_candidate.duplicate(true) + var facility_id := _facility_id(candidate) + if facility_id.is_empty(): + continue + candidate[&"facility_id"] = facility_id + ordered.append(candidate) + ordered.sort_custom( + func(a: Dictionary, b: Dictionary) -> bool: + return String(a[&"facility_id"]) < String(b[&"facility_id"]) + ) + + var accepted: Array[Dictionary] = [] + var rejected: Array[Dictionary] = [] + for candidate: Dictionary in ordered: + var reason := _candidate_rejection_reason(guest, definition, candidate, context) + if reason.is_empty(): + accepted.append(candidate) + else: + rejected.append({ + &"facility_id": String(candidate[&"facility_id"]), + &"reason": String(reason), + }) + _decision_rejections[guest_id] = rejected + last_error = "" + return accepted + + +func get_last_candidate_rejections(guest_id: StringName) -> Array[Dictionary]: + return (_decision_rejections.get(guest_id, []) as Array).duplicate(true) + + +func evaluate_candidate(guest_id: StringName, candidate: Dictionary) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var definition := get_definition(guest.guest_type_id) + if definition == null: + return _error_result("Guest definition is missing.") + if not _candidate_need_maps_are_valid(candidate): + return _error_result("Candidate Need map is invalid.") + # Shared only for exact builtin pure evaluation. Custom providers and + # conversions retain the original helper calls and their observation order. + var shared_need_map: Variant = null + if _can_share_candidate_need_map(guest, definition, candidate): + shared_need_map = _normalized_partial_need_map(_value(candidate, &"need_relief", {})) + var need_relief := _need_relief(guest, candidate) if shared_need_map == null else _need_relief_from_normalized(guest, shared_need_map) + var preference := int(_value( + candidate, + &"preference", + definition.preference_for(_value(candidate, &"facility_tags", _value(candidate, &"tags", []))) + )) + var expected_quality := int(_value( + candidate, + &"expected_quality", + _value(candidate, &"quality", 0) + )) + var novelty := int(_value( + candidate, + &"novelty", + 0 if guest.visited_facility_ids.has(_facility_id(candidate)) else 10 + )) + var group_fit := int(_value(candidate, &"group_fit", 0)) + var price_burden := int(_value(candidate, &"price_burden", _price_burden(guest, candidate))) + var travel_minutes := maxi(int(_value(candidate, &"travel_minutes", 0)), 0) + var wait_minutes := maxi(int(_value( + candidate, + &"wait_minutes", + _value(candidate, &"expected_wait", 0) + )), 0) + var risk := maxi(int(_value(candidate, &"risk", _value(candidate, &"risk_level", 0))), 0) + var default_risk_sensitivity := (100 - definition.risk_tolerance + 24) / 25 + var risk_sensitivity := maxi(int(_value( + candidate, + &"risk_sensitivity", + default_risk_sensitivity + )), 0) + var repetition_penalty := int(_value( + candidate, + &"repetition_penalty", + 10 if guest.visited_facility_ids.has(_facility_id(candidate)) else 0 + )) + var urgency := int(_value(candidate, &"urgency", _candidate_urgency(guest, candidate) if shared_need_map == null else _candidate_urgency_from_normalized(guest, shared_need_map))) + var utility := ( + need_relief * 8 + + preference * 5 + + expected_quality * 3 + + novelty * 2 + + group_fit * 2 + - price_burden * 5 + - travel_minutes * 2 + - wait_minutes * 3 + - risk * risk_sensitivity + - repetition_penalty + ) + var result := candidate.duplicate(true) + result[&"facility_id"] = _facility_id(candidate) + result[&"need_priority"] = _primary_need_priority(guest, candidate) if shared_need_map == null else _primary_need_priority_from_normalized(guest, shared_need_map) + result[&"utility"] = utility + result[&"urgency"] = urgency + result[&"travel_minutes"] = travel_minutes + result[&"utility_terms"] = { + &"need_relief": need_relief, + &"preference": preference, + &"expected_quality": expected_quality, + &"novelty": novelty, + &"group_fit": group_fit, + &"price_burden": price_burden, + &"travel_minutes": travel_minutes, + &"wait_minutes": wait_minutes, + &"risk": risk, + &"risk_sensitivity": risk_sensitivity, + &"repetition_penalty": repetition_penalty, + } + return result + + +func rank_candidates(guest_id: StringName, candidates: Array) -> Array[Dictionary]: + var ranked: Array[Dictionary] = [] + for raw_candidate: Variant in candidates: + if raw_candidate is Dictionary: + var evaluated := evaluate_candidate(guest_id, raw_candidate) + if bool(evaluated.get(&"ok", true)): + ranked.append(evaluated) + ranked.sort_custom(_candidate_precedes) + return ranked + + +func select_best_candidate( + guest_id: StringName, + candidates: Array, + exit_threshold: int = DEFAULT_EXIT_THRESHOLD +) -> Dictionary: + var ranked := rank_candidates(guest_id, candidates) + for candidate: Dictionary in ranked: + if int(candidate[&"utility"]) >= exit_threshold: + return candidate.duplicate(true) + return {} + + +func decide( + guest_id: StringName, + facilities: Array, + absolute_minute: int, + context: Dictionary = {} +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var filtered := extract_candidates(guest_id, facilities, context) + var ranked := rank_candidates(guest_id, filtered) + var threshold := int(_value(context, &"exit_threshold", DEFAULT_EXIT_THRESHOLD)) + var reserve_callback: Callable = _value(context, &"reserve_callback", Callable()) + for candidate: Dictionary in ranked: + if int(candidate[&"utility"]) < threshold: + continue + if reserve_callback.is_valid(): + var reservation_result: Variant = reserve_callback.call( + guest_id, + candidate[&"facility_id"], + candidate.duplicate(true) + ) + if not _callback_succeeded(reservation_result): + continue + if not _transition_is_available(guest, GuestRuntimeType.State.MOVING, absolute_minute): + return _error_result("Guest cannot enter MOVING in this tick.") + guest.target_facility_id = candidate[&"facility_id"] + guest.path_handle = _copy(_value(candidate, &"path_handle", null)) + _apply_transition(guest, GuestRuntimeType.State.MOVING, absolute_minute, &"FACILITY_SELECTED") + last_error = "" + return { + &"ok": true, + &"action": &"MOVE_TO_FACILITY", + &"candidate": candidate.duplicate(true), + } + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot enter LEAVING in this tick.") + guest.target_facility_id = &"" + guest.path_handle = null + guest.exit_reason = StringName(_value(context, &"exit_reason", &"NO_SUITABLE_FACILITY")) + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, guest.exit_reason) + last_error = "" + return {&"ok": true, &"action": &"LEAVE", &"candidate": {}} + + +func transition_guest( + guest_id: StringName, + new_state: int, + absolute_minute: int, + reason: StringName = &"" +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if new_state < GuestRuntimeType.State.SPAWNING or new_state > GuestRuntimeType.State.INCAPACITATED: + return _error_result("Unknown guest state.") + if guest.state == new_state: + return {&"ok": true, &"changed": false, &"state": new_state} + if not _is_allowed_transition(guest.state, new_state): + return _error_result( + "Invalid guest transition: %s -> %s" % [ + GuestRuntimeType.State.keys()[guest.state], + GuestRuntimeType.State.keys()[new_state], + ] + ) + if not _transition_is_available(guest, new_state, absolute_minute): + _force_loop_guard(guest, absolute_minute) + return { + &"ok": false, + &"changed": true, + &"loop_guard": true, + &"state": guest.state, + } + _apply_transition(guest, new_state, absolute_minute, reason) + last_error = "" + return {&"ok": true, &"changed": true, &"state": new_state} + + +func enter_queue( + guest_id: StringName, + facility_id: StringName, + request_id: StringName, + absolute_minute: int, + initial_patience: int = -1, + enqueue_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or facility_id.is_empty() or request_id.is_empty(): + return _error_result("Guest, facility and request are required for queue entry.") + if not _transition_is_available(guest, GuestRuntimeType.State.QUEUING, absolute_minute): + return _error_result("Guest cannot enter QUEUING in this tick.") + if enqueue_callback.is_valid(): + var external_result: Variant = enqueue_callback.call(guest_id, facility_id, request_id) + if not _callback_succeeded(external_result): + return _error_result("Facility queue rejected the guest.") + var definition := get_definition(guest.guest_type_id) + var resolved_patience := initial_patience + if resolved_patience < 0: + resolved_patience = definition.base_patience if definition != null else 0 + guest.target_facility_id = facility_id + guest.queue_request_id = request_id + guest.patience_remaining = maxi(resolved_patience, 0) + _apply_transition(guest, GuestRuntimeType.State.QUEUING, absolute_minute, &"QUEUE_ENTERED") + last_error = "" + return {&"ok": true, &"patience_remaining": guest.patience_remaining} + + +## Closure asks the guest to choose again, without charging a patience penalty. +## The callback releases the owning facility request and inventory reservations. +func release_queue_for_replanning( + guest_id: StringName, + absolute_minute: int, + release_callback: Callable = Callable() +) -> Dictionary: + if _external_mutation_blocked: + return _error_result("Guest mutation is blocked while committed notifications are delivered.") + var guest := get_guest(guest_id) + if guest == null or guest.state != GuestRuntimeType.State.QUEUING: + return _error_result("Guest is not in a queue.") + if not _transition_is_available(guest, GuestRuntimeType.State.DECIDING, absolute_minute): + return _error_result("Guest cannot atomically replan the queue in this tick.") + var facility_id := guest.target_facility_id + var request_id := guest.queue_request_id + if facility_id.is_empty() or request_id.is_empty(): + return _error_result("Queued guest is missing a facility or request.") + if release_callback.is_valid(): + var external_result: Variant = release_callback.call(guest_id, facility_id, request_id) + if not _callback_succeeded(external_result): + return _error_result("Facility queue removal failed; guest state was not changed.") + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.path_handle = null + _apply_transition(guest, GuestRuntimeType.State.DECIDING, absolute_minute, &"FACILITY_CLOSED") + last_error = "" + return { + &"ok": true, + &"facility_id": facility_id, + &"request_id": request_id, + &"patience_remaining": guest.patience_remaining, + } + + +func tick_queue_patience( + guest_id: StringName, + absolute_minute: int, + recovery: int = 0, + leave_queue_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or guest.state != GuestRuntimeType.State.QUEUING: + return _error_result("Guest is not in a queue.") + var next_patience := maxi(guest.patience_remaining - 1 + recovery, 0) + if next_patience > 0: + guest.patience_remaining = next_patience + last_error = "" + return {&"ok": true, &"abandoned": false, &"patience_remaining": next_patience} + ## Preflight every internal mutation before invoking the external queue owner. + if not _transition_is_available(guest, GuestRuntimeType.State.ABANDONING, absolute_minute): + return _error_result("Guest cannot atomically abandon the queue in this tick.") + var facility_id := guest.target_facility_id + var request_id := guest.queue_request_id + if leave_queue_callback.is_valid(): + var external_result: Variant = leave_queue_callback.call( + guest_id, + facility_id, + request_id + ) + if not _callback_succeeded(external_result): + return _error_result("Facility queue removal failed; guest state was not changed.") + guest.patience_remaining = 0 + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.path_handle = null + _apply_transition(guest, GuestRuntimeType.State.ABANDONING, absolute_minute, &"PATIENCE_EXHAUSTED") + guest_queue_abandoned.emit(guest_id, facility_id, request_id) + last_error = "" + return {&"ok": true, &"abandoned": true, &"patience_remaining": 0} + + +func begin_service( + guest_id: StringName, + facility_id: StringName, + service_id: StringName, + absolute_minute: int +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or facility_id.is_empty() or service_id.is_empty(): + return _error_result("Guest, facility and service are required.") + if not _transition_is_available(guest, GuestRuntimeType.State.RECEIVING_SERVICE, absolute_minute): + return _error_result("Guest cannot begin service in this tick.") + guest.target_facility_id = facility_id + guest.current_service_id = service_id + guest.queue_request_id = &"" + _apply_transition(guest, GuestRuntimeType.State.RECEIVING_SERVICE, absolute_minute, &"SERVICE_STARTED") + last_error = "" + return {&"ok": true, &"service_id": service_id} + + +func interrupt_service( + guest_id: StringName, + service_id: StringName, + absolute_minute: int, + interrupt_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or service_id.is_empty(): + return _error_result("Guest and service are required.") + if guest.interrupted_service_ids.has(service_id): + return {&"ok": true, &"replayed": true, &"service_id": service_id} + if guest.current_service_id != service_id: + return _error_result("Guest is not receiving the specified service.") + if not _transition_is_available(guest, GuestRuntimeType.State.INTERRUPTED, absolute_minute): + return _error_result("Guest cannot enter INTERRUPTED in this tick.") + if interrupt_callback.is_valid(): + var external_result: Variant = interrupt_callback.call(guest_id, service_id) + if not _callback_succeeded(external_result): + return _error_result("Service interruption callback failed.") + guest.interrupted_service_ids.append(service_id) + guest.interrupted_service_ids.sort_custom( + func(a: StringName, b: StringName) -> bool: return String(a) < String(b) + ) + guest.current_service_id = &"" + _apply_transition(guest, GuestRuntimeType.State.INTERRUPTED, absolute_minute, &"FACILITY_STOPPED") + last_error = "" + return {&"ok": true, &"replayed": false, &"service_id": service_id} + + +func complete_service( + guest_id: StringName, + service_id: StringName, + absolute_minute: int, + satisfaction_sample: int, + need_effects: Dictionary = {} +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or guest.current_service_id != service_id: + return _error_result("Guest is not receiving the specified service.") + var normalized_effects: Variant = _normalized_partial_need_map(need_effects) + if not normalized_effects is Dictionary: + return _error_result("Service Need effects must use canonical integer Need IDs.") + if not _transition_is_available(guest, GuestRuntimeType.State.DECIDING, absolute_minute): + return _error_result("Guest cannot complete service in this tick.") + var sample := clampi(satisfaction_sample, 0, 100) + guest.satisfaction_total += sample + guest.satisfaction_samples += 1 + for need_id: StringName in NEED_IDS: + var effect := int(normalized_effects.get(need_id, 0)) + guest.needs[need_id] = clampi(int(guest.needs[need_id]) + effect, 0, 100) + if not guest.target_facility_id.is_empty() \ + and not guest.visited_facility_ids.has(guest.target_facility_id): + guest.visited_facility_ids.append(guest.target_facility_id) + guest.visited_facility_ids.sort_custom( + func(a: StringName, b: StringName) -> bool: return String(a) < String(b) + ) + guest.current_service_id = &"" + guest.queue_request_id = &"" + guest.target_facility_id = &"" + _apply_transition(guest, GuestRuntimeType.State.DECIDING, absolute_minute, &"SERVICE_COMPLETED") + last_error = "" + return {&"ok": true, &"satisfaction_sample": sample} + + +func record_path_failure( + guest_id: StringName, + absolute_minute: int, + recovery_targets: Array = [], + reachability_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + guest.path_failure_count += 1 + if guest.path_failure_count < PATH_FAILURE_LIMIT: + if guest.state != GuestRuntimeType.State.REPLANNING: + var transition := transition_guest( + guest_id, + GuestRuntimeType.State.REPLANNING, + absolute_minute, + &"PATH_INVALID" + ) + if not bool(transition.get(&"ok", false)): + return transition + last_error = "" + return { + &"ok": true, + &"recovery_started": false, + &"failure_count": guest.path_failure_count, + } + + var selected := _select_recovery_target(guest, recovery_targets, reachability_callback) + if selected.is_empty(): + ## A virtual emergency egress is preferable to leaving an entity stranded. + selected = { + &"kind": &"emergency_despawn", + &"cell": guest.current_cell, + &"path_handle": {&"virtual_evacuation": true}, + } + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot enter safe recovery in this tick.") + guest.recovery_kind = StringName(_value(selected, &"kind", &"emergency_exit")) + guest.recovery_target_cell = _vector3i(_value(selected, &"cell", guest.current_cell)) + guest.path_handle = _copy(_value(selected, &"path_handle", { + &"recovery": true, + &"target_cell": guest.recovery_target_cell, + })) + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.exit_reason = &"PATH_RECOVERY" + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, &"PATH_FAILURE_LIMIT") + _diagnostics.append({ + &"code": &"GUEST_PATH_RECOVERY", + &"guest_id": String(guest_id), + &"minute": absolute_minute, + &"failure_count": guest.path_failure_count, + &"recovery_kind": String(guest.recovery_kind), + &"target_cell": _cell_to_array(guest.recovery_target_cell), + }) + guest_recovery_started.emit(guest_id, guest.recovery_kind, guest.recovery_target_cell) + last_error = "" + return { + &"ok": true, + &"recovery_started": true, + &"failure_count": guest.path_failure_count, + &"recovery_kind": guest.recovery_kind, + &"target_cell": guest.recovery_target_cell, + } + + +func record_path_success(guest_id: StringName, cell: Vector3i, absolute_minute: int) -> bool: + var guest := get_guest(guest_id) + if guest == null or absolute_minute < 0: + return _fail("A valid guest and minute are required.") + guest.current_cell = cell + guest.last_progress_cell = cell + guest.last_progress_at = absolute_minute + guest.path_failure_count = 0 + guest.recovery_kind = &"" + last_error = "" + return true + + +func note_movement( + guest_id: StringName, + cell: Vector3i, + absolute_minute: int, + recovery_targets: Array = [], + reachability_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if cell != guest.last_progress_cell: + record_path_success(guest_id, cell, absolute_minute) + return {&"ok": true, &"progressed": true, &"replanned": false} + guest.current_cell = cell + if absolute_minute - guest.last_progress_at < STUCK_REPLAN_MINUTES: + return {&"ok": true, &"progressed": false, &"replanned": false} + guest.last_progress_at = absolute_minute + var result := record_path_failure( + guest_id, + absolute_minute, + recovery_targets, + reachability_callback + ) + result[&"progressed"] = false + result[&"replanned"] = true + return result + + +func begin_leaving( + guest_id: StringName, + absolute_minute: int, + reason: StringName +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot begin leaving in this tick.") + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.exit_reason = reason + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, reason) + last_error = "" + return {&"ok": true} + + +func despawn_guest(guest_id: StringName, absolute_minute: int) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if guest.state != GuestRuntimeType.State.LEAVING \ + and guest.state != GuestRuntimeType.State.DESPAWNING: + return _error_result("Only a leaving guest can despawn.") + if guest.state != GuestRuntimeType.State.DESPAWNING: + if not _transition_is_available(guest, GuestRuntimeType.State.DESPAWNING, absolute_minute): + return _error_result("Guest cannot enter DESPAWNING in this tick.") + _apply_transition(guest, GuestRuntimeType.State.DESPAWNING, absolute_minute, &"EXIT_REACHED") + var record := { + &"guest_id": String(guest.guest_id), + &"guest_type_id": String(guest.guest_type_id), + &"debt_gold": guest.debt_gold, + &"unresolved_incident_ids": _names_to_strings(guest.unresolved_incident_ids), + &"outstanding_reservation_ids": _names_to_strings(guest.outstanding_reservation_ids), + } + _persistent_records[guest_id] = record + _guests.erase(guest_id) + _pending_spawn_notifications.erase(guest_id) + _guest_order_dirty = true + _decision_rejections.erase(guest_id) + guest_despawned.emit(guest_id, record.duplicate(true)) + last_error = "" + return {&"ok": true, &"persistent_record": record.duplicate(true)} + + +func create_snapshot() -> Dictionary: + var saved_definitions: Array[Dictionary] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + saved_definitions.append((_definitions[guest_type_id] as GuestDefinition).create_snapshot()) + var saved_guests: Array[Dictionary] = [] + for guest_id: StringName in _ordered_guest_ids(): + saved_guests.append((_guests[guest_id] as GuestRuntime).create_snapshot()) + var saved_records: Array[Dictionary] = [] + for guest_id: StringName in _sorted_ids(_persistent_records.keys()): + saved_records.append((_persistent_records[guest_id] as Dictionary).duplicate(true)) + return { + &"version": SNAPSHOT_VERSION, + &"current_minute": current_minute, + &"definitions": saved_definitions, + &"guests": saved_guests, + &"persistent_records": saved_records, + &"diagnostics": _diagnostics.duplicate(true), + } + + +func restore_snapshot(snapshot: Dictionary) -> bool: + var raw_version: Variant = snapshot.get(&"version", snapshot.get("version", null)) + if not raw_version is int or int(raw_version) not in [1, SNAPSHOT_VERSION]: + return _fail("Unsupported GuestService snapshot version.") + var prepared: Variant = snapshot.duplicate(true) + if int(raw_version) == 1: + prepared = _migrate_v1_snapshot(snapshot) + if not prepared is Dictionary: + return _fail("GuestService v1 Need schema is invalid or mixed.") + var candidate: Dictionary = prepared + if not candidate.get(&"definitions", null) is Array \ + or not candidate.get(&"guests", null) is Array \ + or not candidate.get(&"persistent_records", null) is Array \ + or not candidate.get(&"diagnostics", null) is Array: + return _fail("GuestService snapshot arrays are missing.") + var next_minute := int(candidate.get(&"current_minute", -2)) + if next_minute < -1: + return _fail("GuestService snapshot minute is invalid.") + + ## Parse into temporary containers. Live state is replaced only after every + ## cross-reference and invariant has been validated. + var next_definitions: Dictionary = {} + for raw_definition: Variant in candidate[&"definitions"]: + if not raw_definition is Dictionary: + return _fail("Guest definition snapshot has an invalid type.") + var definition := GuestDefinitionType.from_snapshot(raw_definition) + if definition == null or next_definitions.has(definition.guest_type_id): + return _fail("Guest definition snapshot is invalid or duplicated.") + next_definitions[definition.guest_type_id] = definition + + var next_guests: Dictionary = {} + for raw_guest: Variant in candidate[&"guests"]: + if not raw_guest is Dictionary: + return _fail("Guest runtime snapshot has an invalid type.") + var guest := GuestRuntimeType.from_snapshot(raw_guest) + if guest == null or next_guests.has(guest.guest_id): + return _fail("Guest runtime snapshot is invalid or duplicated.") + if not next_definitions.has(guest.guest_type_id): + return _fail("Guest runtime snapshot references an unknown definition.") + next_guests[guest.guest_id] = guest + + var next_records: Dictionary = {} + for raw_record: Variant in candidate[&"persistent_records"]: + if not raw_record is Dictionary or not _persistent_record_is_valid(raw_record): + return _fail("Persistent guest record is invalid.") + var record_id := StringName(raw_record.get(&"guest_id", "")) + if next_records.has(record_id) or next_guests.has(record_id): + return _fail("Persistent guest id is duplicated or still active.") + next_records[record_id] = raw_record.duplicate(true) + + var next_diagnostics: Array[Dictionary] = [] + for raw_diagnostic: Variant in candidate[&"diagnostics"]: + if not raw_diagnostic is Dictionary: + return _fail("Guest diagnostic snapshot has an invalid type.") + next_diagnostics.append(raw_diagnostic.duplicate(true)) + + _definitions = next_definitions + _guests = next_guests + _guest_order_dirty = true + _persistent_records = next_records + _decision_rejections = {} + _diagnostics = next_diagnostics + _pending_spawn_notifications.clear() + _external_mutation_blocked = false + current_minute = next_minute + last_error = "" + return true + + +func validate_invariants() -> Dictionary: + var errors: Array[String] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + var definition: GuestDefinition = _definitions[guest_type_id] + if not definition.is_valid() or definition.guest_type_id != guest_type_id: + errors.append("Invalid definition: %s" % guest_type_id) + for guest_id: StringName in _sorted_ids(_guests.keys()): + var guest: GuestRuntime = _guests[guest_id] + if not guest.is_valid() or guest.guest_id != guest_id: + errors.append("Invalid runtime: %s" % guest_id) + elif not _definitions.has(guest.guest_type_id): + errors.append("Unknown guest type for: %s" % guest_id) + if _persistent_records.has(guest_id): + errors.append("Guest is both active and persisted: %s" % guest_id) + for guest_id: StringName in _sorted_ids(_persistent_records.keys()): + if not _persistent_record_is_valid(_persistent_records[guest_id]): + errors.append("Invalid persistent record: %s" % guest_id) + return {&"valid": errors.is_empty(), &"errors": errors} + + +func _migrate_v1_snapshot(snapshot: Dictionary) -> Variant: + var definitions: Variant = snapshot.get(&"definitions", snapshot.get("definitions", null)) + var guests: Variant = snapshot.get(&"guests", snapshot.get("guests", null)) + if not definitions is Array or not guests is Array: + return null + var migrated := snapshot.duplicate(true) + for index: int in range(definitions.size()): + var raw_definition: Variant = definitions[index] + if not raw_definition is Dictionary: + return null + var translated_rates: Variant = _translate_v1_need_map( + _value(raw_definition, &"need_rates", null) + ) + if not translated_rates is Dictionary: + return null + migrated[&"definitions"][index][&"need_rates"] = translated_rates + for index: int in range(guests.size()): + var raw_guest: Variant = guests[index] + if not raw_guest is Dictionary: + return null + var translated_needs: Variant = _translate_v1_need_map( + _value(raw_guest, &"needs", null) + ) + if not translated_needs is Dictionary: + return null + migrated[&"guests"][index][&"needs"] = translated_needs + migrated[&"version"] = SNAPSHOT_VERSION + return migrated + + +static func _translate_v1_need_map(source: Variant) -> Variant: + if not source is Dictionary or source.size() != LEGACY_NEED_IDS.size(): + return null + var legacy: Dictionary = {} + for raw_key: Variant in source: + if not raw_key is String and not raw_key is StringName: + return null + var legacy_id := StringName(raw_key) + if not LEGACY_NEED_IDS.has(legacy_id) or legacy.has(legacy_id): + return null + var raw_value: Variant = source[raw_key] + if not raw_value is int: + return null + legacy[legacy_id] = raw_value + if legacy.size() != LEGACY_NEED_IDS.size(): + return null + return { + &"hunger": legacy[&"hunger"], + &"sleep": legacy[&"rest"], + &"hygiene": 0, + &"medical": legacy[&"safety"], + &"leisure": legacy[&"fun"], + &"intimacy": legacy[&"social"], + } + + +func _need_context_error(context: Dictionary) -> String: + for field_id: StringName in [ + &"dirt", + &"injury", + &"illness", + &"intimacy_day", + &"intimacy_night", + ]: + if not _has_value(context, field_id): + continue + var raw_value: Variant = _value(context, field_id, null) + if not raw_value is int or int(raw_value) < 0 or int(raw_value) > 100: + return "%s must be an integer from 0 through 100." % field_id + for field_id: StringName in [&"is_night", &"work_like_activity", &"unstimulated"]: + if _has_value(context, field_id) and not _value(context, field_id, null) is bool: + return "%s must be boolean." % field_id + if _has_value(context, &"need_effects"): + var effects: Variant = _normalized_partial_need_map( + _value(context, &"need_effects", null) + ) + if not effects is Dictionary: + return "need_effects must contain only canonical integer Need IDs." + return "" + + +static func _normalized_partial_need_map(source: Variant) -> Variant: + if not source is Dictionary or source.size() > NEED_IDS.size(): + return null + var result: Dictionary = {} + for raw_key: Variant in source: + if not raw_key is String and not raw_key is StringName: + return null + var need_id := StringName(raw_key) + if not NEED_IDS.has(need_id) or result.has(need_id): + return null + var raw_value: Variant = source[raw_key] + if not raw_value is int: + return null + result[need_id] = raw_value + return result + + +static func _candidate_need_maps_are_valid(candidate: Dictionary) -> bool: + for field_id: StringName in [&"need_relief", &"need_effects"]: + if not _has_value(candidate, field_id): + continue + var raw_value: Variant = _value(candidate, field_id, null) + if field_id == &"need_relief" and raw_value is int: + continue + if not _normalized_partial_need_map(raw_value) is Dictionary: + return false + return true + + +func _candidate_rejection_reason( + guest: GuestRuntime, + definition: GuestDefinition, + candidate: Dictionary, + context: Dictionary +) -> StringName: + if not _candidate_need_maps_are_valid(candidate): + return &"INVALID_NEED_MAP" + if not bool(_value(candidate, &"inputs_available", true)): + return &"MISSING_INPUTS" + var adult_only := bool(_value( + candidate, + &"adult_only", + _value(candidate, &"requires_adult_verified", false) + )) + if adult_only: + if not definition.adult_verified: + return &"ADULT_NOT_VERIFIED" + if not bool(_value(context, &"adult_content_enabled", true)): + return &"CONTENT_FILTERED" + var required_tags := _name_array(_value(candidate, &"required_guest_tags", [])) + for tag: StringName in required_tags: + if not definition.tags.has(tag): + return &"MISSING_PERMISSION" + var forbidden_tags := _name_array(_value(candidate, &"forbidden_guest_tags", [])) + for tag: StringName in forbidden_tags: + if definition.tags.has(tag): + return &"FORBIDDEN_GUEST_TAG" + var allowed_types := _name_array(_value(candidate, &"allowed_guest_types", [])) + if not allowed_types.is_empty() and not allowed_types.has(guest.guest_type_id): + return &"GUEST_TYPE_NOT_ALLOWED" + if not bool(_value(candidate, &"is_open", _value(candidate, &"open", true))) \ + and not bool(_value(candidate, &"open_at_arrival", false)): + return &"FACILITY_CLOSED" + if not _candidate_is_reachable(guest, candidate, context): + return &"UNREACHABLE" + var travel_minutes := maxi(int(_value(candidate, &"travel_minutes", 0)), 0) + var decision_minute := int(_value(context, &"absolute_minute", current_minute)) + if decision_minute >= 0 and decision_minute + travel_minutes > guest.planned_exit_at: + return &"ETA_AFTER_EXIT" + if not _candidate_is_affordable(guest, definition, candidate): + return &"CANNOT_PAY" + var wait_minutes := maxi(int(_value( + candidate, + &"wait_minutes", + _value(candidate, &"expected_wait", 0) + )), 0) + var hard_limit := maxi(int(_value( + candidate, + &"wait_hard_limit", + _value(context, &"wait_hard_limit", DEFAULT_WAIT_HARD_LIMIT) + )), 0) + if wait_minutes >= hard_limit: + return &"WAIT_HARD_LIMIT" + var risk_level := clampi(int(_value(candidate, &"risk_level", _value(candidate, &"risk", 0))), 0, 100) + if risk_level > definition.risk_tolerance: + return &"RISK_REFUSED" + var illegal_level := clampi(int(_value(candidate, &"illegal_level", 0)), 0, 100) + if illegal_level > 100 - definition.legal_sensitivity \ + and not bool(_value(candidate, &"legal_permission_override", false)): + return &"ILLEGALITY_REFUSED" + var quality := clampi(int(_value(candidate, &"expected_quality", _value(candidate, &"quality", 0))), 0, 100) + var minimum_quality := clampi(int(_value(candidate, &"minimum_quality", definition.minimum_quality)), 0, 100) + if quality < minimum_quality: + return &"QUALITY_TOO_LOW" + return &"" + + +func _candidate_is_reachable( + guest: GuestRuntime, + candidate: Dictionary, + context: Dictionary +) -> bool: + var callback: Callable = _value(context, &"reachability_callback", Callable()) + if callback.is_valid(): + return _callback_succeeded(callback.call(guest, candidate.duplicate(true), context)) + return bool(_value(candidate, &"reachable", true)) + + +func _candidate_is_affordable( + guest: GuestRuntime, + definition: GuestDefinition, + candidate: Dictionary +) -> bool: + var price := _candidate_price(candidate) + var cash_available := guest.wallet_gold + var respect_reserve := bool(_value( + candidate, + &"respect_survival_reserve", + _candidate_is_discretionary(candidate) + )) + if respect_reserve: + cash_available = maxi(cash_available - _survival_reserve(guest, definition), 0) + if price <= cash_available: + return true + if not bool(_value(candidate, &"credit_allowed", _value(candidate, &"allow_credit", false))): + return false + var remaining_credit := maxi(guest.credit_limit_gold - guest.debt_gold, 0) + return price - cash_available <= remaining_credit + + +func _candidate_is_discretionary(candidate: Dictionary) -> bool: + if bool(_value(candidate, &"discretionary", false)): + return true + var category := StringName(_value(candidate, &"service_category", &"")) + if category in [&"leisure", &"entertainment", &"gambling", &"intimacy", &"tourism"]: + return true + var facility_tags := _name_array(_value( + candidate, + &"facility_tags", + _value(candidate, &"tags", []) + )) + for tag: StringName in [&"leisure", &"entertainment", &"gambling", &"adult", &"intimacy"]: + if facility_tags.has(tag): + return true + return false + + +func _survival_reserve(guest: GuestRuntime, definition: GuestDefinition) -> int: + if guest.max_addiction() >= 75: + return 0 + if definition.tags.has(&"spendthrift"): + return guest.wallet_gold / 10 + if definition.tags.has(&"frugal"): + return guest.wallet_gold / 2 + return guest.wallet_gold * 3 / 10 + + +func _candidate_price(candidate: Dictionary) -> int: + return maxi(int(_value( + candidate, + &"estimated_total_gold", + _value(candidate, &"price_gold", _value(candidate, &"quoted_price", 0)) + )), 0) + + +func _price_burden(guest: GuestRuntime, candidate: Dictionary) -> int: + var price := _candidate_price(candidate) + var purchasing_power := maxi(guest.wallet_gold + maxi(guest.credit_limit_gold - guest.debt_gold, 0), 1) + return clampi((price * 100 + purchasing_power - 1) / purchasing_power, 0, 100) + + +func _need_relief(guest: GuestRuntime, candidate: Dictionary) -> int: + var raw_relief: Variant = _value(candidate, &"need_relief", 0) + if raw_relief is int: + return maxi(int(raw_relief), 0) + if not raw_relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(raw_relief) + var best := 0 + for need_id: StringName in NEED_IDS: + var relief := maxi(int(normalized.get(need_id, 0)), 0) + best = maxi(best, relief * int(guest.needs[need_id]) / 100) + return best + + +func _candidate_urgency(guest: GuestRuntime, candidate: Dictionary) -> int: + var raw_relief: Variant = _value(candidate, &"need_relief", {}) + if not raw_relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(raw_relief) + var result := 0 + for need_id: StringName in NEED_IDS: + if int(normalized.get(need_id, 0)) > 0: + result = maxi(result, int(guest.needs[need_id])) + return result + + +func _primary_need_priority(guest: GuestRuntime, candidate: Dictionary) -> int: + var relief: Variant = _value(candidate, &"need_relief", {}) + if not relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(relief) + var priority := 0 + for need: StringName in PRIMARY_NEEDS: + if int(normalized.get(need, 0)) > 0: + priority = maxi(priority, int(guest.needs[need])) + return priority + + +func get_observation(guest_id: StringName) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return {} + var highest := 0 + var primary: Array[StringName] = [] + for need: StringName in PRIMARY_NEEDS: + var value := int(guest.needs[need]) + if value > highest: + highest = value + primary.clear() + if value == highest and value > 0: + primary.append(need) + var observed_needs: Dictionary = {} + for need: StringName in NEED_IDS: + observed_needs[need] = guest.needs[need] + return {&"guest_id": guest.guest_id, &"state": guest.state, &"needs": observed_needs, + &"primary_needs": primary, &"target_facility_id": guest.target_facility_id, + &"wallet_gold": guest.wallet_gold, &"patience": guest.patience_remaining} + + +func _candidate_precedes(a: Dictionary, b: Dictionary) -> bool: + var a_priority := int(a.get(&"need_priority", 0)) + var b_priority := int(b.get(&"need_priority", 0)) + if a_priority != b_priority: + return a_priority > b_priority + var a_utility := int(a[&"utility"]) + var b_utility := int(b[&"utility"]) + if a_utility != b_utility: + return a_utility > b_utility + var a_urgency := int(a[&"urgency"]) + var b_urgency := int(b[&"urgency"]) + if a_urgency != b_urgency: + return a_urgency > b_urgency + var a_travel := int(a[&"travel_minutes"]) + var b_travel := int(b[&"travel_minutes"]) + if a_travel != b_travel: + return a_travel < b_travel + return String(a[&"facility_id"]) < String(b[&"facility_id"]) + + +func _select_recovery_target( + guest: GuestRuntime, + targets: Array, + reachability_callback: Callable +) -> Dictionary: + var ordered: Array[Dictionary] = [] + for raw_target: Variant in targets: + if raw_target is Dictionary: + var target: Dictionary = raw_target.duplicate(true) + target[&"kind"] = StringName(_value(target, &"kind", &"emergency_exit")) + target[&"cell"] = _vector3i(_value(target, &"cell", guest.current_cell)) + ordered.append(target) + ordered.sort_custom( + func(a: Dictionary, b: Dictionary) -> bool: + var a_priority := _recovery_priority(StringName(a[&"kind"])) + var b_priority := _recovery_priority(StringName(b[&"kind"])) + if a_priority != b_priority: + return a_priority < b_priority + return _cell_precedes(a[&"cell"], b[&"cell"]) + ) + for target: Dictionary in ordered: + var reachable := bool(_value(target, &"reachable", true)) + if reachability_callback.is_valid(): + reachable = _callback_succeeded(reachability_callback.call( + guest.current_cell, + target[&"cell"], + guest.guest_id, + target.duplicate(true) + )) + if reachable: + return target + return {} + + +func _recovery_priority(kind: StringName) -> int: + match kind: + &"guidance", &"guidance_point": + return 0 + &"entrance", &"main_entrance": + return 1 + &"emergency_exit", &"emergency_egress": + return 2 + _: + return 3 + + +func _transition_is_available(guest: GuestRuntime, new_state: int, minute: int) -> bool: + if minute < 0 or not _is_allowed_transition(guest.state, new_state): + return false + var count := guest.transitions_this_tick if guest.transition_tick == minute else 0 + return count < MAX_TRANSITIONS_PER_TICK + + +func _apply_transition( + guest: GuestRuntime, + new_state: int, + absolute_minute: int, + reason: StringName +) -> void: + var old_state := guest.state + if guest.transition_tick != absolute_minute: + guest.transition_tick = absolute_minute + guest.transitions_this_tick = 0 + guest.transitions_this_tick += 1 + guest.state = new_state as GuestRuntime.State + guest.state_entered_at = absolute_minute + guest_state_changed.emit(guest.guest_id, old_state, new_state, reason) + + +func _force_loop_guard(guest: GuestRuntime, absolute_minute: int) -> void: + var old_state := guest.state + guest.state = GuestRuntimeType.State.DECIDING + guest.state_entered_at = absolute_minute + guest.transition_tick = absolute_minute + guest.transitions_this_tick = MAX_TRANSITIONS_PER_TICK + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.path_handle = null + _diagnostics.append({ + &"code": &"AI_LOOP_GUARD", + &"guest_id": String(guest.guest_id), + &"minute": absolute_minute, + }) + guest_state_changed.emit( + guest.guest_id, + old_state, + GuestRuntimeType.State.DECIDING, + &"AI_LOOP_GUARD" + ) + + +func _is_allowed_transition(from_state: int, to_state: int) -> bool: + if from_state == to_state: + return true + if to_state in [ + GuestRuntimeType.State.LEAVING, + GuestRuntimeType.State.FLEEING, + GuestRuntimeType.State.INCAPACITATED, + ]: + return from_state != GuestRuntimeType.State.DESPAWNING + match from_state: + GuestRuntimeType.State.SPAWNING: + return to_state in [GuestRuntimeType.State.ENTERING, GuestRuntimeType.State.DECIDING] + GuestRuntimeType.State.ENTERING: + return to_state in [GuestRuntimeType.State.DECIDING, GuestRuntimeType.State.MOVING] + GuestRuntimeType.State.DECIDING: + return to_state in [GuestRuntimeType.State.MOVING, GuestRuntimeType.State.QUEUING] + GuestRuntimeType.State.MOVING: + return to_state in [ + GuestRuntimeType.State.QUEUING, + GuestRuntimeType.State.RECEIVING_SERVICE, + GuestRuntimeType.State.DECIDING, + GuestRuntimeType.State.REPLANNING, + ] + GuestRuntimeType.State.QUEUING: + return to_state in [ + GuestRuntimeType.State.RECEIVING_SERVICE, + GuestRuntimeType.State.ABANDONING, + GuestRuntimeType.State.DECIDING, + ] + GuestRuntimeType.State.RECEIVING_SERVICE: + return to_state in [GuestRuntimeType.State.DECIDING, GuestRuntimeType.State.INTERRUPTED] + GuestRuntimeType.State.REPLANNING: + return to_state in [GuestRuntimeType.State.MOVING, GuestRuntimeType.State.DECIDING] + GuestRuntimeType.State.ABANDONING, GuestRuntimeType.State.INTERRUPTED: + return to_state == GuestRuntimeType.State.DECIDING + GuestRuntimeType.State.FLEEING: + return to_state in [GuestRuntimeType.State.LEAVING, GuestRuntimeType.State.DESPAWNING] + GuestRuntimeType.State.LEAVING: + return to_state == GuestRuntimeType.State.DESPAWNING + _: + return false + + +static func _callback_succeeded(result: Variant) -> bool: + if result == null: + return true + if result is bool: + return result + if result is Dictionary: + return bool(result.get(&"ok", result.get("ok", result.get(&"success", result.get("success", false))))) + if result is Object and result.has_method("is_success"): + return bool(result.call("is_success")) + return false + + +static func _persistent_record_is_valid(record: Dictionary) -> bool: + if StringName(record.get(&"guest_id", "")).is_empty(): + return false + if StringName(record.get(&"guest_type_id", "")).is_empty(): + return false + if int(record.get(&"debt_gold", -1)) < 0: + return false + return record.get(&"unresolved_incident_ids", null) is Array \ + and record.get(&"outstanding_reservation_ids", null) is Array + + +static func _facility_id(candidate: Dictionary) -> StringName: + return StringName(_value( + candidate, + &"facility_id", + _value(candidate, &"instance_id", &"") + )) + + +static func _name_array(source: Variant) -> Array[StringName]: + var result: Array[StringName] = [] + if not source is Array: + return result + for raw_value: Variant in source: + var value := StringName(raw_value) + if not value.is_empty() and not result.has(value): + result.append(value) + result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) + return result + + +static func _sorted_ids(source: Array) -> Array[StringName]: + var result: Array[StringName] = [] + for raw_value: Variant in source: + result.append(StringName(raw_value)) + result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) + return result + + +func _ordered_guest_ids() -> Array[StringName]: + if _guest_order_dirty: + _sorted_guest_ids_cache = _sorted_ids(_guests.keys()) + _guest_order_dirty = false + return _sorted_guest_ids_cache + + +static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: + if source.has(key): + return source[key] + var string_key := String(key) + return source[string_key] if source.has(string_key) else fallback + + +static func _has_value(source: Dictionary, key: StringName) -> bool: + return source.has(key) or source.has(String(key)) + + +static func _need_report(values: Dictionary) -> Dictionary: + # Matches _string_key_dictionary's canonical lexical key order without + # allocating and sorting a six-key array for every guest and minute. + return { + "hunger": int(values[&"hunger"]), + "hygiene": int(values[&"hygiene"]), + "intimacy": int(values[&"intimacy"]), + "leisure": int(values[&"leisure"]), + "medical": int(values[&"medical"]), + "sleep": int(values[&"sleep"]), + } + + +static func _copy(value: Variant) -> Variant: + if value is Array or value is Dictionary: + return value.duplicate(true) + return value + + +static func _vector3i(value: Variant) -> Vector3i: + if value is Vector3i: + return value + if value is Array and value.size() == 3: + return Vector3i(int(value[0]), int(value[1]), int(value[2])) + return Vector3i.ZERO + + +static func _cell_to_array(cell: Vector3i) -> Array[int]: + return [cell.x, cell.y, cell.z] + + +static func _cell_precedes(a: Vector3i, b: Vector3i) -> bool: + if a.z != b.z: + return a.z < b.z + if a.y != b.y: + return a.y < b.y + return a.x < b.x + + +static func _names_to_strings(source: Array[StringName]) -> Array[String]: + var result: Array[String] = [] + for value: StringName in source: + result.append(String(value)) + return result + + +static func _string_key_dictionary(source: Dictionary) -> Dictionary: + var result: Dictionary = {} + var keys: Array = source.keys() + keys.sort_custom(func(a: Variant, b: Variant) -> bool: return String(a) < String(b)) + for raw_key: Variant in keys: + result[String(raw_key)] = source[raw_key] + return result + + +func _fail(message: String) -> bool: + last_error = message + return false + + +func _error_result(message: String) -> Dictionary: + last_error = message + return {&"ok": false, &"error": message} + + +# No retained cache and no observation is moved across a user callback. All +# expressions evaluated between the three original helper positions are pure +# builtin conversions under this guard. Extra candidate fields stay untouched. +func _can_share_candidate_need_map(guest: GuestRuntime, definition: GuestDefinition, candidate: Dictionary) -> bool: + if get_script() != load("res://scripts/guest/guest_service.gd") \ + or guest.get_script() != GuestRuntimeType \ + or definition.get_script() != GuestDefinitionType: + return false + if not _value(candidate, &"need_relief", null) is Dictionary: + return false + for need: StringName in NEED_IDS: + if not guest.needs.get(need, null) is int: + return false + for field: StringName in [&"preference", &"expected_quality", &"quality", &"novelty", &"group_fit", &"price_burden", &"travel_minutes", &"wait_minutes", &"expected_wait", &"risk", &"risk_level", &"risk_sensitivity", &"repetition_penalty", &"urgency", &"estimated_total_gold", &"price_gold", &"quoted_price"]: + if _has_value(candidate, field) and not _value(candidate, field, null) is int: + return false + for field: StringName in [&"facility_id", &"instance_id"]: + if _has_value(candidate, field): + var value: Variant = _value(candidate, field, null) + if not value is String and not value is StringName: + return false + for field: StringName in [&"facility_tags", &"tags"]: + if _has_value(candidate, field): + var value: Variant = _value(candidate, field, null) + if not value is Array: + return false + for tag: Variant in value: + if not tag is String and not tag is StringName: + return false + for value: Variant in definition.preference_weights.values(): + if not value is int: + return false + return true + + +func _need_relief_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var best := 0 + for need_id: StringName in NEED_IDS: + var relief := maxi(int(normalized.get(need_id, 0)), 0) + best = maxi(best, relief * int(guest.needs[need_id]) / 100) + return best + + +func _candidate_urgency_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var result := 0 + for need_id: StringName in NEED_IDS: + if int(normalized.get(need_id, 0)) > 0: + result = maxi(result, int(guest.needs[need_id])) + return result + + +func _primary_need_priority_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var priority := 0 + for need: StringName in PRIMARY_NEEDS: + if int(normalized.get(need, 0)) > 0: + priority = maxi(priority, int(guest.needs[need])) + return priority diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/structural-check.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/structural-check.json new file mode 100644 index 0000000..aa23677 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r295/structural-check.json @@ -0,0 +1,8 @@ +{ + "inverse_original_byteexact": true, + "three_pure_loop_bodies_byteexact": true, + "parser_executed": false, + "runtime_equivalence_accepted": false, + "production_stage_edits": 0, + "unit_accepted": false +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/input.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/input.json new file mode 100644 index 0000000..bed87b9 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/input.json @@ -0,0 +1,133 @@ +{ + "pins": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd", + "bytes": 63639, + "sha256": "5895d5da6488c767685b109ef01f9e2b57f41272d49cc9e3c415865769adc11d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json", + "bytes": 9244, + "sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-actual-independent-review-r296.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-actual-independent-review-r296.json", + "bytes": 19007, + "sha256": "ce06bfe9156ed8e1c9ec53519d6cc52b26c963ca0e8b4a263ff70d1a1abf9a23" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/actual-root-verification.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/actual-root-verification.json", + "bytes": 10454, + "sha256": "6d9dc235ac63d6629e4375e5b75516f522f47d4d491b40cda790da14fa65f4fe" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json", + "bytes": 10788, + "sha256": "d01a859ce75a18084cbec7a081c906e267d1e8c7a42bce55e2539dd3a7ede8c3" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/input.json", + "bytes": 4511, + "sha256": "6bd46c589bc57ab5afad1d7195f7293ec450aafeed055e394a3a880744666ca3" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_definition.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_definition.gd", + "bytes": 9374, + "sha256": "082aa3f33b9cb17daf10a2cf8a1dece7b79dd80c2ae21e1a424d1b2ceb52b918" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_runtime.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/scripts/guest/guest_runtime.gd", + "bytes": 14267, + "sha256": "57ba99e5d977b5302987f4359a19056f1ab09c8fb7e4762ee8d79cbb9316741a" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.cpp": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.cpp", + "bytes": 94629, + "sha256": "4af0a620aa59d2376d1b35ab201f26edd352f056c908b970890ad9a3449bb5c0" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.cpp.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.cpp.source.json", + "bytes": 278, + "sha256": "dfce2e51d892ac01b198055551cc899c06aa1b9962f20bca687dbb1c31e2bd9d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.h": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.h", + "bytes": 34956, + "sha256": "3d7d7c8bdc02d4ac73d06b16f487a9ba0f9a188c854ab0befc57b98f807523b2" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.h.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant.h.source.json", + "bytes": 276, + "sha256": "42d0b1ef9b9f5329029e4250cc97533bd800ccfea2fa18d6586c21f7973009af" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.cpp": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.cpp", + "bytes": 19030, + "sha256": "51e8ebbbffb9c65959aa4c86f7dd1a21ed121ef82706996893743de077c82645" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.cpp.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.cpp.source.json", + "bytes": 288, + "sha256": "7aed774b87af26bf775abeaa63a22df93cb1552398cc52ca3230800b1abdd555" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.h": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.h", + "bytes": 25252, + "sha256": "e6aecba36f8ff90af382427fb6be8b69e161583cffba7751b2c2ffdfa5e632c9" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.h.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_construct.h.source.json", + "bytes": 286, + "sha256": "e7c85dca32c621dd842706fe0851e74a63e288848b4055ca895d657059bcc312" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/dictionary.cpp": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/dictionary.cpp", + "bytes": 27721, + "sha256": "13eb030c2e59ac0e7060852949bf5b7a62624e31474b781d89e72515410daa7d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/dictionary.cpp.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/dictionary.cpp.source.json", + "bytes": 281, + "sha256": "032aaf8bb193500745770c940088a2223c1f3f6bb9f8edf6300693bb3e26fc7b" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_setget.cpp": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_setget.cpp", + "bytes": 62857, + "sha256": "e3f73e1df096bedd4856e29227b302f879d0520c66718467338556b589f685c9" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_setget.cpp.source.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-conversion-source-r297/variant_setget.cpp.source.json", + "bytes": 276, + "sha256": "f3002c603869280c99e5efc5f1e923258726c48c404571af34ba6f3bdc009cd2" + } + }, + "scope": "Only replace r295 sharing guard, retains original pure computation/return order. Exact builtin service/guest/definition guards unchanged; no retained cache.", + "parent": "r274/library275 native plus unadopted295 GDScript refinement", + "removed_guards_basis": [ + "Variant::_to_int and int constructors use scalar conversions; no Object/custom callback invoked", + "StringName constructor registrations and strict conversion selection do not invoke custom Object stringify", + "Dictionary::duplicate(true) uses RESOURCE_DEEP_DUPLICATE_NONE; nested Objects/Resources are returned unchanged by recursive_duplicate", + "Exact service/guest/definition checks retain custom properties/preference/helper dynamic fallback", + "Original Need map validation remains before sharing; shared map contains only validated INT scalar values" + ], + "r296_scope": "Accepted local231+2/1619 only, initial guard single-Need 2000calls 28461->41815us not performance gain or representative tick. Static298 and modified local/whole verification required; old result does not validate expanded guard.", + "inverse_parent_byteexact": true, + "source_edits": 0, + "stage_edits": 0, + "engine_runs": 0, + "compiler_runs": 0, + "unit_accepted": false, + "candidate_adopted": false, + "unverified": [ + "independent static callback/conversion/duplication proof", + "actual parse and expanded-guard local equivalence", + "new same-template helper observations including full-six Need map without extrapolating budget", + "original impact and whole same-version equality", + "full final regression and all17 acceptance conditions" + ] +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd new file mode 100644 index 0000000..74f4ef5 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-candidate-r298/source/guest_service.gd @@ -0,0 +1,1730 @@ +class_name GuestService +extends RefCounted + +## Deterministic guest needs, decisions and lifecycle authority. +## +## Facility, path and economy integrations intentionally cross Dictionary and +## Callable boundaries. GuestService therefore remains independently testable +## and cannot accidentally mutate another service's private state. + +signal guest_spawned(guest: GuestRuntime) +signal guest_state_changed(guest_id: StringName, old_state: int, new_state: int, reason: StringName) +signal guest_queue_abandoned(guest_id: StringName, facility_id: StringName, request_id: StringName) +signal guest_recovery_started(guest_id: StringName, recovery_kind: StringName, target_cell: Vector3i) +signal guest_despawned(guest_id: StringName, persistent_record: Dictionary) + +const GuestDefinitionType = preload("res://scripts/guest/guest_definition.gd") +const GuestRuntimeType = preload("res://scripts/guest/guest_runtime.gd") + +const SNAPSHOT_VERSION := 2 +const MAX_TRANSITIONS_PER_TICK := 4 +const PATH_FAILURE_LIMIT := 3 +const STUCK_REPLAN_MINUTES := 60 +const DEFAULT_WAIT_HARD_LIMIT := 120 +const DEFAULT_EXIT_THRESHOLD := 15 +const PRIMARY_NEEDS: Array[StringName] = [&"hunger", &"sleep", &"leisure", &"intimacy"] + +const NEED_IDS: Array[StringName] = [ + &"hunger", + &"sleep", + &"hygiene", + &"medical", + &"leisure", + &"intimacy", +] +const LEGACY_NEED_IDS: Array[StringName] = [ + &"hunger", + &"rest", + &"fun", + &"social", + &"safety", +] + +var current_minute := -1 +var last_error := "" + +var _definitions: Dictionary = {} +var _guests: Dictionary = {} +var _persistent_records: Dictionary = {} +var _decision_rejections: Dictionary = {} +var _diagnostics: Array[Dictionary] = [] +var _sorted_guest_ids_cache: Array[StringName] = [] +var _guest_order_dirty := true +var _pending_spawn_notifications: Dictionary = {} +var _external_mutation_blocked := false + + +func register_definition(definition: Variant) -> bool: + if definition == null or not definition is GuestDefinition: + return _fail("Guest definition has an invalid type.") + var typed_definition: GuestDefinition = definition + if not typed_definition.is_valid(): + return _fail("Guest definition is invalid.") + if _definitions.has(typed_definition.guest_type_id): + return _fail("Duplicate guest definition: %s" % typed_definition.guest_type_id) + _definitions[typed_definition.guest_type_id] = typed_definition + last_error = "" + return true + + +func create_definition(guest_type_id: StringName, options: Dictionary = {}) -> GuestDefinition: + var definition := GuestDefinitionType.new().configure(guest_type_id, options) + return definition if register_definition(definition) else null + + +func get_definition(guest_type_id: StringName) -> GuestDefinition: + return _definitions.get(guest_type_id) as GuestDefinition + + +func get_definitions() -> Array[GuestDefinition]: + var result: Array[GuestDefinition] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + result.append(_definitions[guest_type_id]) + return result + + +func register_guest(guest: Variant, emit_notification := true) -> bool: + if _external_mutation_blocked: + return _fail("Guest mutation is blocked while committed notifications are delivered.") + if guest == null or not guest is GuestRuntime: + return _fail("Guest runtime has an invalid type.") + var runtime: GuestRuntime = guest + if not runtime.is_valid(): + return _fail("Guest runtime is invalid.") + if not _definitions.has(runtime.guest_type_id): + return _fail("Guest runtime references an unknown definition.") + if _guests.has(runtime.guest_id) or _persistent_records.has(runtime.guest_id): + return _fail("Duplicate guest id: %s" % runtime.guest_id) + _guests[runtime.guest_id] = runtime + _guest_order_dirty = true + if emit_notification: + guest_spawned.emit(runtime) + else: + _pending_spawn_notifications[runtime.guest_id] = true + last_error = "" + return true + + +func spawn_guest( + guest_id: StringName, + guest_type_id: StringName, + absolute_minute: int, + options: Dictionary = {}, + emit_notification := true +) -> GuestRuntime: + var definition := get_definition(guest_type_id) + if definition == null or absolute_minute < 0: + _fail("A known definition and non-negative spawn minute are required.") + return null + var runtime_options := options.duplicate(true) + if not _has_value(runtime_options, &"wallet_gold"): + runtime_options[&"wallet_gold"] = definition.wealth_min + if not _has_value(runtime_options, &"credit_limit_gold"): + runtime_options[&"credit_limit_gold"] = definition.credit_limit_gold + if not _has_value(runtime_options, &"patience_remaining"): + runtime_options[&"patience_remaining"] = definition.base_patience + if not _has_value(runtime_options, &"state"): + runtime_options[&"state"] = GuestRuntimeType.State.SPAWNING + if not _has_value(runtime_options, &"state_entered_at"): + runtime_options[&"state_entered_at"] = absolute_minute + if not _has_value(runtime_options, &"planned_exit_at"): + runtime_options[&"planned_exit_at"] = absolute_minute + 720 + var runtime := GuestRuntimeType.new().configure(guest_id, guest_type_id, runtime_options) + if not register_guest(runtime, emit_notification): + return null + return runtime + + +func commit_guest_spawned_notification(guest_id: StringName) -> bool: + if not _pending_spawn_notifications.has(guest_id) or not _guests.has(guest_id): + return _fail("Guest has no pending spawn notification: %s" % guest_id) + _pending_spawn_notifications.erase(guest_id) + guest_spawned.emit(_guests[guest_id] as GuestRuntime) + last_error = "" + return true + + +func discard_guest_spawned_notification(guest_id: StringName) -> bool: + if not _pending_spawn_notifications.has(guest_id): + return false + _pending_spawn_notifications.erase(guest_id) + return true + + +func has_pending_guest_spawned_notification(guest_id: StringName) -> bool: + return _pending_spawn_notifications.has(guest_id) + + +func set_external_mutation_blocked(blocked: bool) -> void: + _external_mutation_blocked = blocked + + +func has_guest(guest_id: StringName) -> bool: + return _guests.has(guest_id) + + +func get_guest(guest_id: StringName) -> GuestRuntime: + return _guests.get(guest_id) as GuestRuntime + + +func get_guests() -> Array[GuestRuntime]: + var result: Array[GuestRuntime] = [] + for guest_id: StringName in _ordered_guest_ids(): + result.append(_guests[guest_id]) + return result + + +func guest_count() -> int: + return _guests.size() + + +func get_persistent_record(guest_id: StringName) -> Dictionary: + if not _persistent_records.has(guest_id): + return {} + return (_persistent_records[guest_id] as Dictionary).duplicate(true) + + +func get_diagnostics() -> Array[Dictionary]: + return _diagnostics.duplicate(true) + + +func advance_one_minute( + absolute_minute: int, + context_by_guest: Dictionary = {} +) -> Dictionary: + if absolute_minute < 0 or absolute_minute <= current_minute: + return _error_result("Guest minute must advance monotonically.") + var ordered_guest_ids := _ordered_guest_ids() + # The common no-context tick has no external values to validate or isolate. + # Avoid allocating one duplicated context plus a resolved-context entry per + # Guest while retaining the same deterministic order and report contract. + if context_by_guest.is_empty(): + return _advance_with_shared_need_context( + absolute_minute, + ordered_guest_ids, + {} + ) + var resolved_contexts: Dictionary = {} + var default_context: Dictionary = {} + var raw_default: Variant = _value(context_by_guest, &"default", {}) + if raw_default is Dictionary: + default_context = raw_default + # Production supplies one common default context. Validate it once, then + # share the same read-only Dictionary across the population. Any per-Guest + # override keeps using the all-context preflight below, preserving atomic + # rejection and the malformed-override contract. + if context_by_guest.size() == 1 and _has_value(context_by_guest, &"default"): + # Historically an unused default was not validated when the population + # was empty. Preserve that edge case and the first-Guest error wording. + if not ordered_guest_ids.is_empty(): + var default_error := _need_context_error(default_context) + if not default_error.is_empty(): + return _error_result( + "Invalid Need context for %s: %s" % [ + ordered_guest_ids[0], + default_error, + ] + ) + return _advance_with_shared_need_context( + absolute_minute, + ordered_guest_ids, + default_context + ) + for guest_id: StringName in ordered_guest_ids: + var context := default_context + var raw_context: Variant = null + var has_guest_context := false + if context_by_guest.has(guest_id): + has_guest_context = true + raw_context = context_by_guest[guest_id] + else: + var string_guest_id := String(guest_id) + if context_by_guest.has(string_guest_id): + has_guest_context = true + raw_context = context_by_guest[string_guest_id] + if raw_context is Dictionary: + context = raw_context + elif has_guest_context: + # Preserve the original contract: a malformed per-guest override + # suppresses the default context rather than inheriting it. + context = {} + var context_error := _need_context_error(context) + if not context_error.is_empty(): + return _error_result("Invalid Need context for %s: %s" % [guest_id, context_error]) + resolved_contexts[guest_id] = context.duplicate(true) + + ## All contexts are preflighted before mutating any guest, so a malformed + ## batched update cannot partially advance the population or the minute. + var reports: Array[Dictionary] = [] + for guest_id: StringName in ordered_guest_ids: + var context: Dictionary = resolved_contexts[guest_id] + var guest: GuestRuntime = _guests[guest_id] + var definition: GuestDefinition = _definitions.get(guest.guest_type_id) as GuestDefinition + if definition == null: + reports.append(_error_result("Guest definition is missing.")) + else: + reports.append(_update_needs_runtime(guest, definition, context)) + current_minute = absolute_minute + last_error = "" + return { + &"ok": true, + &"minute": absolute_minute, + &"guest_reports": reports, + } + + +func _advance_with_shared_need_context( + absolute_minute: int, + ordered_guest_ids: Array[StringName], + context: Dictionary +) -> Dictionary: + var reports: Array[Dictionary] = [] + for guest_id: StringName in ordered_guest_ids: + var guest: GuestRuntime = _guests[guest_id] + var definition: GuestDefinition = _definitions.get( + guest.guest_type_id + ) as GuestDefinition + if definition == null: + reports.append(_error_result("Guest definition is missing.")) + else: + reports.append(_update_needs_runtime(guest, definition, context)) + current_minute = absolute_minute + last_error = "" + return { + &"ok": true, + &"minute": absolute_minute, + &"guest_reports": reports, + } + + +func update_needs(guest_id: StringName, context: Dictionary = {}) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var definition := get_definition(guest.guest_type_id) + if definition == null: + return _error_result("Guest definition is missing.") + var context_error := _need_context_error(context) + if not context_error.is_empty(): + return _error_result("Invalid Need context: %s" % context_error) + last_error = "" + return _update_needs_runtime(guest, definition, context) + + +func _update_needs_runtime( + guest: GuestRuntime, + definition: GuestDefinition, + context: Dictionary +) -> Dictionary: + var before_hunger := int(guest.needs[&"hunger"]) + var before_sleep := int(guest.needs[&"sleep"]) + var before_hygiene := int(guest.needs[&"hygiene"]) + var before_medical := int(guest.needs[&"medical"]) + var before_leisure := int(guest.needs[&"leisure"]) + var before_intimacy := int(guest.needs[&"intimacy"]) + var delta_hunger := int(definition.need_rates[&"hunger"]) + var delta_sleep := int(definition.need_rates[&"sleep"]) + var delta_hygiene := int(definition.need_rates[&"hygiene"]) + var delta_medical := int(definition.need_rates[&"medical"]) + var delta_leisure := int(definition.need_rates[&"leisure"]) + var delta_intimacy := int(definition.need_rates[&"intimacy"]) + + ## State effects are accumulated first; every Need is clamped exactly once. + if guest.state == GuestRuntimeType.State.MOVING \ + or guest.state == GuestRuntimeType.State.REPLANNING: + delta_hunger += 2 + delta_sleep += 2 + elif guest.state == GuestRuntimeType.State.QUEUING: + delta_hunger += 1 + + if context.is_empty(): + if guest.state != GuestRuntimeType.State.RECEIVING_SERVICE: + delta_leisure += 1 + else: + if bool(_value(context, &"work_like_activity", false)): + delta_sleep += 1 + if bool(_value( + context, + &"unstimulated", + guest.state != GuestRuntimeType.State.RECEIVING_SERVICE + )): + delta_leisure += 1 + delta_hygiene += int(_value(context, &"dirt", 0)) + delta_medical += int(_value(context, &"injury", 0)) + delta_medical += int(_value(context, &"illness", 0)) + if definition.adult_verified: + var intimacy_key := &"intimacy_night" \ + if bool(_value(context, &"is_night", false)) \ + else &"intimacy_day" + delta_intimacy += int(_value(context, intimacy_key, 0)) + var raw_effects: Variant = _value(context, &"need_effects", {}) + var normalized_effects: Dictionary = _normalized_partial_need_map(raw_effects) + delta_hunger += int(normalized_effects.get(&"hunger", 0)) + delta_sleep += int(normalized_effects.get(&"sleep", 0)) + delta_hygiene += int(normalized_effects.get(&"hygiene", 0)) + delta_medical += int(normalized_effects.get(&"medical", 0)) + delta_leisure += int(normalized_effects.get(&"leisure", 0)) + delta_intimacy += int(normalized_effects.get(&"intimacy", 0)) + + var after_hunger := clampi(before_hunger + delta_hunger, 0, 100) + var after_sleep := clampi(before_sleep + delta_sleep, 0, 100) + var after_hygiene := clampi(before_hygiene + delta_hygiene, 0, 100) + var after_medical := clampi(before_medical + delta_medical, 0, 100) + var after_leisure := clampi(before_leisure + delta_leisure, 0, 100) + var after_intimacy := clampi(before_intimacy + delta_intimacy, 0, 100) + guest.needs[&"hunger"] = after_hunger + guest.needs[&"sleep"] = after_sleep + guest.needs[&"hygiene"] = after_hygiene + guest.needs[&"medical"] = after_medical + guest.needs[&"leisure"] = after_leisure + guest.needs[&"intimacy"] = after_intimacy + return { + &"ok": true, + &"guest_id": String(guest.guest_id), + &"before": { + "hunger": before_hunger, + "hygiene": before_hygiene, + "intimacy": before_intimacy, + "leisure": before_leisure, + "medical": before_medical, + "sleep": before_sleep, + }, + &"deltas": { + "hunger": delta_hunger, + "hygiene": delta_hygiene, + "intimacy": delta_intimacy, + "leisure": delta_leisure, + "medical": delta_medical, + "sleep": delta_sleep, + }, + &"after": { + "hunger": after_hunger, + "hygiene": after_hygiene, + "intimacy": after_intimacy, + "leisure": after_leisure, + "medical": after_medical, + "sleep": after_sleep, + }, + } + + +## Returns canonical candidate copies in facility_id order. Supported canonical +## fields are documented by the filtering helpers below; aliases allow existing +## FacilityService projections to be adapted without a hard dependency. +func extract_candidates( + guest_id: StringName, + facilities: Array, + context: Dictionary = {} +) -> Array[Dictionary]: + var guest := get_guest(guest_id) + if guest == null: + _fail("Unknown guest: %s" % guest_id) + return [] + var definition := get_definition(guest.guest_type_id) + if definition == null: + _fail("Guest definition is missing.") + return [] + var ordered: Array[Dictionary] = [] + for raw_candidate: Variant in facilities: + if not raw_candidate is Dictionary: + continue + var candidate: Dictionary = raw_candidate.duplicate(true) + var facility_id := _facility_id(candidate) + if facility_id.is_empty(): + continue + candidate[&"facility_id"] = facility_id + ordered.append(candidate) + ordered.sort_custom( + func(a: Dictionary, b: Dictionary) -> bool: + return String(a[&"facility_id"]) < String(b[&"facility_id"]) + ) + + var accepted: Array[Dictionary] = [] + var rejected: Array[Dictionary] = [] + for candidate: Dictionary in ordered: + var reason := _candidate_rejection_reason(guest, definition, candidate, context) + if reason.is_empty(): + accepted.append(candidate) + else: + rejected.append({ + &"facility_id": String(candidate[&"facility_id"]), + &"reason": String(reason), + }) + _decision_rejections[guest_id] = rejected + last_error = "" + return accepted + + +func get_last_candidate_rejections(guest_id: StringName) -> Array[Dictionary]: + return (_decision_rejections.get(guest_id, []) as Array).duplicate(true) + + +func evaluate_candidate(guest_id: StringName, candidate: Dictionary) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var definition := get_definition(guest.guest_type_id) + if definition == null: + return _error_result("Guest definition is missing.") + if not _candidate_need_maps_are_valid(candidate): + return _error_result("Candidate Need map is invalid.") + # Shared only for exact builtin pure evaluation. Custom providers and + # conversions retain the original helper calls and their observation order. + var shared_need_map: Variant = null + if _can_share_candidate_need_map(guest, definition, candidate): + shared_need_map = _normalized_partial_need_map(_value(candidate, &"need_relief", {})) + var need_relief := _need_relief(guest, candidate) if shared_need_map == null else _need_relief_from_normalized(guest, shared_need_map) + var preference := int(_value( + candidate, + &"preference", + definition.preference_for(_value(candidate, &"facility_tags", _value(candidate, &"tags", []))) + )) + var expected_quality := int(_value( + candidate, + &"expected_quality", + _value(candidate, &"quality", 0) + )) + var novelty := int(_value( + candidate, + &"novelty", + 0 if guest.visited_facility_ids.has(_facility_id(candidate)) else 10 + )) + var group_fit := int(_value(candidate, &"group_fit", 0)) + var price_burden := int(_value(candidate, &"price_burden", _price_burden(guest, candidate))) + var travel_minutes := maxi(int(_value(candidate, &"travel_minutes", 0)), 0) + var wait_minutes := maxi(int(_value( + candidate, + &"wait_minutes", + _value(candidate, &"expected_wait", 0) + )), 0) + var risk := maxi(int(_value(candidate, &"risk", _value(candidate, &"risk_level", 0))), 0) + var default_risk_sensitivity := (100 - definition.risk_tolerance + 24) / 25 + var risk_sensitivity := maxi(int(_value( + candidate, + &"risk_sensitivity", + default_risk_sensitivity + )), 0) + var repetition_penalty := int(_value( + candidate, + &"repetition_penalty", + 10 if guest.visited_facility_ids.has(_facility_id(candidate)) else 0 + )) + var urgency := int(_value(candidate, &"urgency", _candidate_urgency(guest, candidate) if shared_need_map == null else _candidate_urgency_from_normalized(guest, shared_need_map))) + var utility := ( + need_relief * 8 + + preference * 5 + + expected_quality * 3 + + novelty * 2 + + group_fit * 2 + - price_burden * 5 + - travel_minutes * 2 + - wait_minutes * 3 + - risk * risk_sensitivity + - repetition_penalty + ) + var result := candidate.duplicate(true) + result[&"facility_id"] = _facility_id(candidate) + result[&"need_priority"] = _primary_need_priority(guest, candidate) if shared_need_map == null else _primary_need_priority_from_normalized(guest, shared_need_map) + result[&"utility"] = utility + result[&"urgency"] = urgency + result[&"travel_minutes"] = travel_minutes + result[&"utility_terms"] = { + &"need_relief": need_relief, + &"preference": preference, + &"expected_quality": expected_quality, + &"novelty": novelty, + &"group_fit": group_fit, + &"price_burden": price_burden, + &"travel_minutes": travel_minutes, + &"wait_minutes": wait_minutes, + &"risk": risk, + &"risk_sensitivity": risk_sensitivity, + &"repetition_penalty": repetition_penalty, + } + return result + + +func rank_candidates(guest_id: StringName, candidates: Array) -> Array[Dictionary]: + var ranked: Array[Dictionary] = [] + for raw_candidate: Variant in candidates: + if raw_candidate is Dictionary: + var evaluated := evaluate_candidate(guest_id, raw_candidate) + if bool(evaluated.get(&"ok", true)): + ranked.append(evaluated) + ranked.sort_custom(_candidate_precedes) + return ranked + + +func select_best_candidate( + guest_id: StringName, + candidates: Array, + exit_threshold: int = DEFAULT_EXIT_THRESHOLD +) -> Dictionary: + var ranked := rank_candidates(guest_id, candidates) + for candidate: Dictionary in ranked: + if int(candidate[&"utility"]) >= exit_threshold: + return candidate.duplicate(true) + return {} + + +func decide( + guest_id: StringName, + facilities: Array, + absolute_minute: int, + context: Dictionary = {} +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + var filtered := extract_candidates(guest_id, facilities, context) + var ranked := rank_candidates(guest_id, filtered) + var threshold := int(_value(context, &"exit_threshold", DEFAULT_EXIT_THRESHOLD)) + var reserve_callback: Callable = _value(context, &"reserve_callback", Callable()) + for candidate: Dictionary in ranked: + if int(candidate[&"utility"]) < threshold: + continue + if reserve_callback.is_valid(): + var reservation_result: Variant = reserve_callback.call( + guest_id, + candidate[&"facility_id"], + candidate.duplicate(true) + ) + if not _callback_succeeded(reservation_result): + continue + if not _transition_is_available(guest, GuestRuntimeType.State.MOVING, absolute_minute): + return _error_result("Guest cannot enter MOVING in this tick.") + guest.target_facility_id = candidate[&"facility_id"] + guest.path_handle = _copy(_value(candidate, &"path_handle", null)) + _apply_transition(guest, GuestRuntimeType.State.MOVING, absolute_minute, &"FACILITY_SELECTED") + last_error = "" + return { + &"ok": true, + &"action": &"MOVE_TO_FACILITY", + &"candidate": candidate.duplicate(true), + } + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot enter LEAVING in this tick.") + guest.target_facility_id = &"" + guest.path_handle = null + guest.exit_reason = StringName(_value(context, &"exit_reason", &"NO_SUITABLE_FACILITY")) + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, guest.exit_reason) + last_error = "" + return {&"ok": true, &"action": &"LEAVE", &"candidate": {}} + + +func transition_guest( + guest_id: StringName, + new_state: int, + absolute_minute: int, + reason: StringName = &"" +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if new_state < GuestRuntimeType.State.SPAWNING or new_state > GuestRuntimeType.State.INCAPACITATED: + return _error_result("Unknown guest state.") + if guest.state == new_state: + return {&"ok": true, &"changed": false, &"state": new_state} + if not _is_allowed_transition(guest.state, new_state): + return _error_result( + "Invalid guest transition: %s -> %s" % [ + GuestRuntimeType.State.keys()[guest.state], + GuestRuntimeType.State.keys()[new_state], + ] + ) + if not _transition_is_available(guest, new_state, absolute_minute): + _force_loop_guard(guest, absolute_minute) + return { + &"ok": false, + &"changed": true, + &"loop_guard": true, + &"state": guest.state, + } + _apply_transition(guest, new_state, absolute_minute, reason) + last_error = "" + return {&"ok": true, &"changed": true, &"state": new_state} + + +func enter_queue( + guest_id: StringName, + facility_id: StringName, + request_id: StringName, + absolute_minute: int, + initial_patience: int = -1, + enqueue_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or facility_id.is_empty() or request_id.is_empty(): + return _error_result("Guest, facility and request are required for queue entry.") + if not _transition_is_available(guest, GuestRuntimeType.State.QUEUING, absolute_minute): + return _error_result("Guest cannot enter QUEUING in this tick.") + if enqueue_callback.is_valid(): + var external_result: Variant = enqueue_callback.call(guest_id, facility_id, request_id) + if not _callback_succeeded(external_result): + return _error_result("Facility queue rejected the guest.") + var definition := get_definition(guest.guest_type_id) + var resolved_patience := initial_patience + if resolved_patience < 0: + resolved_patience = definition.base_patience if definition != null else 0 + guest.target_facility_id = facility_id + guest.queue_request_id = request_id + guest.patience_remaining = maxi(resolved_patience, 0) + _apply_transition(guest, GuestRuntimeType.State.QUEUING, absolute_minute, &"QUEUE_ENTERED") + last_error = "" + return {&"ok": true, &"patience_remaining": guest.patience_remaining} + + +## Closure asks the guest to choose again, without charging a patience penalty. +## The callback releases the owning facility request and inventory reservations. +func release_queue_for_replanning( + guest_id: StringName, + absolute_minute: int, + release_callback: Callable = Callable() +) -> Dictionary: + if _external_mutation_blocked: + return _error_result("Guest mutation is blocked while committed notifications are delivered.") + var guest := get_guest(guest_id) + if guest == null or guest.state != GuestRuntimeType.State.QUEUING: + return _error_result("Guest is not in a queue.") + if not _transition_is_available(guest, GuestRuntimeType.State.DECIDING, absolute_minute): + return _error_result("Guest cannot atomically replan the queue in this tick.") + var facility_id := guest.target_facility_id + var request_id := guest.queue_request_id + if facility_id.is_empty() or request_id.is_empty(): + return _error_result("Queued guest is missing a facility or request.") + if release_callback.is_valid(): + var external_result: Variant = release_callback.call(guest_id, facility_id, request_id) + if not _callback_succeeded(external_result): + return _error_result("Facility queue removal failed; guest state was not changed.") + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.path_handle = null + _apply_transition(guest, GuestRuntimeType.State.DECIDING, absolute_minute, &"FACILITY_CLOSED") + last_error = "" + return { + &"ok": true, + &"facility_id": facility_id, + &"request_id": request_id, + &"patience_remaining": guest.patience_remaining, + } + + +func tick_queue_patience( + guest_id: StringName, + absolute_minute: int, + recovery: int = 0, + leave_queue_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or guest.state != GuestRuntimeType.State.QUEUING: + return _error_result("Guest is not in a queue.") + var next_patience := maxi(guest.patience_remaining - 1 + recovery, 0) + if next_patience > 0: + guest.patience_remaining = next_patience + last_error = "" + return {&"ok": true, &"abandoned": false, &"patience_remaining": next_patience} + ## Preflight every internal mutation before invoking the external queue owner. + if not _transition_is_available(guest, GuestRuntimeType.State.ABANDONING, absolute_minute): + return _error_result("Guest cannot atomically abandon the queue in this tick.") + var facility_id := guest.target_facility_id + var request_id := guest.queue_request_id + if leave_queue_callback.is_valid(): + var external_result: Variant = leave_queue_callback.call( + guest_id, + facility_id, + request_id + ) + if not _callback_succeeded(external_result): + return _error_result("Facility queue removal failed; guest state was not changed.") + guest.patience_remaining = 0 + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.path_handle = null + _apply_transition(guest, GuestRuntimeType.State.ABANDONING, absolute_minute, &"PATIENCE_EXHAUSTED") + guest_queue_abandoned.emit(guest_id, facility_id, request_id) + last_error = "" + return {&"ok": true, &"abandoned": true, &"patience_remaining": 0} + + +func begin_service( + guest_id: StringName, + facility_id: StringName, + service_id: StringName, + absolute_minute: int +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or facility_id.is_empty() or service_id.is_empty(): + return _error_result("Guest, facility and service are required.") + if not _transition_is_available(guest, GuestRuntimeType.State.RECEIVING_SERVICE, absolute_minute): + return _error_result("Guest cannot begin service in this tick.") + guest.target_facility_id = facility_id + guest.current_service_id = service_id + guest.queue_request_id = &"" + _apply_transition(guest, GuestRuntimeType.State.RECEIVING_SERVICE, absolute_minute, &"SERVICE_STARTED") + last_error = "" + return {&"ok": true, &"service_id": service_id} + + +func interrupt_service( + guest_id: StringName, + service_id: StringName, + absolute_minute: int, + interrupt_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or service_id.is_empty(): + return _error_result("Guest and service are required.") + if guest.interrupted_service_ids.has(service_id): + return {&"ok": true, &"replayed": true, &"service_id": service_id} + if guest.current_service_id != service_id: + return _error_result("Guest is not receiving the specified service.") + if not _transition_is_available(guest, GuestRuntimeType.State.INTERRUPTED, absolute_minute): + return _error_result("Guest cannot enter INTERRUPTED in this tick.") + if interrupt_callback.is_valid(): + var external_result: Variant = interrupt_callback.call(guest_id, service_id) + if not _callback_succeeded(external_result): + return _error_result("Service interruption callback failed.") + guest.interrupted_service_ids.append(service_id) + guest.interrupted_service_ids.sort_custom( + func(a: StringName, b: StringName) -> bool: return String(a) < String(b) + ) + guest.current_service_id = &"" + _apply_transition(guest, GuestRuntimeType.State.INTERRUPTED, absolute_minute, &"FACILITY_STOPPED") + last_error = "" + return {&"ok": true, &"replayed": false, &"service_id": service_id} + + +func complete_service( + guest_id: StringName, + service_id: StringName, + absolute_minute: int, + satisfaction_sample: int, + need_effects: Dictionary = {} +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null or guest.current_service_id != service_id: + return _error_result("Guest is not receiving the specified service.") + var normalized_effects: Variant = _normalized_partial_need_map(need_effects) + if not normalized_effects is Dictionary: + return _error_result("Service Need effects must use canonical integer Need IDs.") + if not _transition_is_available(guest, GuestRuntimeType.State.DECIDING, absolute_minute): + return _error_result("Guest cannot complete service in this tick.") + var sample := clampi(satisfaction_sample, 0, 100) + guest.satisfaction_total += sample + guest.satisfaction_samples += 1 + for need_id: StringName in NEED_IDS: + var effect := int(normalized_effects.get(need_id, 0)) + guest.needs[need_id] = clampi(int(guest.needs[need_id]) + effect, 0, 100) + if not guest.target_facility_id.is_empty() \ + and not guest.visited_facility_ids.has(guest.target_facility_id): + guest.visited_facility_ids.append(guest.target_facility_id) + guest.visited_facility_ids.sort_custom( + func(a: StringName, b: StringName) -> bool: return String(a) < String(b) + ) + guest.current_service_id = &"" + guest.queue_request_id = &"" + guest.target_facility_id = &"" + _apply_transition(guest, GuestRuntimeType.State.DECIDING, absolute_minute, &"SERVICE_COMPLETED") + last_error = "" + return {&"ok": true, &"satisfaction_sample": sample} + + +func record_path_failure( + guest_id: StringName, + absolute_minute: int, + recovery_targets: Array = [], + reachability_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + guest.path_failure_count += 1 + if guest.path_failure_count < PATH_FAILURE_LIMIT: + if guest.state != GuestRuntimeType.State.REPLANNING: + var transition := transition_guest( + guest_id, + GuestRuntimeType.State.REPLANNING, + absolute_minute, + &"PATH_INVALID" + ) + if not bool(transition.get(&"ok", false)): + return transition + last_error = "" + return { + &"ok": true, + &"recovery_started": false, + &"failure_count": guest.path_failure_count, + } + + var selected := _select_recovery_target(guest, recovery_targets, reachability_callback) + if selected.is_empty(): + ## A virtual emergency egress is preferable to leaving an entity stranded. + selected = { + &"kind": &"emergency_despawn", + &"cell": guest.current_cell, + &"path_handle": {&"virtual_evacuation": true}, + } + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot enter safe recovery in this tick.") + guest.recovery_kind = StringName(_value(selected, &"kind", &"emergency_exit")) + guest.recovery_target_cell = _vector3i(_value(selected, &"cell", guest.current_cell)) + guest.path_handle = _copy(_value(selected, &"path_handle", { + &"recovery": true, + &"target_cell": guest.recovery_target_cell, + })) + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.exit_reason = &"PATH_RECOVERY" + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, &"PATH_FAILURE_LIMIT") + _diagnostics.append({ + &"code": &"GUEST_PATH_RECOVERY", + &"guest_id": String(guest_id), + &"minute": absolute_minute, + &"failure_count": guest.path_failure_count, + &"recovery_kind": String(guest.recovery_kind), + &"target_cell": _cell_to_array(guest.recovery_target_cell), + }) + guest_recovery_started.emit(guest_id, guest.recovery_kind, guest.recovery_target_cell) + last_error = "" + return { + &"ok": true, + &"recovery_started": true, + &"failure_count": guest.path_failure_count, + &"recovery_kind": guest.recovery_kind, + &"target_cell": guest.recovery_target_cell, + } + + +func record_path_success(guest_id: StringName, cell: Vector3i, absolute_minute: int) -> bool: + var guest := get_guest(guest_id) + if guest == null or absolute_minute < 0: + return _fail("A valid guest and minute are required.") + guest.current_cell = cell + guest.last_progress_cell = cell + guest.last_progress_at = absolute_minute + guest.path_failure_count = 0 + guest.recovery_kind = &"" + last_error = "" + return true + + +func note_movement( + guest_id: StringName, + cell: Vector3i, + absolute_minute: int, + recovery_targets: Array = [], + reachability_callback: Callable = Callable() +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if cell != guest.last_progress_cell: + record_path_success(guest_id, cell, absolute_minute) + return {&"ok": true, &"progressed": true, &"replanned": false} + guest.current_cell = cell + if absolute_minute - guest.last_progress_at < STUCK_REPLAN_MINUTES: + return {&"ok": true, &"progressed": false, &"replanned": false} + guest.last_progress_at = absolute_minute + var result := record_path_failure( + guest_id, + absolute_minute, + recovery_targets, + reachability_callback + ) + result[&"progressed"] = false + result[&"replanned"] = true + return result + + +func begin_leaving( + guest_id: StringName, + absolute_minute: int, + reason: StringName +) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if not _transition_is_available(guest, GuestRuntimeType.State.LEAVING, absolute_minute): + return _error_result("Guest cannot begin leaving in this tick.") + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.exit_reason = reason + _apply_transition(guest, GuestRuntimeType.State.LEAVING, absolute_minute, reason) + last_error = "" + return {&"ok": true} + + +func despawn_guest(guest_id: StringName, absolute_minute: int) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return _error_result("Unknown guest: %s" % guest_id) + if guest.state != GuestRuntimeType.State.LEAVING \ + and guest.state != GuestRuntimeType.State.DESPAWNING: + return _error_result("Only a leaving guest can despawn.") + if guest.state != GuestRuntimeType.State.DESPAWNING: + if not _transition_is_available(guest, GuestRuntimeType.State.DESPAWNING, absolute_minute): + return _error_result("Guest cannot enter DESPAWNING in this tick.") + _apply_transition(guest, GuestRuntimeType.State.DESPAWNING, absolute_minute, &"EXIT_REACHED") + var record := { + &"guest_id": String(guest.guest_id), + &"guest_type_id": String(guest.guest_type_id), + &"debt_gold": guest.debt_gold, + &"unresolved_incident_ids": _names_to_strings(guest.unresolved_incident_ids), + &"outstanding_reservation_ids": _names_to_strings(guest.outstanding_reservation_ids), + } + _persistent_records[guest_id] = record + _guests.erase(guest_id) + _pending_spawn_notifications.erase(guest_id) + _guest_order_dirty = true + _decision_rejections.erase(guest_id) + guest_despawned.emit(guest_id, record.duplicate(true)) + last_error = "" + return {&"ok": true, &"persistent_record": record.duplicate(true)} + + +func create_snapshot() -> Dictionary: + var saved_definitions: Array[Dictionary] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + saved_definitions.append((_definitions[guest_type_id] as GuestDefinition).create_snapshot()) + var saved_guests: Array[Dictionary] = [] + for guest_id: StringName in _ordered_guest_ids(): + saved_guests.append((_guests[guest_id] as GuestRuntime).create_snapshot()) + var saved_records: Array[Dictionary] = [] + for guest_id: StringName in _sorted_ids(_persistent_records.keys()): + saved_records.append((_persistent_records[guest_id] as Dictionary).duplicate(true)) + return { + &"version": SNAPSHOT_VERSION, + &"current_minute": current_minute, + &"definitions": saved_definitions, + &"guests": saved_guests, + &"persistent_records": saved_records, + &"diagnostics": _diagnostics.duplicate(true), + } + + +func restore_snapshot(snapshot: Dictionary) -> bool: + var raw_version: Variant = snapshot.get(&"version", snapshot.get("version", null)) + if not raw_version is int or int(raw_version) not in [1, SNAPSHOT_VERSION]: + return _fail("Unsupported GuestService snapshot version.") + var prepared: Variant = snapshot.duplicate(true) + if int(raw_version) == 1: + prepared = _migrate_v1_snapshot(snapshot) + if not prepared is Dictionary: + return _fail("GuestService v1 Need schema is invalid or mixed.") + var candidate: Dictionary = prepared + if not candidate.get(&"definitions", null) is Array \ + or not candidate.get(&"guests", null) is Array \ + or not candidate.get(&"persistent_records", null) is Array \ + or not candidate.get(&"diagnostics", null) is Array: + return _fail("GuestService snapshot arrays are missing.") + var next_minute := int(candidate.get(&"current_minute", -2)) + if next_minute < -1: + return _fail("GuestService snapshot minute is invalid.") + + ## Parse into temporary containers. Live state is replaced only after every + ## cross-reference and invariant has been validated. + var next_definitions: Dictionary = {} + for raw_definition: Variant in candidate[&"definitions"]: + if not raw_definition is Dictionary: + return _fail("Guest definition snapshot has an invalid type.") + var definition := GuestDefinitionType.from_snapshot(raw_definition) + if definition == null or next_definitions.has(definition.guest_type_id): + return _fail("Guest definition snapshot is invalid or duplicated.") + next_definitions[definition.guest_type_id] = definition + + var next_guests: Dictionary = {} + for raw_guest: Variant in candidate[&"guests"]: + if not raw_guest is Dictionary: + return _fail("Guest runtime snapshot has an invalid type.") + var guest := GuestRuntimeType.from_snapshot(raw_guest) + if guest == null or next_guests.has(guest.guest_id): + return _fail("Guest runtime snapshot is invalid or duplicated.") + if not next_definitions.has(guest.guest_type_id): + return _fail("Guest runtime snapshot references an unknown definition.") + next_guests[guest.guest_id] = guest + + var next_records: Dictionary = {} + for raw_record: Variant in candidate[&"persistent_records"]: + if not raw_record is Dictionary or not _persistent_record_is_valid(raw_record): + return _fail("Persistent guest record is invalid.") + var record_id := StringName(raw_record.get(&"guest_id", "")) + if next_records.has(record_id) or next_guests.has(record_id): + return _fail("Persistent guest id is duplicated or still active.") + next_records[record_id] = raw_record.duplicate(true) + + var next_diagnostics: Array[Dictionary] = [] + for raw_diagnostic: Variant in candidate[&"diagnostics"]: + if not raw_diagnostic is Dictionary: + return _fail("Guest diagnostic snapshot has an invalid type.") + next_diagnostics.append(raw_diagnostic.duplicate(true)) + + _definitions = next_definitions + _guests = next_guests + _guest_order_dirty = true + _persistent_records = next_records + _decision_rejections = {} + _diagnostics = next_diagnostics + _pending_spawn_notifications.clear() + _external_mutation_blocked = false + current_minute = next_minute + last_error = "" + return true + + +func validate_invariants() -> Dictionary: + var errors: Array[String] = [] + for guest_type_id: StringName in _sorted_ids(_definitions.keys()): + var definition: GuestDefinition = _definitions[guest_type_id] + if not definition.is_valid() or definition.guest_type_id != guest_type_id: + errors.append("Invalid definition: %s" % guest_type_id) + for guest_id: StringName in _sorted_ids(_guests.keys()): + var guest: GuestRuntime = _guests[guest_id] + if not guest.is_valid() or guest.guest_id != guest_id: + errors.append("Invalid runtime: %s" % guest_id) + elif not _definitions.has(guest.guest_type_id): + errors.append("Unknown guest type for: %s" % guest_id) + if _persistent_records.has(guest_id): + errors.append("Guest is both active and persisted: %s" % guest_id) + for guest_id: StringName in _sorted_ids(_persistent_records.keys()): + if not _persistent_record_is_valid(_persistent_records[guest_id]): + errors.append("Invalid persistent record: %s" % guest_id) + return {&"valid": errors.is_empty(), &"errors": errors} + + +func _migrate_v1_snapshot(snapshot: Dictionary) -> Variant: + var definitions: Variant = snapshot.get(&"definitions", snapshot.get("definitions", null)) + var guests: Variant = snapshot.get(&"guests", snapshot.get("guests", null)) + if not definitions is Array or not guests is Array: + return null + var migrated := snapshot.duplicate(true) + for index: int in range(definitions.size()): + var raw_definition: Variant = definitions[index] + if not raw_definition is Dictionary: + return null + var translated_rates: Variant = _translate_v1_need_map( + _value(raw_definition, &"need_rates", null) + ) + if not translated_rates is Dictionary: + return null + migrated[&"definitions"][index][&"need_rates"] = translated_rates + for index: int in range(guests.size()): + var raw_guest: Variant = guests[index] + if not raw_guest is Dictionary: + return null + var translated_needs: Variant = _translate_v1_need_map( + _value(raw_guest, &"needs", null) + ) + if not translated_needs is Dictionary: + return null + migrated[&"guests"][index][&"needs"] = translated_needs + migrated[&"version"] = SNAPSHOT_VERSION + return migrated + + +static func _translate_v1_need_map(source: Variant) -> Variant: + if not source is Dictionary or source.size() != LEGACY_NEED_IDS.size(): + return null + var legacy: Dictionary = {} + for raw_key: Variant in source: + if not raw_key is String and not raw_key is StringName: + return null + var legacy_id := StringName(raw_key) + if not LEGACY_NEED_IDS.has(legacy_id) or legacy.has(legacy_id): + return null + var raw_value: Variant = source[raw_key] + if not raw_value is int: + return null + legacy[legacy_id] = raw_value + if legacy.size() != LEGACY_NEED_IDS.size(): + return null + return { + &"hunger": legacy[&"hunger"], + &"sleep": legacy[&"rest"], + &"hygiene": 0, + &"medical": legacy[&"safety"], + &"leisure": legacy[&"fun"], + &"intimacy": legacy[&"social"], + } + + +func _need_context_error(context: Dictionary) -> String: + for field_id: StringName in [ + &"dirt", + &"injury", + &"illness", + &"intimacy_day", + &"intimacy_night", + ]: + if not _has_value(context, field_id): + continue + var raw_value: Variant = _value(context, field_id, null) + if not raw_value is int or int(raw_value) < 0 or int(raw_value) > 100: + return "%s must be an integer from 0 through 100." % field_id + for field_id: StringName in [&"is_night", &"work_like_activity", &"unstimulated"]: + if _has_value(context, field_id) and not _value(context, field_id, null) is bool: + return "%s must be boolean." % field_id + if _has_value(context, &"need_effects"): + var effects: Variant = _normalized_partial_need_map( + _value(context, &"need_effects", null) + ) + if not effects is Dictionary: + return "need_effects must contain only canonical integer Need IDs." + return "" + + +static func _normalized_partial_need_map(source: Variant) -> Variant: + if not source is Dictionary or source.size() > NEED_IDS.size(): + return null + var result: Dictionary = {} + for raw_key: Variant in source: + if not raw_key is String and not raw_key is StringName: + return null + var need_id := StringName(raw_key) + if not NEED_IDS.has(need_id) or result.has(need_id): + return null + var raw_value: Variant = source[raw_key] + if not raw_value is int: + return null + result[need_id] = raw_value + return result + + +static func _candidate_need_maps_are_valid(candidate: Dictionary) -> bool: + for field_id: StringName in [&"need_relief", &"need_effects"]: + if not _has_value(candidate, field_id): + continue + var raw_value: Variant = _value(candidate, field_id, null) + if field_id == &"need_relief" and raw_value is int: + continue + if not _normalized_partial_need_map(raw_value) is Dictionary: + return false + return true + + +func _candidate_rejection_reason( + guest: GuestRuntime, + definition: GuestDefinition, + candidate: Dictionary, + context: Dictionary +) -> StringName: + if not _candidate_need_maps_are_valid(candidate): + return &"INVALID_NEED_MAP" + if not bool(_value(candidate, &"inputs_available", true)): + return &"MISSING_INPUTS" + var adult_only := bool(_value( + candidate, + &"adult_only", + _value(candidate, &"requires_adult_verified", false) + )) + if adult_only: + if not definition.adult_verified: + return &"ADULT_NOT_VERIFIED" + if not bool(_value(context, &"adult_content_enabled", true)): + return &"CONTENT_FILTERED" + var required_tags := _name_array(_value(candidate, &"required_guest_tags", [])) + for tag: StringName in required_tags: + if not definition.tags.has(tag): + return &"MISSING_PERMISSION" + var forbidden_tags := _name_array(_value(candidate, &"forbidden_guest_tags", [])) + for tag: StringName in forbidden_tags: + if definition.tags.has(tag): + return &"FORBIDDEN_GUEST_TAG" + var allowed_types := _name_array(_value(candidate, &"allowed_guest_types", [])) + if not allowed_types.is_empty() and not allowed_types.has(guest.guest_type_id): + return &"GUEST_TYPE_NOT_ALLOWED" + if not bool(_value(candidate, &"is_open", _value(candidate, &"open", true))) \ + and not bool(_value(candidate, &"open_at_arrival", false)): + return &"FACILITY_CLOSED" + if not _candidate_is_reachable(guest, candidate, context): + return &"UNREACHABLE" + var travel_minutes := maxi(int(_value(candidate, &"travel_minutes", 0)), 0) + var decision_minute := int(_value(context, &"absolute_minute", current_minute)) + if decision_minute >= 0 and decision_minute + travel_minutes > guest.planned_exit_at: + return &"ETA_AFTER_EXIT" + if not _candidate_is_affordable(guest, definition, candidate): + return &"CANNOT_PAY" + var wait_minutes := maxi(int(_value( + candidate, + &"wait_minutes", + _value(candidate, &"expected_wait", 0) + )), 0) + var hard_limit := maxi(int(_value( + candidate, + &"wait_hard_limit", + _value(context, &"wait_hard_limit", DEFAULT_WAIT_HARD_LIMIT) + )), 0) + if wait_minutes >= hard_limit: + return &"WAIT_HARD_LIMIT" + var risk_level := clampi(int(_value(candidate, &"risk_level", _value(candidate, &"risk", 0))), 0, 100) + if risk_level > definition.risk_tolerance: + return &"RISK_REFUSED" + var illegal_level := clampi(int(_value(candidate, &"illegal_level", 0)), 0, 100) + if illegal_level > 100 - definition.legal_sensitivity \ + and not bool(_value(candidate, &"legal_permission_override", false)): + return &"ILLEGALITY_REFUSED" + var quality := clampi(int(_value(candidate, &"expected_quality", _value(candidate, &"quality", 0))), 0, 100) + var minimum_quality := clampi(int(_value(candidate, &"minimum_quality", definition.minimum_quality)), 0, 100) + if quality < minimum_quality: + return &"QUALITY_TOO_LOW" + return &"" + + +func _candidate_is_reachable( + guest: GuestRuntime, + candidate: Dictionary, + context: Dictionary +) -> bool: + var callback: Callable = _value(context, &"reachability_callback", Callable()) + if callback.is_valid(): + return _callback_succeeded(callback.call(guest, candidate.duplicate(true), context)) + return bool(_value(candidate, &"reachable", true)) + + +func _candidate_is_affordable( + guest: GuestRuntime, + definition: GuestDefinition, + candidate: Dictionary +) -> bool: + var price := _candidate_price(candidate) + var cash_available := guest.wallet_gold + var respect_reserve := bool(_value( + candidate, + &"respect_survival_reserve", + _candidate_is_discretionary(candidate) + )) + if respect_reserve: + cash_available = maxi(cash_available - _survival_reserve(guest, definition), 0) + if price <= cash_available: + return true + if not bool(_value(candidate, &"credit_allowed", _value(candidate, &"allow_credit", false))): + return false + var remaining_credit := maxi(guest.credit_limit_gold - guest.debt_gold, 0) + return price - cash_available <= remaining_credit + + +func _candidate_is_discretionary(candidate: Dictionary) -> bool: + if bool(_value(candidate, &"discretionary", false)): + return true + var category := StringName(_value(candidate, &"service_category", &"")) + if category in [&"leisure", &"entertainment", &"gambling", &"intimacy", &"tourism"]: + return true + var facility_tags := _name_array(_value( + candidate, + &"facility_tags", + _value(candidate, &"tags", []) + )) + for tag: StringName in [&"leisure", &"entertainment", &"gambling", &"adult", &"intimacy"]: + if facility_tags.has(tag): + return true + return false + + +func _survival_reserve(guest: GuestRuntime, definition: GuestDefinition) -> int: + if guest.max_addiction() >= 75: + return 0 + if definition.tags.has(&"spendthrift"): + return guest.wallet_gold / 10 + if definition.tags.has(&"frugal"): + return guest.wallet_gold / 2 + return guest.wallet_gold * 3 / 10 + + +func _candidate_price(candidate: Dictionary) -> int: + return maxi(int(_value( + candidate, + &"estimated_total_gold", + _value(candidate, &"price_gold", _value(candidate, &"quoted_price", 0)) + )), 0) + + +func _price_burden(guest: GuestRuntime, candidate: Dictionary) -> int: + var price := _candidate_price(candidate) + var purchasing_power := maxi(guest.wallet_gold + maxi(guest.credit_limit_gold - guest.debt_gold, 0), 1) + return clampi((price * 100 + purchasing_power - 1) / purchasing_power, 0, 100) + + +func _need_relief(guest: GuestRuntime, candidate: Dictionary) -> int: + var raw_relief: Variant = _value(candidate, &"need_relief", 0) + if raw_relief is int: + return maxi(int(raw_relief), 0) + if not raw_relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(raw_relief) + var best := 0 + for need_id: StringName in NEED_IDS: + var relief := maxi(int(normalized.get(need_id, 0)), 0) + best = maxi(best, relief * int(guest.needs[need_id]) / 100) + return best + + +func _candidate_urgency(guest: GuestRuntime, candidate: Dictionary) -> int: + var raw_relief: Variant = _value(candidate, &"need_relief", {}) + if not raw_relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(raw_relief) + var result := 0 + for need_id: StringName in NEED_IDS: + if int(normalized.get(need_id, 0)) > 0: + result = maxi(result, int(guest.needs[need_id])) + return result + + +func _primary_need_priority(guest: GuestRuntime, candidate: Dictionary) -> int: + var relief: Variant = _value(candidate, &"need_relief", {}) + if not relief is Dictionary: + return 0 + var normalized: Dictionary = _normalized_partial_need_map(relief) + var priority := 0 + for need: StringName in PRIMARY_NEEDS: + if int(normalized.get(need, 0)) > 0: + priority = maxi(priority, int(guest.needs[need])) + return priority + + +func get_observation(guest_id: StringName) -> Dictionary: + var guest := get_guest(guest_id) + if guest == null: + return {} + var highest := 0 + var primary: Array[StringName] = [] + for need: StringName in PRIMARY_NEEDS: + var value := int(guest.needs[need]) + if value > highest: + highest = value + primary.clear() + if value == highest and value > 0: + primary.append(need) + var observed_needs: Dictionary = {} + for need: StringName in NEED_IDS: + observed_needs[need] = guest.needs[need] + return {&"guest_id": guest.guest_id, &"state": guest.state, &"needs": observed_needs, + &"primary_needs": primary, &"target_facility_id": guest.target_facility_id, + &"wallet_gold": guest.wallet_gold, &"patience": guest.patience_remaining} + + +func _candidate_precedes(a: Dictionary, b: Dictionary) -> bool: + var a_priority := int(a.get(&"need_priority", 0)) + var b_priority := int(b.get(&"need_priority", 0)) + if a_priority != b_priority: + return a_priority > b_priority + var a_utility := int(a[&"utility"]) + var b_utility := int(b[&"utility"]) + if a_utility != b_utility: + return a_utility > b_utility + var a_urgency := int(a[&"urgency"]) + var b_urgency := int(b[&"urgency"]) + if a_urgency != b_urgency: + return a_urgency > b_urgency + var a_travel := int(a[&"travel_minutes"]) + var b_travel := int(b[&"travel_minutes"]) + if a_travel != b_travel: + return a_travel < b_travel + return String(a[&"facility_id"]) < String(b[&"facility_id"]) + + +func _select_recovery_target( + guest: GuestRuntime, + targets: Array, + reachability_callback: Callable +) -> Dictionary: + var ordered: Array[Dictionary] = [] + for raw_target: Variant in targets: + if raw_target is Dictionary: + var target: Dictionary = raw_target.duplicate(true) + target[&"kind"] = StringName(_value(target, &"kind", &"emergency_exit")) + target[&"cell"] = _vector3i(_value(target, &"cell", guest.current_cell)) + ordered.append(target) + ordered.sort_custom( + func(a: Dictionary, b: Dictionary) -> bool: + var a_priority := _recovery_priority(StringName(a[&"kind"])) + var b_priority := _recovery_priority(StringName(b[&"kind"])) + if a_priority != b_priority: + return a_priority < b_priority + return _cell_precedes(a[&"cell"], b[&"cell"]) + ) + for target: Dictionary in ordered: + var reachable := bool(_value(target, &"reachable", true)) + if reachability_callback.is_valid(): + reachable = _callback_succeeded(reachability_callback.call( + guest.current_cell, + target[&"cell"], + guest.guest_id, + target.duplicate(true) + )) + if reachable: + return target + return {} + + +func _recovery_priority(kind: StringName) -> int: + match kind: + &"guidance", &"guidance_point": + return 0 + &"entrance", &"main_entrance": + return 1 + &"emergency_exit", &"emergency_egress": + return 2 + _: + return 3 + + +func _transition_is_available(guest: GuestRuntime, new_state: int, minute: int) -> bool: + if minute < 0 or not _is_allowed_transition(guest.state, new_state): + return false + var count := guest.transitions_this_tick if guest.transition_tick == minute else 0 + return count < MAX_TRANSITIONS_PER_TICK + + +func _apply_transition( + guest: GuestRuntime, + new_state: int, + absolute_minute: int, + reason: StringName +) -> void: + var old_state := guest.state + if guest.transition_tick != absolute_minute: + guest.transition_tick = absolute_minute + guest.transitions_this_tick = 0 + guest.transitions_this_tick += 1 + guest.state = new_state as GuestRuntime.State + guest.state_entered_at = absolute_minute + guest_state_changed.emit(guest.guest_id, old_state, new_state, reason) + + +func _force_loop_guard(guest: GuestRuntime, absolute_minute: int) -> void: + var old_state := guest.state + guest.state = GuestRuntimeType.State.DECIDING + guest.state_entered_at = absolute_minute + guest.transition_tick = absolute_minute + guest.transitions_this_tick = MAX_TRANSITIONS_PER_TICK + guest.target_facility_id = &"" + guest.queue_request_id = &"" + guest.current_service_id = &"" + guest.path_handle = null + _diagnostics.append({ + &"code": &"AI_LOOP_GUARD", + &"guest_id": String(guest.guest_id), + &"minute": absolute_minute, + }) + guest_state_changed.emit( + guest.guest_id, + old_state, + GuestRuntimeType.State.DECIDING, + &"AI_LOOP_GUARD" + ) + + +func _is_allowed_transition(from_state: int, to_state: int) -> bool: + if from_state == to_state: + return true + if to_state in [ + GuestRuntimeType.State.LEAVING, + GuestRuntimeType.State.FLEEING, + GuestRuntimeType.State.INCAPACITATED, + ]: + return from_state != GuestRuntimeType.State.DESPAWNING + match from_state: + GuestRuntimeType.State.SPAWNING: + return to_state in [GuestRuntimeType.State.ENTERING, GuestRuntimeType.State.DECIDING] + GuestRuntimeType.State.ENTERING: + return to_state in [GuestRuntimeType.State.DECIDING, GuestRuntimeType.State.MOVING] + GuestRuntimeType.State.DECIDING: + return to_state in [GuestRuntimeType.State.MOVING, GuestRuntimeType.State.QUEUING] + GuestRuntimeType.State.MOVING: + return to_state in [ + GuestRuntimeType.State.QUEUING, + GuestRuntimeType.State.RECEIVING_SERVICE, + GuestRuntimeType.State.DECIDING, + GuestRuntimeType.State.REPLANNING, + ] + GuestRuntimeType.State.QUEUING: + return to_state in [ + GuestRuntimeType.State.RECEIVING_SERVICE, + GuestRuntimeType.State.ABANDONING, + GuestRuntimeType.State.DECIDING, + ] + GuestRuntimeType.State.RECEIVING_SERVICE: + return to_state in [GuestRuntimeType.State.DECIDING, GuestRuntimeType.State.INTERRUPTED] + GuestRuntimeType.State.REPLANNING: + return to_state in [GuestRuntimeType.State.MOVING, GuestRuntimeType.State.DECIDING] + GuestRuntimeType.State.ABANDONING, GuestRuntimeType.State.INTERRUPTED: + return to_state == GuestRuntimeType.State.DECIDING + GuestRuntimeType.State.FLEEING: + return to_state in [GuestRuntimeType.State.LEAVING, GuestRuntimeType.State.DESPAWNING] + GuestRuntimeType.State.LEAVING: + return to_state == GuestRuntimeType.State.DESPAWNING + _: + return false + + +static func _callback_succeeded(result: Variant) -> bool: + if result == null: + return true + if result is bool: + return result + if result is Dictionary: + return bool(result.get(&"ok", result.get("ok", result.get(&"success", result.get("success", false))))) + if result is Object and result.has_method("is_success"): + return bool(result.call("is_success")) + return false + + +static func _persistent_record_is_valid(record: Dictionary) -> bool: + if StringName(record.get(&"guest_id", "")).is_empty(): + return false + if StringName(record.get(&"guest_type_id", "")).is_empty(): + return false + if int(record.get(&"debt_gold", -1)) < 0: + return false + return record.get(&"unresolved_incident_ids", null) is Array \ + and record.get(&"outstanding_reservation_ids", null) is Array + + +static func _facility_id(candidate: Dictionary) -> StringName: + return StringName(_value( + candidate, + &"facility_id", + _value(candidate, &"instance_id", &"") + )) + + +static func _name_array(source: Variant) -> Array[StringName]: + var result: Array[StringName] = [] + if not source is Array: + return result + for raw_value: Variant in source: + var value := StringName(raw_value) + if not value.is_empty() and not result.has(value): + result.append(value) + result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) + return result + + +static func _sorted_ids(source: Array) -> Array[StringName]: + var result: Array[StringName] = [] + for raw_value: Variant in source: + result.append(StringName(raw_value)) + result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) + return result + + +func _ordered_guest_ids() -> Array[StringName]: + if _guest_order_dirty: + _sorted_guest_ids_cache = _sorted_ids(_guests.keys()) + _guest_order_dirty = false + return _sorted_guest_ids_cache + + +static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: + if source.has(key): + return source[key] + var string_key := String(key) + return source[string_key] if source.has(string_key) else fallback + + +static func _has_value(source: Dictionary, key: StringName) -> bool: + return source.has(key) or source.has(String(key)) + + +static func _need_report(values: Dictionary) -> Dictionary: + # Matches _string_key_dictionary's canonical lexical key order without + # allocating and sorting a six-key array for every guest and minute. + return { + "hunger": int(values[&"hunger"]), + "hygiene": int(values[&"hygiene"]), + "intimacy": int(values[&"intimacy"]), + "leisure": int(values[&"leisure"]), + "medical": int(values[&"medical"]), + "sleep": int(values[&"sleep"]), + } + + +static func _copy(value: Variant) -> Variant: + if value is Array or value is Dictionary: + return value.duplicate(true) + return value + + +static func _vector3i(value: Variant) -> Vector3i: + if value is Vector3i: + return value + if value is Array and value.size() == 3: + return Vector3i(int(value[0]), int(value[1]), int(value[2])) + return Vector3i.ZERO + + +static func _cell_to_array(cell: Vector3i) -> Array[int]: + return [cell.x, cell.y, cell.z] + + +static func _cell_precedes(a: Vector3i, b: Vector3i) -> bool: + if a.z != b.z: + return a.z < b.z + if a.y != b.y: + return a.y < b.y + return a.x < b.x + + +static func _names_to_strings(source: Array[StringName]) -> Array[String]: + var result: Array[String] = [] + for value: StringName in source: + result.append(String(value)) + return result + + +static func _string_key_dictionary(source: Dictionary) -> Dictionary: + var result: Dictionary = {} + var keys: Array = source.keys() + keys.sort_custom(func(a: Variant, b: Variant) -> bool: return String(a) < String(b)) + for raw_key: Variant in keys: + result[String(raw_key)] = source[raw_key] + return result + + +func _fail(message: String) -> bool: + last_error = message + return false + + +func _error_result(message: String) -> Dictionary: + last_error = message + return {&"ok": false, &"error": message} + + +# No retained cache and no observation is moved across a user callback. All +# expressions evaluated between the three original helper positions are pure +# builtin conversions under this guard. Extra candidate fields stay untouched. +func _can_share_candidate_need_map(guest: GuestRuntime, definition: GuestDefinition, candidate: Dictionary) -> bool: + # Same-engine constructor/duplicate source shows no user callback for these + # builtin conversions or nested Dictionary.duplicate(true). Identity guards + # preserve custom service, guest properties, and preference implementations. + return get_script() == load("res://scripts/guest/guest_service.gd") \ + and guest.get_script() == GuestRuntimeType \ + and definition.get_script() == GuestDefinitionType \ + and _value(candidate, &"need_relief", null) is Dictionary + + +func _need_relief_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var best := 0 + for need_id: StringName in NEED_IDS: + var relief := maxi(int(normalized.get(need_id, 0)), 0) + best = maxi(best, relief * int(guest.needs[need_id]) / 100) + return best + + +func _candidate_urgency_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var result := 0 + for need_id: StringName in NEED_IDS: + if int(normalized.get(need_id, 0)) > 0: + result = maxi(result, int(guest.needs[need_id])) + return result + + +func _primary_need_priority_from_normalized(guest: GuestRuntime, normalized: Dictionary) -> int: + var priority := 0 + for need: StringName in PRIMARY_NEEDS: + if int(normalized.get(need, 0)) > 0: + priority = maxi(priority, int(guest.needs[need])) + return priority diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-actual-independent-review-r296.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-actual-independent-review-r296.json new file mode 100644 index 0000000..5591d0f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-actual-independent-review-r296.json @@ -0,0 +1,249 @@ +{ + "review_id": "p23-02-guest-need-normalization-local-actual-independent-review-r296", + "finite_local_equivalence_accepted": true, + "finite_callback_fallback_equivalence_accepted": true, + "finite_exit_observation_accepted": true, + "blockers": [], + "evidence_sha256": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd", + "bytes": 61414, + "sha256": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd", + "bytes": 61426, + "sha256": "a70362ac6326266573a873319146d50a540829b751ad4c07746937fef85f5193" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd", + "bytes": 281, + "sha256": "d29c18196c89452a0ba6db8ec17d0ac2e75be2b6092533c734288230bc44f0c7" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd", + "bytes": 7048, + "sha256": "bd9cb24e28bc97c0e8f906b6d2b0d60c14964fdaabbac3ac6561334e434ff97f" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "bytes": 525, + "sha256": "5db64f0527aee0c74d0dff52103aefeec112824faa82137ab78d546979655658" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/run_all_tests.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/run_all_tests.py", + "bytes": 26296, + "sha256": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/regression/owned_outer.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/regression/owned_outer.py", + "bytes": 4177, + "sha256": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json", + "bytes": 9244, + "sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json", + "bytes": 4366, + "sha256": "f592b9895d9253be9e7f829cd88888fe166e77e00959570f7d4a7f0c3c169489" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-build-preview-monitor-compressed-r06.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-build-preview-monitor-compressed-r06.gd", + "bytes": 17582, + "sha256": "92d5e1956f4e613e12555d756de4f71fef03678cde159ac68fac3188ce3f336e" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd", + "bytes": 2276, + "sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f" + }, + "/Applications/Godot.app/Contents/MacOS/Godot": { + "path": "/Applications/Godot.app/Contents/MacOS/Godot", + "bytes": 351312912, + "sha256": "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py", + "bytes": 5232, + "sha256": "b9059debc1eb3aea9497d5abea646238edec5c73291d170737c29ce9026803eb" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json", + "bytes": 11638, + "sha256": "dc2662c7f9b92de16e9d4d97daf86d3a2eb537f2587cc76946d77e7021907071" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json", + "bytes": 968619, + "sha256": "3bba39c1ff62fb59a7b844515072dc322728295c5d7680aef4055af5d10494ee" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/preexecution-root-verification.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/preexecution-root-verification.json", + "bytes": 5516, + "sha256": "9877abcde2f6fa06aeaa81a8a8d6b8a5b06bb7db8012bbe321f9cf8e106a8f12" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json", + "bytes": 10788, + "sha256": "d01a859ce75a18084cbec7a081c906e267d1e8c7a42bce55e2539dd3a7ede8c3" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/profile.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/profile.json", + "bytes": 907, + "sha256": "f52b9cc1f3642a8b150c97a40f2cf3cd9a80c793cba5a6693811369294a50a64" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/phase.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/phase.json", + "bytes": 8097, + "sha256": "5ea3de68f87dda0fa0793e2d87428990645d61fa70cba14e44b8fb4d73c6f3b9" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst", + "bytes": 3540, + "sha256": "c21dad1a71f30a2c8d4cab558bbfb4543f2daca4487b1fbdbcbb1b1a97b9143d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/report.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/report.json", + "bytes": 1128, + "sha256": "fcf08d05ca7109beed8e0c4fc75f422baab72a515467e09debf58c812fb0689d" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stderr.log": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stderr.log", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/ended.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/ended.json", + "bytes": 1772, + "sha256": "63f84a8e05af97d63b2a37ed47e1075263b1b8fe51190ff14f934f24149c3919" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/adapter-owned-cleanup.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/adapter-owned-cleanup.json", + "bytes": 351, + "sha256": "c1f3d289b362cc61b9be169a3994be27df39f35e6221881b4a9678ce3c72ba54" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log", + "bytes": 7725, + "sha256": "067ea4cc50ac8e60720e8e58d4e83729afd1574522b290e8127a030eca581c1c" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stdout.log": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stdout.log", + "bytes": 7875, + "sha256": "f9e15b96bce11af6984f0562320758063cdcc880b237b802f09de863d616d839" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/started.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/started.json", + "bytes": 2392, + "sha256": "25d185b097982970d1d089fc6f45edcf97b461035e3c326d171922b3b1758384" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/process.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/process.json", + "bytes": 6661, + "sha256": "4ddf96836dc928b532bb708f81b6798705e314aac4b3369d068a5ac93cbf633b" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/outer-owned-finally.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/outer-owned-finally.json", + "bytes": 964, + "sha256": "13c2101303504e43e70f76c53389ef9894e4dd801a6de2939738f5e858eac729" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-ai-r03-typed-difference-independent-review-r01/finite_variant_codec.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-ai-r03-typed-difference-independent-review-r01/finite_variant_codec.py", + "bytes": 5438, + "sha256": "8c7077b53e28da4054bdf7dd88e82de02a51149269a8fce59bae0e74f95b4734" + } + }, + "canonical_doc4_context_pins": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md", + "bytes": 492299, + "sha256": "61776f0673770277ab6d182107337ed40a108ff3b5ab2c1a8f29fc4f2c3015ad" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md", + "bytes": 410363, + "sha256": "8b0488910dba35b34da96d7062b9a69d293e70a1963c9c5890ec32b2891baaea" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json", + "bytes": 1184337, + "sha256": "32d80e911910bc762287dc3ddf0ee0ea3072ca2f0158aa2f9f355b9202cfdfeb" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json", + "bytes": 539512, + "sha256": "b417ee1b945dfe4666f3a2f3501f906756826ea88d741bb116f3dd20250c8958" + } + }, + "observations": { + "engine_pid": 44204, + "owned_normal_exit_code": 0, + "checks": 1619, + "failures": 0, + "cases": 231, + "callback_cases": 2, + "typed_record_rows": 233, + "whole_raw_bytes": 408484, + "whole_raw_sha256": "ae97195121e2def68615caf2b0cb3bda59a130097ef34cb9656c75ac387b7343", + "finitecodec": { + "nodes": 28007, + "headers": { + "28": 676, + "27": 2078, + "4": 1288, + "21": 13636, + "65538": 288, + "2": 10028, + "0": 1, + "1": 8, + "3": 4 + }, + "file_envelope_bytes": 0, + "all_bytes_roundtrip": true + }, + "independent_paired_full_span_equal": 233, + "private_profile_conditions": 5, + "original_service_full_body_baseline": "Only class_name renamed; original body authenticated by accepted preexecution proof and matching pin.", + "actual_loaded_scope": "r295 GDScript GuestService finite evaluate/rank, not new native ABI/library acceptance.", + "script_parse_and_listed_paths_observed": true, + "input_and_state_observation": "Pinned QA passed full service snapshot/error/input comparisons and 224 actual nested-readonly checks. Readonly/alias flags are QA assertions, not independently encoded flags in Variant byte archive. Returned fresh Dictionary checked 231 times.", + "microbenchmark": { + "input_index": 50, + "need_map_entries": 1, + "calls_each": 2000, + "original_us": 28461, + "candidate_us": 41815, + "fixed_order": [ + "original", + "candidate" + ], + "performance_accepted": false + } + }, + "scope_limits": [ + "233 stored expected/actual ordered typed result spans independently match all bytes; full snapshots/errors/input preservation and guard selection are evidenced by passing pinned QA assertions, not additional snapshot records in this archive.", + "Two custom preference cases genuinely mutate guest.needs and nested candidate relief, including explicit preference eager-default dispatch, with original fallback results and state equality. This is not general reentry or arbitrary custom override acceptance.", + "Inputs[50] is one Need map, not representative six-map workload; one-process fixed-order 2000-call microbenchmark is not whole tick, stable benefit/regression, pure CPU, P95/D10 or 50ms budget evidence.", + "No whole647/full172/normal OS/same-version Windows/performance/adoption/unit completion acceptance. Candidate remains unadopted.", + "16MiB output restriction is post-phase, not live peak." + ], + "reviewer_execution": { + "engine": 0, + "compiler": 0, + "tests": 0, + "validator": 0, + "author": 0, + "new_agents": 0, + "existing_edits": 0, + "external_zstd_read_only": 1, + "finite_codec_read_only": 1 + } +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json new file mode 100644 index 0000000..75a19aa --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json @@ -0,0 +1,155 @@ +{ + "review_id": "p23-02-guest-need-normalization-local-preexecution-independent-review-r296", + "execution_allowed": true, + "blockers": [], + "evidence_sha256": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd", + "bytes": 61414, + "sha256": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd", + "bytes": 61426, + "sha256": "a70362ac6326266573a873319146d50a540829b751ad4c07746937fef85f5193" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd", + "bytes": 281, + "sha256": "d29c18196c89452a0ba6db8ec17d0ac2e75be2b6092533c734288230bc44f0c7" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd", + "bytes": 7048, + "sha256": "bd9cb24e28bc97c0e8f906b6d2b0d60c14964fdaabbac3ac6561334e434ff97f" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "bytes": 525, + "sha256": "5db64f0527aee0c74d0dff52103aefeec112824faa82137ab78d546979655658" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/run_all_tests.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/run_all_tests.py", + "bytes": 26296, + "sha256": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/regression/owned_outer.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/regression/owned_outer.py", + "bytes": 4177, + "sha256": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json", + "bytes": 9244, + "sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json", + "bytes": 4366, + "sha256": "f592b9895d9253be9e7f829cd88888fe166e77e00959570f7d4a7f0c3c169489" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-build-preview-monitor-compressed-r06.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-build-preview-monitor-compressed-r06.gd", + "bytes": 17582, + "sha256": "92d5e1956f4e613e12555d756de4f71fef03678cde159ac68fac3188ce3f336e" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd", + "bytes": 2276, + "sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f" + }, + "/Applications/Godot.app/Contents/MacOS/Godot": { + "path": "/Applications/Godot.app/Contents/MacOS/Godot", + "bytes": 351312912, + "sha256": "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py", + "bytes": 5232, + "sha256": "b9059debc1eb3aea9497d5abea646238edec5c73291d170737c29ce9026803eb" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json", + "bytes": 968619, + "sha256": "3bba39c1ff62fb59a7b844515072dc322728295c5d7680aef4055af5d10494ee" + } + }, + "canonical_doc4_context_pins": { + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md", + "bytes": 492299, + "sha256": "61776f0673770277ab6d182107337ed40a108ff3b5ab2c1a8f29fc4f2c3015ad" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md", + "bytes": 410363, + "sha256": "8b0488910dba35b34da96d7062b9a69d293e70a1963c9c5890ec32b2891baaea" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json", + "bytes": 1184337, + "sha256": "32d80e911910bc762287dc3ddf0ee0ea3072ca2f0158aa2f9f355b9202cfdfeb" + }, + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json", + "bytes": 539512, + "sha256": "b417ee1b945dfe4666f3a2f3501f906756826ea88d741bb116f3dd20250c8958" + } + }, + "allowed_scope": "One bounded owned headless engine, original class-name-only baseline versus transient r295 GuestService; restore original GuestService/project after child termination. No compiler, clone, adoption or whole execution.", + "independent_derivation": { + "enumerated_builtin_cases": 224, + "additional_cases": 7, + "candidate_cases": 231, + "callback_cases": 2, + "checks": { + "setup": 4, + "builtin_comparisons": 1120, + "additional_comparisons": 28, + "rank": 1, + "custom_callback": 10, + "actual_nested_readonly": 224, + "fresh_return": 231, + "exclusive_typed": 1, + "total": 1619 + }, + "starter_assertions": 0, + "inventory_files_each": 1299, + "transient_inventory_difference": [ + "scripts/guest/guest_service.gd" + ], + "baseline_body_exact_inverse": true, + "capacity_free_bytes_observed": 1255604224, + "floor_bytes": 536870912, + "admission_bytes": 553648128 + }, + "verified_controls": [ + "Original class_name is the only baseline change; full evaluate and other methods remain byte-identical. Candidate source and static review pins match.", + "224 canonical map fixtures independently restore nested readonly after duplicate(true); 7 additional fixtures cover scalar/invalid/float/explicit/alias spellings. No count assumes a registration assertion in the Guard starter.", + "CustomDefinition inherits a Resource definition with no autoload-dependent startup references; preference_for performs real needs and nested relief mutation; eager default call must execute once even with explicit preference.", + "Snapshot/input/ordered return comparisons, fresh returned Dictionary checks and one ranked tie/order comparison remain asserted.", + "QA inherits deferred synchronous run; no coroutine super.run issue. Typed archive exclusive check occurs before report construction. Guard activates private runtime profile before deferred evaluation.", + "Driver validates preexecution pins and bound root SHA, exact source/default/current docs/SUPPORT/shared and immutable parent, 512MiB floor plus 16MiB admission, no live foreign engine before patch, owned90s capture/strict error logs/private5/orphan rejection. Restoration in finally requires all captured engines ended; final full integrity protection." + ], + "inference_limits": [ + "Textual GDScript syntax/type review is not actual parse execution; isolated engine may still reject types and must retain failure evidence.", + "One process 2000 evaluations per side is a helper microbenchmark only, fixed original-first order; no stable gain, whole tick, pure CPU, P95/D10 or 50ms inference.", + "16MiB output budget is checked after phase, not enforced as live peak. Capacity acceptance is observation-specific and runtime admission remains mandatory.", + "Finite callback covers custom definition preference mutation; arbitrary service/guest override, concurrent mutation, malicious same-path resource replacement and generic reentry remain unaccepted.", + "No whole647/final172/normal OS/same-version Windows/performance/adoption/unit acceptance." + ], + "reviewer_execution": { + "engine": 0, + "compiler": 0, + "tests": 0, + "validator": 0, + "author": 0, + "existing_edits": 0, + "new_agents": 0 + } +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296.py b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296.py new file mode 100644 index 0000000..76db0df --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296.py @@ -0,0 +1,43 @@ +# coding: utf-8 +from pathlib import Path +import hashlib,importlib.util,json,os,shutil,sys,uuid +sys.dont_write_bytecode=True +R=Path(__file__).resolve().parents[2];A=R/'artifacts/original-completion-2026-10-02';D=R/'docs/plans/original-completion-2026-10-02';O=A/'p23-02-guest-need-normalization-local-r296' +def mod(name,path): + s=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(s);s.loader.exec_module(m);return m +def main(): + r=mod('r296',R/'tools/run_all_tests.py');outer=mod('outer296',r.SUPPORT/'owned_outer.py');plan=r.read(O/'input.json');assert not (O/'execution.json').exists() + prepath=A/'p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json';pre=r.read(prepath);assert pre['execution_allowed'] and not pre['blockers'] + for group in ['evidence_sha256','canonical_doc4_context_pins']: + for key,q in pre[group].items(): + p=Path(q.get('path',key));assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256'] + root=r.read(O/'preexecution-root-verification.json');assert root['passed'] and root['review_sha256']==r.sha(prepath) + stage=Path(plan['stage']);parent=Path(plan['parent']);guest=Path(plan['guest_source']);original=guest.read_bytes();modified=Path(plan['modified_guest']).read_bytes();public=(stage/'project.godot').read_bytes();patched=False + def protect(): + assert r.source_refs(R)==plan['source_before'] and r.default_player(R)==plan['default_before'];assert {n:r.sha(D/n) for n in plan['doc4_before']}==plan['doc4_before'];assert r.inventory(r.SUPPORT)==plan['support_inventory'] + assert r.inventory(parent,stage=True)==plan['expected_stage'];assert r.inventory(stage,stage=True)==(plan['patched_stage'] if patched else plan['expected_stage']) + for key,q in plan['pins'].items(): + p=Path(q['path']) + if p==guest:assert p.read_bytes()==(modified if patched else original) + else:assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256'],key + protect();assert not outer.native.helper['live_engines']();assert shutil.disk_usage(A).free>=plan['capacity_floor_bytes']+plan['output_budget_bytes'] + error=None;phase=None;report=None;cleanup=[];cleanup_errors=[] + try: + guest.write_bytes(modified);patched=True;protect();output=O/'measurement';output.mkdir();ident=uuid.uuid4().hex;startup='TheTree-PCK-tests-'+ident+'-needs-startup';profile='TheTree-PCK-tests-'+ident+'-needs-runtime';assert not r.profile_path(startup).exists() and not r.profile_path(profile).exists() + env=r.fresh_environment(os.environ);env.update(THE_TREE_PACK_PROFILE_NAME=profile,THE_TREE_PACK_PROFILE_EXPECTED=str(r.profile_path(profile)),THE_TREE_PACK_PROFILE_REPORT=str(O/'profile.json'),THE_TREE_RUNTIME_MONITOR_OUTPUT=str(output)) + (stage/'project.godot').write_bytes(r.private_project(public,startup));argv=[plan['engine_path'],'--headless','--path',str(stage),'--script',str(O/'guarded.gd'),'--verbose','--log-file',str(O/'engine/engine-native.log')] + result,capture=outer.run_phase(argv,O/'engine',plan['timeout_seconds'],env,now_fn=r.now,dump_fn=r.write,sha_fn=r.sha,live_engines_fn=outer.native.helper['live_engines']);phase=dict(result=result,capture=capture);r.write(O/'phase.json',phase) + rp=output/'report.json';report=r.read(rp) if rp.exists() else None;assert result and result['exit_code']==0 and not result['error_lines'] and not result['timed_out'] and capture['passed'];assert report and report['passed'] and not report['failures'] and report['checks']==plan['expected_checks'] and report['cases']==plan['local_cases'] and report['callback_cases']==2 and report['pid']==result['pid'] + logs=''.join((O/'engine'/n).read_text() for n in ['stdout.log','stderr.log','engine-native.log']);assert 'Orphan StringName:' not in logs and 'unclaimed string names' not in logs;r.profile_gate(r.profile_path(profile),O/'profile.json',profile) + assert sum(p.stat().st_size for p in O.rglob('*') if p.is_file())<=plan['output_budget_bytes'];(stage/'project.godot').write_bytes(public);guest.write_bytes(original);patched=False;protect() + except BaseException as caught:error=type(caught).__name__+': '+str(caught) + finally: + cleanup,cleanup_errors=outer.finish_captured(outer.OWNED) + if all(q['engine_ended'] is True for q in cleanup):guest.write_bytes(original);patched=False;(stage/'project.godot').write_bytes(public) + else:cleanup_errors.append('Restoration refused while owned child remains live') + guard_errors=[] + try:assert guest.read_bytes()==original and not patched;protect() + except BaseException as caught:guard_errors.append(type(caught).__name__+': '+str(caught)) + passed=error is None and not guard_errors and not cleanup_errors and phase is not None and report is not None + r.write(O/'execution.json',dict(passed=passed,error=error,guard_errors=guard_errors,phase=phase,report=report,cleanup=cleanup,cleanup_errors=cleanup_errors,source_default_doc4_support_stage_preserved=not guard_errors,guest_project_restored_byteexact=not patched,compiler_runs=0,unit_accepted=False,performance_accepted=False,candidate_adopted=False));print(json.dumps(dict(passed=passed,error=error,guard_errors=guard_errors,timings=report.get('timings') if report else None)),flush=True);return 0 if passed else 1 +if __name__=='__main__':raise SystemExit(main()) diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/actual-root-verification.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/actual-root-verification.json new file mode 100644 index 0000000..e2a2830 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/actual-root-verification.json @@ -0,0 +1,183 @@ +{ + "passed": true, + "review_sha256": "ce06bfe9156ed8e1c9ec53519d6cc52b26c963ca0e8b4a263ff70d1a1abf9a23", + "verified_entries": [ + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage/scripts/guest/guest_service.gd", + "bytes": 61414, + "sha256": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/source/guest_service.gd", + "bytes": 64563, + "sha256": "2794f20630168d902e6a7fb55a0b5aacfbbcfcccd51d336f0724f03ee35ea249" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/original_guest_service.gd", + "bytes": 61426, + "sha256": "a70362ac6326266573a873319146d50a540829b751ad4c07746937fef85f5193" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/custom_definition.gd", + "bytes": 281, + "sha256": "d29c18196c89452a0ba6db8ec17d0ac2e75be2b6092533c734288230bc44f0c7" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd", + "bytes": 7048, + "sha256": "bd9cb24e28bc97c0e8f906b6d2b0d60c14964fdaabbac3ac6561334e434ff97f" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "bytes": 525, + "sha256": "5db64f0527aee0c74d0dff52103aefeec112824faa82137ab78d546979655658" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/run_all_tests.py", + "bytes": 26296, + "sha256": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/tools/regression/owned_outer.py", + "bytes": 4177, + "sha256": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-static-independent-review-r295.json", + "bytes": 9244, + "sha256": "0954f512e6065c26ffc7d06c087fe973379a48c9cebef6752f19ec52d24f59d8" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-candidate-r295/input.json", + "bytes": 4366, + "sha256": "f592b9895d9253be9e7f829cd88888fe166e77e00959570f7d4a7f0c3c169489" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-build-preview-monitor-compressed-r06.gd", + "bytes": 17582, + "sha256": "92d5e1956f4e613e12555d756de4f71fef03678cde159ac68fac3188ce3f336e" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd", + "bytes": 2276, + "sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f" + }, + { + "path": "/Applications/Godot.app/Contents/MacOS/Godot", + "bytes": 351312912, + "sha256": "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296.py", + "bytes": 5232, + "sha256": "b9059debc1eb3aea9497d5abea646238edec5c73291d170737c29ce9026803eb" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-preexecution-independent-review-r296.json", + "bytes": 11638, + "sha256": "dc2662c7f9b92de16e9d4d97daf86d3a2eb537f2587cc76946d77e7021907071" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/input.json", + "bytes": 968619, + "sha256": "3bba39c1ff62fb59a7b844515072dc322728295c5d7680aef4055af5d10494ee" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/preexecution-root-verification.json", + "bytes": 5516, + "sha256": "9877abcde2f6fa06aeaa81a8a8d6b8a5b06bb7db8012bbe321f9cf8e106a8f12" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/execution.json", + "bytes": 10788, + "sha256": "d01a859ce75a18084cbec7a081c906e267d1e8c7a42bce55e2539dd3a7ede8c3" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/profile.json", + "bytes": 907, + "sha256": "f52b9cc1f3642a8b150c97a40f2cf3cd9a80c793cba5a6693811369294a50a64" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/phase.json", + "bytes": 8097, + "sha256": "5ea3de68f87dda0fa0793e2d87428990645d61fa70cba14e44b8fb4d73c6f3b9" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst", + "bytes": 3540, + "sha256": "c21dad1a71f30a2c8d4cab558bbfb4543f2daca4487b1fbdbcbb1b1a97b9143d" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/report.json", + "bytes": 1128, + "sha256": "fcf08d05ca7109beed8e0c4fc75f422baab72a515467e09debf58c812fb0689d" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stderr.log", + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/ended.json", + "bytes": 1772, + "sha256": "63f84a8e05af97d63b2a37ed47e1075263b1b8fe51190ff14f934f24149c3919" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/adapter-owned-cleanup.json", + "bytes": 351, + "sha256": "c1f3d289b362cc61b9be169a3994be27df39f35e6221881b4a9678ce3c72ba54" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log", + "bytes": 7725, + "sha256": "067ea4cc50ac8e60720e8e58d4e83729afd1574522b290e8127a030eca581c1c" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/stdout.log", + "bytes": 7875, + "sha256": "f9e15b96bce11af6984f0562320758063cdcc880b237b802f09de863d616d839" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/started.json", + "bytes": 2392, + "sha256": "25d185b097982970d1d089fc6f45edcf97b461035e3c326d171922b3b1758384" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/process.json", + "bytes": 6661, + "sha256": "4ddf96836dc928b532bb708f81b6798705e314aac4b3369d068a5ac93cbf633b" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/outer-owned-finally.json", + "bytes": 964, + "sha256": "13c2101303504e43e70f76c53389ef9894e4dd801a6de2939738f5e858eac729" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-ai-r03-typed-difference-independent-review-r01/finite_variant_codec.py", + "bytes": 5438, + "sha256": "8c7077b53e28da4054bdf7dd88e82de02a51149269a8fce59bae0e74f95b4734" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md", + "bytes": 492299, + "sha256": "61776f0673770277ab6d182107337ed40a108ff3b5ab2c1a8f29fc4f2c3015ad" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md", + "bytes": 410363, + "sha256": "8b0488910dba35b34da96d7062b9a69d293e70a1963c9c5890ec32b2891baaea" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json", + "bytes": 1184337, + "sha256": "32d80e911910bc762287dc3ddf0ee0ea3072ca2f0158aa2f9f355b9202cfdfeb" + }, + { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json", + "bytes": 539512, + "sha256": "b417ee1b945dfe4666f3a2f3501f906756826ea88d741bb116f3dd20250c8958" + } + ], + "unit_accepted": false, + "performance_accepted": false +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/custom_definition.gd b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/custom_definition.gd new file mode 100644 index 0000000..06ac6ff --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/custom_definition.gd @@ -0,0 +1,9 @@ +extends "res://scripts/guest/guest_definition.gd" +var watched_guest: Variant +var watched_candidate: Dictionary +var calls := 0 +func preference_for(_tags: Variant) -> int: + calls += 1 + watched_guest.needs[&"hunger"] = 99 + watched_candidate[&"need_relief"][&"hunger"] = 80 + return 13 diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/execution.json b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/execution.json new file mode 100644 index 0000000..b94d15b --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/execution.json @@ -0,0 +1,262 @@ +{ + "passed": true, + "error": null, + "guard_errors": [], + "phase": { + "result": { + "argv": [ + "/Applications/Godot.app/Contents/MacOS/Godot", + "--headless", + "--path", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage", + "--script", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "--verbose", + "--log-file", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + ], + "pid": 44204, + "started_utc": "2026-10-11T23:34:52.715973+00:00", + "ended_utc": "2026-10-11T23:34:54.109697+00:00", + "wall_seconds": 1.393236333, + "exit_code": 0, + "helper_exit_code": 0, + "timed_out": false, + "stdout_sha256": "f9e15b96bce11af6984f0562320758063cdcc880b237b802f09de863d616d839", + "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "error_lines": [], + "owned_popen_ended": true, + "old_helper_engine_ended": true, + "start_receipt_written": true, + "end_receipt_written": true, + "lifecycle_error": null, + "old_helper_lifecycle_error": null, + "actual_start_binding": { + "scope": "isolated diagnostic owned child", + "event": "ENGINE_STARTED", + "actual_pid": 44204, + "owner_pid": 44180, + "command": [ + "/Applications/Godot.app/Contents/MacOS/Godot", + "--headless", + "--path", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage", + "--script", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "--verbose", + "--log-file", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + ], + "monotonic_ns": 2578288416, + "pid": 44204, + "argv": [ + "/Applications/Godot.app/Contents/MacOS/Godot", + "--headless", + "--path", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage", + "--script", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "--verbose", + "--log-file", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + ], + "started_utc": "2026-10-11T23:34:52.715973+00:00", + "current_process_identity": { + "pid": 44204, + "ppid": 44180, + "pgid": 44204, + "args": "/Applications/Godot.app/Contents/MacOS/Godot --headless --path /Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage --script /Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd --verbose --log-file /Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + }, + "live_identity_exact": true, + "tracked_child_ended_before_initial_probe": false, + "signal_scope": "tracked Popen child only; no external group signal" + }, + "actual_end_binding": { + "scope": "isolated diagnostic owned child", + "event": "ENGINE_ENDED", + "actual_pid": 44204, + "owner_pid": 44180, + "command": [ + "/Applications/Godot.app/Contents/MacOS/Godot", + "--headless", + "--path", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage", + "--script", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "--verbose", + "--log-file", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + ], + "monotonic_ns": 3964880666, + "exit_code": 0, + "actual_process_exit_code": 0, + "engine_ended": true, + "timed_out": false, + "lifecycle_error": null, + "start_receipt_written": true, + "pid": 44204, + "argv": [ + "/Applications/Godot.app/Contents/MacOS/Godot", + "--headless", + "--path", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-reconstruction-local-r209/stage", + "--script", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/guarded.gd", + "--verbose", + "--log-file", + "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/engine/engine-native.log" + ], + "ended_utc": "2026-10-11T23:34:54.107599+00:00", + "signal_scope": "tracked Popen child only; no external group signal" + }, + "strict_native_diagnostics": { + "errors": [], + "banner_count": 1, + "known_unicode_diagnostic_count": 0, + "unrecognized_unicode_diagnostic_count": 0, + "exit_leak_diagnostic_lines": [] + }, + "external_process_or_group_signals": 0, + "owned_child_process_helper_sha256": "c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037", + "adapter_owned_cleanup": { + "owned_child_created": true, + "engine_ended": true, + "actual_process_exit_code": 0, + "attempts": [ + { + "attempt": 1, + "kill_attempted": false, + "wait_timeout_seconds": 5, + "wait_completed": true, + "errors": [], + "poll_before": 0, + "wait_returned_code": 0, + "poll_after": 0 + } + ], + "errors": [] + }, + "adapter_error": null, + "old_helper_bytes_preserved": true, + "helper_wait_None_maps_to_actual_timeout_seconds": 5, + "explicit_helper_wait_timeouts_preserved": true, + "additional_cleanup_wait_timeout_seconds": 5, + "additional_cleanup_max_attempts": 3 + }, + "capture": { + "actual_captured_pids": [ + 44204 + ], + "cleanup": [ + { + "pid": 44204, + "cleanup": { + "owned_child_created": true, + "engine_ended": true, + "actual_process_exit_code": 0, + "attempts": [ + { + "attempt": 1, + "kill_attempted": false, + "wait_timeout_seconds": 5, + "wait_completed": true, + "errors": [], + "poll_before": 0, + "wait_returned_code": 0, + "poll_after": 0 + } + ], + "errors": [] + }, + "outer_cleanup_errors": [], + "engine_ended": true, + "outer_maximum_helper_invocations": 3, + "helper_maximum_waits": 3, + "per_wait_timeout_seconds": 5, + "foreign_process_or_group_signals": 0 + } + ], + "adapter_exception": null, + "all_captured_ended": true, + "outer_cleanup_errors": [], + "foreign_process_or_group_signals": 0, + "all_cleanup_before_outer_receipt": true, + "outer_receipt_written": true, + "outer_receipt_error": null, + "passed": true + } + }, + "report": { + "callback_cases": 2, + "candidate_adopted": false, + "cases": 231, + "checks": 1619, + "failures": [], + "fixtures": { + "bytes": 3540, + "codec": "zstd", + "field_deletions": 0, + "file_envelope": false, + "full_bytes_roundtrip": true, + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/measurement/evaluation-fixtures.varbytes.zst", + "sha256": "c21dad1a71f30a2c8d4cab558bbfb4543f2daca4487b1fbdbcbb1b1a97b9143d", + "uncompressed_bytes": 408484, + "uncompressed_sha256": "ae97195121e2def68615caf2b0cb3bda59a130097ef34cb9656c75ac387b7343" + }, + "limits": [ + "One process finite builtin and custom-preference observations only. Adjacent helper microbenchmark is not representative whole-tick, stable gain, P95/D10/50ms, full regression, OS or Windows acceptance." + ], + "passed": true, + "performance_accepted": false, + "pid": 44204, + "timings": [ + { + "calls": 2000, + "elapsed_us": 28461, + "mode": "original" + }, + { + "calls": 2000, + "elapsed_us": 41815, + "mode": "candidate" + } + ], + "unit_accepted": false + }, + "cleanup": [ + { + "pid": 44204, + "cleanup": { + "owned_child_created": true, + "engine_ended": true, + "actual_process_exit_code": 0, + "attempts": [ + { + "attempt": 1, + "kill_attempted": false, + "wait_timeout_seconds": 5, + "wait_completed": true, + "errors": [], + "poll_before": 0, + "wait_returned_code": 0, + "poll_after": 0 + } + ], + "errors": [] + }, + "outer_cleanup_errors": [], + "engine_ended": true, + "outer_maximum_helper_invocations": 3, + "helper_maximum_waits": 3, + "per_wait_timeout_seconds": 5, + "foreign_process_or_group_signals": 0 + } + ], + "cleanup_errors": [], + "source_default_doc4_support_stage_preserved": true, + "guest_project_restored_byteexact": true, + "compiler_runs": 0, + "unit_accepted": false, + "performance_accepted": false, + "candidate_adopted": false +} diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/guarded.gd b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/guarded.gd new file mode 100644 index 0000000..7aa3046 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/guarded.gd @@ -0,0 +1,5 @@ +extends "/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-guest-need-normalization-local-r296/local.gd" +const Guard := preload("/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-cache-public-mutations-r144/profile_guard.gd") +func _initialize() -> void: + if not Guard.activate(): quit(97); return + super._initialize() diff --git a/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/input.json.gz b/docs/plans/original-completion-2026-10-02/checkpoints/guest-need-normalization-r295-r298/p23-02-guest-need-normalization-local-r296/input.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..58959e31510dff14ab3a84669ea45cce310912df GIT binary patch literal 227315 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