diff --git a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json index 06b74d1..68b8dc8 100644 --- a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +++ b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -3,14 +3,25 @@ "scope": "exact per-suite completion label/schema; all check counts from actual raw only", "rows": [ { - "test": "tests/ui/test_operations_notification.gd", - "template": "Operations notification: checks, failures", + "test": "tests/s4/test_original_relationship_model.gd", + "template": "Original relationship model: checks, failures", "required": true, "fixed_check_count_used": false, - "source_sha256_at_schema_audit": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", + "source_sha256_at_schema_audit": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", "authority": { - "path": "docs/spec/acceptance/P24-02-code-r01.json", - "scope": "new local suite source marker; actual execution pending" + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" + } + }, + { + "test": "tests/s4/test_original_relationship_runtime.gd", + "template": "Original relationship runtime: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "authority": { + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" } }, { @@ -67,7 +78,7 @@ "template": "Required kitchen: checks, failures", "required": true, "fixed_check_count_used": false, - "source_sha256_at_schema_audit": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", + "source_sha256_at_schema_audit": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f", "authority": { "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p09-kitchen-single-link-candidate-r09/candidates/tests/business/test_required_kitchen.gd", "sha256": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", @@ -75,7 +86,7 @@ }, "source_marker_lines": [ { - "line": 281, + "line": 481, "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])" } ] @@ -3679,6 +3690,61 @@ "text": "\tprint(\"TOMARI_FLOOR_TROUBLE checks=%d failures=%d\" % [checks,failures])" } ] + }, + { + "test": "tests/s1/test_p08_combined_migration.gd", + "template": "P08 combined migration: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "authority": { + "path": "docs/spec/acceptance/P08-02.json", + "scope": "new standalone mixed suite; native execution pending" + } + }, + { + "test": "tests/ui/test_p08_normal_placement_ops.gd", + "template": "P08 normal placement: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + }, + { + "test": "tests/transport/test_runtime_build_preview.gd", + "template": "Runtime build preview: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c881b60bce7276047884303a6823da9abfaaf8c1e85dbc5f2c0ff13f0d742449", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + } + ,{ + "test": "tests/ui/test_p23_life_observation.gd", + "template": "P23 life observation: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8", + "authority": { + "path": "docs/spec/acceptance/P23-03.json", + "scope": "component and read-only observation only; native unexecuted; whole unit blocked" + } + }, + { + "test": "tests/ui/test_operations_notification.gd", + "template": "Operations notification: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", + "authority": { + "path": "docs/spec/acceptance/P24-02-code-r01.json", + "scope": "new local suite source marker; actual execution pending" + } } ], "unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting", diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md new file mode 100644 index 0000000..75aa2fe --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md @@ -0,0 +1,63 @@ +# Actual Opus authorized non-UI continuation of the same repair4 + +Actual commander679a702635994176bef72423ebb989bb selected Codex6.1-sol. Existing GUI is terminal on Antigravity quota; no concurrent worker is active. Contract worker=agy remains original UI assignment; this recorded phase authorizes Codex implementation ONLY for the non-UI checker/static/test/doc files below. Do not launch AGY or repeat/replace GUI work. No task/state/spec/counter changes. + +I authorize one code-only (non-UI) Codex continuation of the same repair4, on the same worktree and task, at exact HEAD 9641ed52def2ff8e5b0222449a4a83a1191e1d37. This does not add a task, worker registration, repair count, cap or spec change. The old GUI-only direction f8c30db is ended (Antigravity quota) and this replaces it only for the remaining non-UI work. + +What I checked read-only: +- The defects are real. tools/check_p24_operations_evidence.py:420-424 has the two count-bypass `pass` branches. :400-401 silently skips rows that are not dicts. :409/:413/:417 let a missing SHA, failures or passed field through. :69-73 lets the CANDIDATE_COMMIT/TARGET_HEAD env value take the place of the real git HEAD. :86-95 is a separate local roster that differs from tools/run_all_tests.py roster(root) at :27 (stdlib-only, with a __main__ guard at :314). +- The unfinished GUI checker repair4/check_repair4_preservation.py exists but has not been run. +- I could not find preserved-GUI-terminal-quota inside this worktree. It is the owner's root copy outside the worktree, so the bridge must check it before launch. + +PRE-LAUNCH (bridge, read-only): record `git rev-parse HEAD` == 9641ed5…; confirm no live job; record the sha256 of the 4 protected files below and confirm they match the root preserved-GUI-terminal-quota copies. Abort on any mismatch. + +PROTECTED, byte-identical, no edits (SHA guard checked before and after the run; any difference = reject, no submit): +- scenes/main/main.gd +- scenes/main/world_preview.gd +- tools/regression/suite-policy.json +- docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +Also leave unchanged: OWNER_REPAIR4_GUI_INSTRUCTION.md, OWNER_REPAIR4_SUPPLEMENT_CHECKER.md, root-current-main-reference-20261017/**, and all other scope files not named below. + +ALLOWED EDITS: +(a) tools/check_p24_operations_evidence.py, limited to supplement S1/S2 exactly as written in OWNER_REPAIR4_SUPPLEMENT_CHECKER.md: +- Load the required suite set from tools/run_all_tests.py roster(root) with importlib, read-only. Remove the local roster or make it a thin wrapper. +- Compare suite sets exactly. Reject: an empty or non-list suite list, non-dict rows, a missing path, unknown or duplicate suites, missing required suites, failures that are not int 0 (bool rejected), passed that is not True, and source_sha256 that is missing, not HEX64 or mismatched. +- Top-level counts are an optional cross-check only. +- Get the real HEAD from `git rev-parse HEAD` with cwd=repo root. If git fails, return "". The env value is only a cross-check, and a mismatch fails. Update the message at :477. +- S4: no other guard is changed. +(b) docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py: run it, and fix only real defects in it. It must stay read-only toward the protected files. +(c) New stdlib test docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py: the 8 negative cases and the positive controls from S3, using temp dirs and a temp `git init` repo. +(d) New docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/REPAIR4_CODE_PHASE_STATUS.md: +- all 8 conditions marked SOURCE-READY or NOTRUN, each with its dependencies; +- the S5 composition prerequisite (S/M/T/Q provenance; reuse the P23 composition functions and current-main run_all_tests source_refs/roster), listed as an unresolved Root dependency with every missing helper named; +- S6: the 179 and other earlier passes marked stale for this source; +- the GUI quota stop recorded honestly. +(e) The SOURCE_READY_PRESERVATION_RESULT.json produced by (b). +No other paths. + +LOCAL TEST ARGV (run from the worktree root, these only): +1. python3 -m py_compile tools/check_p24_operations_evidence.py tools/run_all_tests.py docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py +2. python3 -c "import importlib.util as u;s=u.spec_from_file_location('c','tools/check_p24_operations_evidence.py');m=u.module_from_spec(s);s.loader.exec_module(m)" +3. python3 docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py +4. python3 docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py +5. shasum -a 256 on the 4 protected files, matched against the pre-launch values. + +Forbidden: Godot, engine/native/OS collector, full roster, run_all_tests main(), the full checker against any index, writing acceptance fixtures or index.json, network, commit/branch/push, agy CLI, manual DB/SQL, env relabeling. + +RELEASE/SUBMIT: normal submit only if all 5 steps exit 0, all 4 protected SHAs are unchanged, git status shows changes only in the allowed paths, and every NOTRUN item is documented. Otherwise report the failure output and do not submit (the task stays repair_ready). + +MANDATORY GATES THAT STILL APPLY: +- Root S/M/T/Q composition in a genuine composed checkout +- Root static/import checks and affected local UI tests in Godot +- actual OS Japanese composition and scale evidence +- the full required roster on the exact final source +- tools/check_p24_operations_evidence.py --verify-only on the real index +- a separate independent Claude review with runtime evidence +- current-main integration and final validation +- Windows same-candidate platform acceptance, still pending + +The 8 conditions remain unmet and nothing counts as acceptance. + +Read OWNER_REPAIR4_SUPPLEMENT_CHECKER.md and the actual current477-line preservation script. Existing4GUIfile working changes are protected, must remain byte-identical. Do not touch Root instruction/reference files. Do not rely on stale prompt source excerpts instead of actual current disk. + +Use python -B where practical. py_compile is permitted by Opus; its small generated __pycache__ is ignored and not source. Do not add unlisted validation artifacts; put required local output facts and8conditions in REPAIR4_CODE_PHASE_STATUS.md. Root will inspect actual captured command exits before normal submit, not your self-report alone. Keep SOURCE-READY/NOTRUN honest. diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md new file mode 100644 index 0000000..41149c7 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md @@ -0,0 +1,52 @@ +# P24-02 repair4 — current same-session GUI work + +This is the complete current instruction. Supersedes old repair3 patch messages and stale review panel. Work in this existing P24-02-implement workspace only. HEAD9641ed5, same registered task, repairs4/maxnull. The old repair3 output was already committed by Root; do not apply it again. + +## Actual Opus repair direction f9560ec6130142749a929427f7a114c1 + +Repair4 (same task, same agy/Antigravity GUI semi-automatic worker via the existing UI packet; no new session, agent, provider, permission or cap change). Purpose: get the source ready for current-main reconciliation only. The latest validation failure (missing BUILD_OUTPUT_DIR/p24-operations-evidence/index.json) is the expected result while no actual run exists. Repair4 must not fabricate that index, add a pass template, or re-run the checker or full suite to make it go away. + +SCOPE: original base 1a5abc65, allowed_paths, spec 738268b4 and all8 conditions stay unchanged. Do NOT merge or rebase whole main into the worker. Reconcile only the 4 conflict files. Use the exact main 51e8657 snapshots in root-current-main-reference-20261017/REFERENCE_MANIFEST.json and check their sha256/blob pins before use: main.gd 92677e4a/fff37f08, world_preview.gd 4bc49a19/fa32fe03, completion_marker_contracts.json 6900df60/2eecb803, suite-policy.json 7eb085d7/ad194ae4. Merge the worker's own P24 diff (1a5abc..9641ed5) onto each snapshot. Every other main change is left to Root composition. If a reconciled file needs a symbol that exists only outside allowed_paths on main, keep the main snapshot text as-is and record it as a Root-composition dependency. Do not stub it or copy that other file. + +1) world_preview.gd: start from the main snapshot. Keep all accepted P23 code intact: LifeOverlay/_draw_life_overlay, the single coherent frame report, its getters and the strict guards. Re-apply the P24 click-control, placement/invalid-drop feedback and bubble render bindings on top, with no duplicated or shadowed funcs/vars/signals. +2) main.gd: start from the main snapshot. Keep staff relation snapshot-v2, the save migration, the normal menu, the staff candidate/management panels, _on_snapshot_published and the dimension migration paths. Re-apply the P24 save-label modal/IME, pause/resume, event log, cut-in wiring and operations camera controls. Cut-in hooks attach only to existing signals/states. Ordinary NTR/duel/defense triggers stay explicit unmet dependencies on whole P18-01/02, P19-01 and P21-01. P18 265ce30/5f6bc11 is backend-only. +3) completion_marker_contracts.json and suite-policy.json: keep every existing main entry byte-equivalent in meaning, then add only the P24 entries from the worker's diff. Do not regenerate, reorder, weaken or remove any roster, cleanup or completion authority. +4) Add a static overlap/preservation check (inside the allowed p24-02 docs/tools paths, e.g. a mode in check_p24_operations_evidence.py or a separate docs script). It lists every func/signal/const/class_name and every JSON top-level/entry key found in (a) the main snapshot and (b) the 9641ed5 P24 side for all 4 files. It fails if any of them is missing or duplicated in the reconciled file. It also statically checks that UI actor bubbles still match the current relation schema fields. Save its output as SOURCE-READY evidence. It is not runtime proof. +5) Do not run Godot, OS, import or full tests, and do not run the final checker in a loop. Root owns native/import/affected/full on the composed current-main tree. +6) Write a p24-02/repair4 note: reconciliation map per file with pins, the preservation-check result, status SOURCE-READY/NOTRUN, and all8 conditions each still unmet with the exact missing proof/dependency. Windows subconditions stay individually unmet with no pass templates. Old legacy60 evidence is limited and not current acceptance. Formal 14/88 is unchanged and there is no partial credit. + +After repair_ready, a recorded run decision is required before any worker starts. + +## Actual Opus run direction f8c30db569474df482ae6ab16502ce61 + +P24-02 is repair_ready (repairs=4, max null). Submitted source 9641ed5 is unchanged and no worker is running. I checked the Root reference set in the workspace: all 4 current-main snapshots are there. That is 2 .gd files renamed to .gd.source.txt, completion_marker_contracts.json and suite-policy.json, plus REFERENCE_MANIFEST.json and the 2 all8/limited-evidence audit JSONs. This matches the owner's statement. The owner authorized only one route, so the run is GUI delivery only. Root gets a ui-packet and sends one complete file-based instruction, through the current UI input, to the existing idle Antigravity IDE session P24-02-implement. Do not use the agy CLI and do not start another worker, session or CLI. Do not change the provider, model, settings or permissions. The old unaccepted repair3 review panel is historical only; do not apply that patch again. Repair4 direction for the worker: (1) Reconcile the 4 files against the 9641ed5 disk state and the main snapshots: scenes/main/main.gd, scenes/main/world_preview.gd, completion_marker_contracts.json and tools/regression/suite-policy.json. Keep all 7 accepted P23 code items and the P24 own diff. Do not merge or rebase the whole of main. (2) In the JSON merges, keep every main snapshot entry body and every P24 added entry. Keep main's ordering, policies and scope. (3) Write a mechanical static preservation audit that uses class- or scope-qualified GDScript symbol identities. Then a legitimate _draw or _init in a different inner class is not flagged as a duplicate. Record the audit result. (4) Add a SOURCE-READY/NOTRUN gap note for all 8 conditions. (5) Static Python, check and diff validation only. No Godot, OS or full roster run. Edit only within allowed_paths. Do not change originalbase, allowed paths, spec, the 8 conditions or the old count. Deliver actual reconciled files, not an outline. This run grants no acceptance, approval, integration or budget change. + +## Full pinned workspace materials + +Read docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json and its exact full snapshot paths. The two Godot source snapshots have .gd.source.txt suffix to prevent duplicate class import; bytes/SHA are unchanged. Read the all8 acceptance/gap and60 evidence SHA audit JSON files in that same directory. Keep this entire reference set immutable. Read immutable docs/spec/acceptance/P24-02-code-r01.json and relevant current source dependencies as needed. No size cap. + +## Contract scope (unchanged) + +[ + "scenes/main/main.gd", + "scenes/main/world_preview.gd", + "scripts/ui/event_log_dialog.gd", + "scripts/ui/manual_save_slots_dialog.gd", + "scripts/ui/operation_feedback.gd", + "scripts/ui/operation_feedback.gd.uid", + "scripts/ui/operations_camera_controls.gd", + "scripts/ui/operations_camera_controls.gd.uid", + "tests/ui/test_operations_notification.gd", + "tests/ui/test_operations_notification.gd.uid", + "tests/ui/fixtures/operations_os_observer.gd", + "tests/ui/fixtures/operations_os_observer.gd.uid", + "tools/check_p24_operations_evidence.py", + "tools/run_all_tests.py", + "tools/regression/suite-policy.json", + "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "docs/plans/original-completion-2026-10-02/execution/p24-02/**" +] + +## Required actual result + +Implement the reconciled source files and a meaningful static preservation checker plus its real result, and all8 SOURCE-READY/NOTRUN note in the allowed p24-02 docs area. Root owns actual native/current-main composition, import, impact, OS, final full and acceptance. Do not run Godot/LSP/editor/OS/full/checker-loop, do not change project.godot/caches/player files/settings/permissions/billing or immutable references/spec. Do not commit/push/rebase/merge whole main. Do not claim missing upstream events or Windows as passed. Do not recreate already-complete P23 code or delete earlier strict tests. Compare GDScript qualified symbols and main JSON full entry values plus only P24 added entries, detect missing/duplicate definitions without confusing same method names in different inner classes. Run meaningful static Python check and git diff --check. Report exact changed paths, preservation result and remaining conditions after actually writing files. diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md new file mode 100644 index 0000000..c75f2f5 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md @@ -0,0 +1,7 @@ +# Actual recorded Opus JSON-order-only correction — same repair4 + +Actual commander2a2e28a5c7e641c99263ab9caba5a9e7 chose Codex6.1-sol. Prior Codex1cde6bdd phase is finished, GUI remains terminalquota, no competing worker. Do not repeat previous code implementation or old GUI. + +Continue the SAME repair4 (no new task, no repair-counter change, no permission change). I checked the cause. In tools/regression/suite-policy.json, the new key "tests/ui/test_operations_notification.gd" is the third key (index 2) of suite_policies (line 18). In completion_marker_contracts.json, the P24-02 row is near the top of "rows" (line 34). check_repair4_preservation.py (lines 279-310, equals_main_plus_p24_only_additions_in_order) requires the main snapshot order with the P24-only additions appended at the end. Do not weaken the checker. INSTRUCTIONS: (0) Before any edit, record the sha256 of every protected file: scenes/main/main.gd, scenes/main/world_preview.gd, tools/check_p24_operations_evidence.py, the new 9-test script, repair4/check_repair4_preservation.py, the shared roster and all Root instruction/reference files. Do not run ps, and do not search outside the workspace. Trust ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json. (1) Edit only these two JSON files. In completion_marker_contracts.json, move only the new P24-02 marker row from its current index to the append position the checker expects, after all main rows. In suite-policy.json, move only the key tests/ui/test_operations_notification.gd and its object, unchanged, to the end of suite_policies. Change no values, no other keys or rows and no old main entries. Match the existing indentation and formatting, and change nothing else in those files. (2) Do a structural JSON comparison before and after. Each file must parse, and its parsed content must be identical apart from the position of the moved entry. That means every existing main entry and the P24 entry body are deep-equal, and the counts are unchanged (180 rows; the same policy key set). (3) Run py_compile on the touched and checker Python files with PYTHONPYCACHEPREFIX="$TMPDIR/p24-phase-pyc" set for that command only. The existing Runner Python 3.12 with the same task-local prefix is also acceptable. Do not mark the work blocked because of the cache location. Change no system settings or permissions. (4) Rerun check_repair4_preservation.py --write-evidence. It must report all pass, including equals_main_plus_p24_only_additions_in_order=true for both JSON files and the .gd results unchanged (279/279 and 179/180 with the one allowed bubble adapter). (5) Run git diff --check. Do not rerun the 9 tests if Root's workspace receipts show that their input/code hashes are unchanged and the earlier actual exit was 0; otherwise run them once. (6) Update only SOURCE_READY_PRESERVATION_RESULT.json (written by the checker) and REPAIR4_CODE_PHASE_STATUS.md. The status file documents the move-only fix, the structural-equality proof, the compile cache prefix, the 9-test reuse or rerun, and the before/after SHA guard. (7) Afterwards, recompute the protected SHAs; they must be byte-identical. Run no engine, OS, full or final checker. Do not create or edit the acceptance index. No commits. Do not relabel the earlier 179 result. Make the normal submission only if every check passes. These items stay pending: whole8, Root S/M/T/Q, native, current full, whole-unit review and Windows. If any check fails, stop and report the exact output without working around it. + +Root bridge has completed current HEAD/no-competing-job/operation-lease preflight. Do NOT run ps or search outsideworkspace. Read ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json for actual raw testcommand/event/exit0 and the exact3 input hashes. Read ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json for originalGUI copies. This correction explicitly may move only newP24 JSON entries, while retaining both .gd source bytes and all Python/helper/test code. No other Source-Ready claim than static/checker readiness; all8 andrealRootcomposition remain unmet. diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md new file mode 100644 index 0000000..00f0eb2 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md @@ -0,0 +1,19 @@ +# Actual Opus supplement — same active repair4 only + +Actual commander run cc9bd84f926d4b11ac320de925832e43. This is not a new worker/run/repair count. Continue current recorded repair4 and add these acceptance-required checker fixes within existing allowed_paths. + +WAIT. The existing GUI repair4 conversation (Opus 5.5 Medium, which received the f8c30db direction at 13:39) is still active: it shows Working/Cancel and is running 3-way reconciliation. The Runner reads repair_ready only because this is semi-automatic GUI mode. Do not issue run or request-repair, and do not add a worker, run or repair count. I read the checker and confirmed both defects in this checkout. Copy the supplement below into docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md and send it in the same active conversation. + +--- SUPPLEMENTAL INSTRUCTION (repair4, same worker, within existing allowed_paths) --- +S1 Count bypasses (tools/check_p24_operations_evidence.py:420-426): delete both `pass` branches, the ones that use len(seen_suites) and len(suite_entries). The roster check must compare actual suite sets and must not use counts. + a) The required set is the shared authority tools/run_all_tests.py roster(root). Load it read-only with importlib.util.spec_from_file_location; the module has a __main__ guard at :314 and imports only stdlib. Remove or replace the divergent local get_required_suite_roster (:86-95), or make it a thin wrapper that delegates to roster(). Do not reimplement the roster. + b) Reject the evidence if any of these hold: suite_entries is not a non-empty list; any row is not a dict, so stop silently skipping non-dict rows (:400-401); any row has no suite path; a suite path is not in the required set (unknown or invented); the same suite path appears more than once; any required suite is missing; a row's failures field is absent or is not int 0 (bool rejected); a row has passed absent or not True; source_sha256 is absent or not HEX64, or does not match the file on disk. + c) The top-level total_suites/suites_count/failures fields are optional cross-checks at most. If present, they must equal len(required) and 0. They must never stand in for per-suite rows. Absent rows always fail. +S2 get_actual_git_head (:68-83): run `git rev-parse HEAD` first, inside the repo root (cwd=repo root). If git fails, return "" so verification fails. CANDIDATE_COMMIT/TARGET_HEAD may only be a cross-check: if set and different from the real HEAD, fail verification with a clear mismatch error, and never use either value in place of the real HEAD. Update the error message at :477 to match. +S3 Tests: add a stdlib-only Python test under docs/plans/original-completion-2026-10-02/execution/p24-02/ (allowed path), using a temp repo root/fixture tests dir and calling the checker functions directly. Negative cases: (1) a required suite missing and replaced by an invented name with equal or higher row count; (2) a required suite missing with duplicate rows padding the count; (3) an unknown extra suite; (4) a failed row, a row with passed absent, a row with failures absent; (5) source_sha256 absent or mismatched; (6) rows absent while full_roster counts are present; (7) the env SHA differs from the real git HEAD in a temp git repo; (8) git is unavailable and only the env SHA is set. Positive controls: an exact roster with all rows passing and correct SHAs, and a real HEAD whose env value agrees or is unset. Run only this test plus static/import checks of the two tools. No Godot, no full roster, no OS collector, no full checker loop, no final acceptance fixtures or index. +S4 Keep every other guard unchanged: the 8 conditions, OS/process/PID checks, artifact hashes, current-profile checks, pending-template rejection. Do not rewrite unrelated code. +S5 Composition prerequisite (document it, do not implement it): the submitted old-base S and the combined current-main T/Q differ outside the 4 allowed files, and the checker has no real S/M/T/Q Git-provenance contract. Record this in p24-02 docs as SOURCE-READY/NOTRUN, an unresolved Root prerequisite that must be settled before heavy final evidence. Plan: Root reuses the accepted P23 composition functions and current-main run_all_tests.source_refs/roster in the genuine composed checkout. Do not import helpers that don't exist on the worker's base. List every missing helper/API as an explicit Root composition dependency. Do not use env or receipt relabeling. +S6 Earlier passes (the 179 and any others) do not apply to this changed source; mark them stale. Keep the same 4-file reconciliation, the static preservation audit and the note that the same 8 conditions are unmet. No commit, no Root source edits, no new workers. +--- END --- + +Root clarification: earlier P23 full179 remains valid for its original accepted d2581af/M5041b88/treea700519 source and already closed P23-03. It cannot be relabelled/reused as full regression for the new changed P24 source. Preserve all original proof bytes and closure records. No new whole-game or whole-unit acceptance here. diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json new file mode 100644 index 0000000..c1c532a --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json @@ -0,0 +1,38 @@ +{ + "task": "P24-02", + "head": "9641ed52def2ff8e5b0222449a4a83a1191e1d37", + "checks_done_by_trusted_Root_bridge_not_repeated_by_sandbox_worker": true, + "actual_no_other_Runner_job_at_prelaunch": [], + "actual_terminal_GUI_quota": "Individual quota reached; Error ID b866fa14-cff8-4fc1-ac4f-fba529d47498-136; queued followup failed same quota Error ID ending143; no Working/Cancel, Send disabled, baseline refresh2026-10-17 18:39:56 JST.", + "original_terminal_run": "261a974d4b6345419851bbba399cf1ae finished; no edits or local tests", + "4protected_original_copies": [ + { + "protected_path": "scenes/main/main.gd", + "readable_original_copy": "docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt", + "expected_and_actual_sha256": "f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24", + "exact_original_copy_verified": true + }, + { + "protected_path": "scenes/main/world_preview.gd", + "readable_original_copy": "docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt", + "expected_and_actual_sha256": "8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa", + "exact_original_copy_verified": true + }, + { + "protected_path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "readable_original_copy": "docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "expected_and_actual_sha256": "1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1", + "exact_original_copy_verified": true + }, + { + "protected_path": "tools/regression/suite-policy.json", + "readable_original_copy": "docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json", + "expected_and_actual_sha256": "fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9", + "exact_original_copy_verified": true + } + ], + "normal_lease": "Bridge acquires operation:P24-02 exclusively while executing Runner.llm. Check that proof; do not try ps from the sandbox. Your own run is necessarily live after Root pre-launch.", + "sandbox_policy_unchanged": true, + "instruction": "Do NOT search user folders or run ps; prelaunch/liveGUI checks belong to trusted bridge. You may rehash the exact four original copies supplied here and compare to actual working files if useful; no external path needed. Implement the allowed code phase and tests now. Root rechecks before/after.", + "at_jst": "2026-10-17T13:54:04.253755+09:00" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json new file mode 100644 index 0000000..ec6fc42 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -0,0 +1,3788 @@ +{ + "schema_version": 1, + "scope": "exact per-suite completion label/schema; all check counts from actual raw only", + "rows": [ + { + "test": "tests/s4/test_original_relationship_model.gd", + "template": "Original relationship model: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", + "authority": { + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" + } + }, + { + "test": "tests/s4/test_original_relationship_runtime.gd", + "template": "Original relationship runtime: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "authority": { + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" + } + }, + { + "test": "tests/ui/test_operations_notification.gd", + "template": "Operations notification: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", + "authority": { + "path": "docs/spec/acceptance/P24-02-code-r01.json", + "scope": "new local suite source marker; actual execution pending" + } + }, + { + "test": "tests/ui/test_selection_departure.gd", + "template": "Selection departure: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4", + "authority": { + "path": "docs/spec/acceptance/P24-03-code-r02.json", + "scope": "new local suite source marker; actual execution pending" + } + }, + { + "test": "tests/business/test_business_closure.gd", + "template": "Business closure: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business closure: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7b13a0dc13d1276cce7515973af47ad9653620e04221c43679e059e674b1d4fb", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Business closure: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/business/test_business_operations.gd", + "template": "Business operations: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business operations: 48 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5b1975cb3e921febe5c23ae26b3c8445d92ab68de2479d23f86f565a934f04f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 156, + "text": "\tprint(\"Business operations: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/business/test_required_kitchen.gd", + "template": "Required kitchen: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p09-kitchen-single-link-candidate-r09/candidates/tests/business/test_required_kitchen.gd", + "sha256": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", + "scope": "exact unexecuted r09 source marker declaration only; no actual count or functional PASS inferred" + }, + "source_marker_lines": [ + { + "line": 481, + "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/environment/test_cleaning_access.gd", + "template": "Cleaning access: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning access: 6 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1382eab9d01dffaa396cb7fb71902538ee649a963ac9e1fd099ea7a147669884", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\tprint(\"Cleaning access: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_facility_cleanliness.gd", + "template": "Facility cleanliness: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility cleanliness: 49 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6dba789489d3e7da8cbd5056f76d7a5eb97c454b087787ccda926682c328a5f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"Facility cleanliness: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_runtime_cleanliness.gd", + "template": "Runtime cleanliness: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime cleanliness: 29 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3b5fe92855f17f1551de76fe1570dbf92c697b769721bb87582bc4d4d06e36b4", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 58, + "text": "\texpect(runtime.restore_snapshot(intact), \"restore current dirt before rollback checks\")" + }, + { + "line": 75, + "text": "\tprint(\"Runtime cleanliness: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_runtime_weather.gd", + "template": "Runtime weather: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime weather: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "047bb23573ef6a4c7e2483e3a2bfd244a3e2061fbf65c33fa08a52952a2655a0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 52, + "text": "\tprint(\"Runtime weather: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_weather_recovery.gd", + "template": "Weather recovery: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weather recovery: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d71bf3fbf1fa1d43eae2c38161a44b18fc6cc34477a1ab9dd12fca54e4ee9089", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Weather recovery: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_weather_timeline.gd", + "template": "Weather timeline: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weather timeline: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "212dab5036f90ae2eaf1b75e0c0adaea4a572cdbfb31b07d0ef4336def4f4791", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\tprint(\"Weather timeline: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/mvp/test_amelia_donations.gd", + "template": "Chapel gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel gameplay: 38 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1bd622a3d88374f01e3d8885a84a816572ed296928f6ce7e3f8134ab15f60793", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/mvp/test_anri_floor_pricing.gd", + "template": "Anri floor pricing: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Anri floor pricing: 48 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "420cd0b14c0a4921ab3a45e612773f8afd38a31b566d1b95942d3e5defc004b1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"Anri floor pricing: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_approved_free_unique_arrears.gd", + "template": "APPROVED_FREE_UNIQUE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "APPROVED_FREE_UNIQUE checks=83 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "411d7836326f2858b68201bc3e8fec2b1d365291342939249cbf5a40a20d6b0b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 208, + "text": "\tprint(\"APPROVED_FREE_UNIQUE checks=%d failures=%d\" % [checks,failures])" + } + ] + }, + { + "test": "tests/mvp/test_chapel_boundaries.gd", + "template": "Chapel boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel boundaries: 46 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a857179fbe5186e27080ef7bae13775edcc821440cd763fd793e03ef7d27ac5c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tprint(\"Chapel boundaries: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_chapel_service.gd", + "template": "Chapel service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel service: 24 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b8631e1095ce7714d2ae581a6ee3d8fe47065184b7dd05e9104a2b7c0cacb1c1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 60, + "text": "\tprint(\"Chapel service: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_fine_cleaning_effect.gd", + "template": "Cleaning gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning gameplay: 95 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7c7df0bac1c072ed4423140e8afe431952ec2a47f76df7dbc24cab62283cae06", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/mvp/test_mvp_content_catalog.gd", + "template": "PASS: MvpContentCatalog ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: MvpContentCatalog (216 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b663b8790d02acfa836c8a6a6609f337c7991b7b91d271d1b60b76d137bbd62b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: MvpContentCatalog (%d checks)\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: MvpContentCatalog (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_operations_coordinator.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 849 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "87499bf5f45a7201cb16082df0ac1031b7b28e9c725950013252d0bb9e5efd6f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 45, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_original_midnight_settlement.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 29 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "916d0b4184842b1c8cc23c1a95db12558a8ba665b251aa6a383a42995eceaa30", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if _failures == 0 else \"FAIL\", _checks])" + } + ] + }, + { + "test": "tests/mvp/test_original_room_zones.gd", + "template": "Original room zones: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original room zones: 40 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6f1b99f2bac0e58eab30fbb061796c88fc93a21e24de7bdcd5ee39a4c02fe8df", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 41, + "text": " print(\"Original room zones: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_pending_service_pricing_report.gd", + "template": "Pending service pricing report: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Pending service pricing report: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6fbde7cb7c8df52625963ebd06ded19a238738d2a8441e516f2079f00c919fc1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 216, + "text": "\tprint(\"Pending service pricing report: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/mvp/test_premium_room.gd", + "template": "Premium room integration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Premium room integration: 15 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ce79ae9632cd17f84ca52961e098d5015921d81abcf57acc72461a16dd44baff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 48, + "text": "\tprint(\"Premium room integration: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_premium_room_guest_flow.gd", + "template": "Premium guest flow: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Premium guest flow: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "68bdba5aee805f782da3aa22bc03d4f051d165b34160151a0afe293b8e8cc8b2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Premium guest flow: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_progression_service.gd", + "template": "PASS: ProgressionService ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: ProgressionService (110 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f97895fceacc42fbbe14f3e77972ef980abf8f63ca9a5c8c8d4003900305b0aa", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: ProgressionService (%d checks)\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: ProgressionService (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_content_progression.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 116 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c8f7a107d024693b863feb2c9cc460383b98cf04e5ed872011bcd7024be68d60", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 41, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_mana_depletion.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 21 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8d94fd8e28b0fc22a699c630b5d68e7afd476a1305c86ecf57858b2f1d49b8ae", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_operations_acceptance.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 46 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a802afdc5786b6690a2c8b81f39eefda9c04624826d5cb90c587cf84190feea3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_operations_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 51 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d31a891906d539d9f4db1ca6c0d327956d3634aa2d89baffa08fc6c8bede67b9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_upkeep.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "eae48823d58d78c419d29728e33a5559fbe5131b49e33d04ca4e6fa3a721c29b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 102, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_staff_unique_contract_report.gd", + "template": "STAFF_UNIQUE_CONTRACT_REPORT checks= failures=", + "required": true, + "historical_marker_for_schema_only": "STAFF_UNIQUE_CONTRACT_REPORT checks=34 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93d7b00d3b27a85246dad25337a25c0036d562390e584854fe89c958ada76907", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 69, + "text": "\texpect(runtime.submit_command(&\"set_staff_work_assignment\", {&\"staff_id\": &\"heroine_anri\", &\"facility_id\": workplace.instance_id, &\"template_id\": &\"NIGHT\"}).is_success(), \"normal assignment uses existing path/role/capacity checks\")" + }, + { + "line": 116, + "text": "\tprint(\"STAFF_UNIQUE_CONTRACT_REPORT checks=%d failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/mvp/test_unique_character_validation_policy.gd", + "template": "PASS: UniqueCharacterValidationPolicy ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: UniqueCharacterValidationPolicy (108 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2b4c70506e4436e5afe4bf987ff9bba765a02394a4de86e2ed0af6ba5b68c766", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\tprint(\"PASS: UniqueCharacterValidationPolicy (%d checks)\" % checks)" + }, + { + "line": 61, + "text": "\t\tpush_error(\"FAIL: UniqueCharacterValidationPolicy (%d of %d checks failed)\" % [failures, checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_construction_boundaries.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 17 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "266c4f8f240d7508c6c282b08745c1cd813caae384fd46e1ab88f170944ab55e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 45, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_legacy_midnight.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 23 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "48009da7074b78929d9259ef4ac8baabcb192e7a337742df0d999442270d6e89", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 78, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_loss_streak.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 15 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b90e6ad96b4a0989b298a62d11c349fcfac53a65cb532f206d8e7ff2223190dc", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 49, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/performance/test_digest_performance.gd", + "template": "Digest performance: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Digest performance: 83 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Digest performance: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "known_unicode_diagnostic": { + "exact_line": "Unicode parsing error, some characters were replaced with � (U+FFFD): Unexpected NUL character", + "source_sha256": "363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f", + "source_lines": [ + 250, + 251, + 252, + 253, + 254, + 255, + 256 + ], + "count_is_observed_not_fixed": true, + "scope": "deliberate UTF8/String.chr(0) digest boundary fixture only; not general error allowance" + } + }, + { + "test": "tests/performance/test_facility_reachability.gd", + "template": "Facility reachability: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility reachability: 33 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f11e7da7f2ac2eb8f80175eda65a285403e18f5d42c3c79c99c1aec9f128eb01", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 51, + "text": "\tprint(\"Facility reachability: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_lazy_digest.gd", + "template": "Lazy digest: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Lazy digest: 11 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b1d62d6c83abd514e462ea737baab113c52e76c3b76f7a0672d4a4a119617846", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Lazy digest: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_mvp_manifest_cache.gd", + "template": "MANIFEST_CACHE_CORRECTNESS checks= failures= candidate=true", + "required": true, + "historical_marker_for_schema_only": "MANIFEST_CACHE_CORRECTNESS checks=79 failures=0 candidate=true\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bcf1668f379344447b72483b6923c2eb59a2fdc29237a6cc817a31d3834d2e52", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 221, + "text": "\tprint(\"MANIFEST_CACHE_CORRECTNESS checks=%d failures=%d candidate=%s\" % [checks, failures, cached])" + } + ] + }, + { + "test": "tests/performance/test_path_membership.gd", + "template": "Path membership: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path membership: 8 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4a0eee95ee39a92eb9f8ec345d46d45f7349e3428454a22bf867b588ecc5bfa6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Path membership: 8 checks, %d failures\" % failures)" + } + ] + }, + { + "test": "tests/performance/test_perf_mvp_001.gd", + "template": " checks= passed= failed= skipped=", + "required": true, + "historical_marker_for_schema_only": " checks=24 passed=24 failed=0 skipped=0", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1a4bc20ea4810924e8e64bc5fec1b44fc327f1676200e978a77577d628675e5b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 62, + "text": "\t\t\" checks=%d passed=%d failed=%d skipped=%d\"" + } + ], + "json_report": { + "prefix": "PERF_MVP_001_REPORT=", + "check_summary_matches_primary": true, + "smoke_is_not_P28_maximum_performance_acceptance": true + } + }, + { + "test": "tests/performance/test_snapshot_ownership.gd", + "template": "Snapshot ownership: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Snapshot ownership: 89 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2fbcd50c490babecd43fb1611d9b3dec4134b41c80deef795835de38e6af241b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\tprint(\"Snapshot ownership: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_system_optimization.gd", + "template": "PASS: checks; failures=", + "required": true, + "historical_marker_for_schema_only": "PASS: 282 checks; failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "374ff4b3ba78b7a724a3620d683db3835fd2a545f1de11606fd4969a3c6ede95", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 83, + "text": "\tprint(\"PASS: %d checks; failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/performance/test_world_snapshot_cache.gd", + "template": "World snapshot cache: checks, failures", + "required": true, + "historical_marker_for_schema_only": "World snapshot cache: 9 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "46f25630b30c9cc0f7cddb051a42a0c3edc486f3b81cbfab94d8d2cd0e989935", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 37, + "text": " print(\"World snapshot cache: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/run_tests.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 73 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4c8ef388e2349ff975a2af7260da0aa02231a6036ea71b4000f2bb0e6f4083d5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 10, + "text": "\tprint(\"The Tree: automated checks\")" + }, + { + "line": 17, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 19, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_command_snapshot_key_order.gd", + "template": "Command Queue snapshot key-order regression: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Command Queue snapshot key-order regression: 1228 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5803a593b0d9dbb35248d79a2d94b5a61668f5bea7687b2cb75366c8b1b33a2b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 137, + "text": "\tprint(\"Command Queue snapshot key-order regression: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s0/test_command_transaction.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1f1559646a38940db027ee83b2622f317e9b9c6c6f7d4b86f41f866eabb7e75d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_definition_registry.gd", + "template": "PASS: DefinitionRegistry ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: DefinitionRegistry (312 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bf355805e007d99addfb9b963d3566a84602cab40bdb1805ccf3d4e8d851ae17", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: DefinitionRegistry (%d checks)\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: DefinitionRegistry (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_determinism.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 15 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a397d2b33a2c8abfae76350d8ee65b967bbc1625186a865dffd776b421cc7520", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_domain_events.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 33 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cfb0c5678f9b88ce8594ad06cd122276faa2c7f1287cd204de61c1f777508fbe", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 16, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 18, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_runtime_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 21 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "79c59678394ca98d3bc4de25e581eb2a888df1ded5ac9d5363cb4476e875f90e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_stable_id_generator.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 8 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2fac546b4788650b69832b2336d9d46f7b8b343b660d687256d98a64ef4b823c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_construction_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 105 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ce63074b84cb75d710eb3650701a9cb2acf920ff607e41be645904fc2395e2c3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 196, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 198, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_dimension_batch_save.gd", + "template": "Dimension batch save: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dimension batch save: 23 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ca397c0457996bab637e8be5217b7db2c14579a1b0df54434b4000a69e793bbf", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 56, + "text": "\truntime.free();clock.free();state.free();print(\"Dimension batch save: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_excavation_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 58 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "64dacd14dbe0bc94368879daa130e4861ff43cc63fac6bb02e4e64091ee9a750", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 77, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 79, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_facility_infrastructure_projector.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 192 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "060fc9ab24d123ba91782652e6c9d7ba61c140a5f418854a8a28e07fa0adea8c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 104, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 106, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_facility_resize_preview.gd", + "template": "Facility resize preview: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility resize preview: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "46339afa52739c6ab626d78ddd298e06f71366ca886954c852549658ff91bc85", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 58, + "text": "\tprint(\"Facility resize preview: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_original_facility_dimensions.gd", + "template": "Original facility dimensions: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original facility dimensions: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b56a8bf2bf0d6bea3bb2c45148e60b51de3fd033d1cb997eb71d5ae330a4950b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 46, + "text": "\tprint(\"Original facility dimensions: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_path_congestion.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 127 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "709bb680f04862fd774644b0b425150977f54346ca76f6223663c414e00f33bd", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 34, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_path_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 22 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0f48bb44c27e32f08477b9aed61a0db5c8808219490928f734c3570a07efb251", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_path_snapshot_transit_guard.gd", + "template": "Path snapshot transit guard: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path snapshot transit guard: 35 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b41d3e1ad3a4cd92bd865ba482c9e6a3d23fbd0ad276346b97c5235f2632f3af", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 47, + "text": "\tprint(\"Path snapshot transit guard: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s1/test_route_subslot_path_adapter.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 184 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a8f712e03776f91899f3311dc3d131d8370faa84032e522092dcc243683487b3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 68, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 70, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_runtime_infrastructure_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 180 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "aaca043097fb31a79e77c77cce1d12e34e5d8630e0ad7337af0d6860f4d45473", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 90, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 92, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 126, + "text": "\t_expect(spatial.validation_calls == 1 and adapter.validation_calls == 1, \"quarantine retains independent dependency checks\")" + } + ] + }, + { + "test": "tests/s1/test_runtime_world_construction.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 37 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "65924a9b69cd27b5923c5464bd44ab839b26fe39a51b3580e691805d5a9e0e5b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_spatial_subslot_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 112 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "06f4f69438a280ef942bbefdb9caaef1ba59e7dfd0eab71469ca29d090a8ad58", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_weighted_resize_runtime.gd", + "template": "Weighted resize Runtime: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weighted resize Runtime: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b6d31d5b27b51eae9be36e2b0195e78f5e3f1105af01660b0c5cf13cd233baff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 188, + "text": "\tprint(\"Weighted resize Runtime: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_world_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 60 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a5e165886f161b8bc71722ac7facdd17704bc6274c88dfe7fd2229f7f2b3f2b7", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_economy_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 45 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cfe6196c0da9e39d95ae0e39f0c5404e321f4fa8be3602b58d4bf6cd3fef3969", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_pricing.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 91 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e658a7421aaef08c1c6f9a1bce42cb819ae39ee8ec0da72547b42400688c1a98", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 61 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e5a0ceef45d2fcae110f777ddb3c678374b7c9110cd40f743f88c578f0ed2d34", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_upkeep_accrual.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 38 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2985f565a3e39605965112a2bc17688315e3c0d527dd283913f9416ff0cf0ef3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_upkeep_settlement.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 51 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5a9666e8e044dbe803c33b41cc7cbb344796703665fa9f7b7a7b465020ec9aa9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\texpect(loaded.restore_snapshot(service.create_snapshot(), loaded_economy), \"receipts restore with ledger crosschecks\")" + }, + { + "line": 127, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_inventory_logistics.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 65 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ef8b87e721f57210da02b8cd0822f58e1a4c1cdaa19fe56301a03a87891e73d6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 30, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 32, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 434, + "text": "\t_expect(guard.restore_snapshot(snapshot), \"strict snapshot fixture restores before corruption checks\")" + } + ] + }, + { + "test": "tests/s2/test_kitchen_production.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 141 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e2aeb1fdbc8d1dfe9400646693a0e97919b3b86cb74a4f000ebeba01bfbee626", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 44, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_ledger_backed_resources.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 18 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "26c2c9b883ee1448345d5555ac3e97293c3bf273b42668c11a6bdc23e1184dd3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 63, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 65, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_network_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 37 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b0a21ea663667979c109cf6fd2efff717c284398071948c897b9e99090ce12fc", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_power_projection.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 14 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e3020534b084b57a8b773967931fd2be34a1de878b4940c8805b99592bbbc46f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_reserve_balance.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 33 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "07fe5f10e665cac3c8aa1b12e0c71878ffccc6d4b409250b317135345e3c8a05", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_original_facility_parameters.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 29 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3d5cc5f13f986f8cea9500a7f8d48d8f5803b8d0bee5eb0e7c44b3218a2e3d7b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\texpect(a.operation_state == FacilityInstance.OperationState.INACTIVE and not a.related_facility_operating, \"nested dependency checks actual operation\")" + }, + { + "line": 71, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_runtime_operations_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 58 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b7c07beb887b268213a3ba484d1a4f65c542a2dc06c64c78b4f806eaeeed997b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_service_start_slots.gd", + "template": "Service start slots: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Service start slots: 25 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "286786b9a7c2c86b8fc7ce808abd84dae05f2eccf7e2b3faa3780746ce4e2133", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\tprint(\"Service start slots: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s3/test_guest_observation_restore_order.gd", + "template": "Guest observation restore-order diagnostic: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest observation restore-order diagnostic: 52 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7abf5692a07edf8270fa9bf4d9ee89a3835741c208c4c670dc605158532284f2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 86, + "text": "\tprint(\"Guest observation restore-order diagnostic: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s3/test_guest_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 137 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0d10b68512685d2ed7d5c0c858952009333e0e47f348ace4b4ccdd0b21d23647", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 35, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 37, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_actor_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cbad1505c735f3dab4596f7ad823d6501faf0cea4b9b91cc2b6ff0422296a2de", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_actor_hardening.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 74 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1cdb1706646e70d99ac77530416ca5681582fc6e76ced455023bf5b674efffa5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 338, + "text": "\t_expect(_runtime.restore_snapshot(valid_snapshot), \"valid hardening snapshot restores before corruption checks\")" + } + ] + }, + { + "test": "tests/s3/test_runtime_path_traffic.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 91 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "92cdada10177363ac5ebae6435d0b4c6f0c7d96e6ccc2d0731487bf4f2cdceeb", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 61, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_staff_assignment.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 104 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f6004ab1601e2910268dd5d02923c32eb697fb587ad74da1f06ca4de6c21713c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d / %d checks\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_staff_payroll_operations.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 88 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "05c817b7e891ac1649072ad33102561e26280b9bdd34f6f3831c7da055044f8c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_staff_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 209 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "82e9a5de3247251ac29b52aa524fffeb4e63a62a9ab587d62004c268a0180481", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 40, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 42, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_placement_risk_preview_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 115 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "165eddf9a5c51d8d9eeb85ea89e21b1ab6067b7ee057ee0fe5b189e97a71c1ad", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_relationship_event_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 67 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b65afa74a67985059cc9e8e19684405324cfcdde277a21d7eba6062c968b24c5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 33, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 35, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_runtime_relationship_events.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 73 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "52c9df100bd85fc49b62b80d214f84750c907330255a909a83a8dbd8195aac39", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_authority_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 61 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "983ac275dc4f40f7c8058831b7239f1f4b4e2b3f12da0bbb6ea5e8ad69afe2c5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_defense_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 507 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6e97ace03c46091775afb5a772adc32a7add8146f36c3492e99f4ae30f6ea62b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 38, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 40, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_authority_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 65 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "33952b6253c77410c7997ddbf37e039f79bcc0ae283da76c3580ec166f928273", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 266, + "text": "\t).is_success(), \"inspector checks in through Runtime Command\")" + } + ] + }, + { + "test": "tests/s5/test_runtime_defense_autonomy.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 188 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "28ca81ddde4da49442b427ed4c55ddcf213ecb389712db585b64cd416636c403", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_defense_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 162 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a909e2af73aae43590cdd91d07ed2548f4fd2f675d26fa63aa74f6a5cda34357", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_event_defense.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 186 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "09bd125d85714ed69f7b35b900507136a099375a10ebb1dae0ab55886387b046", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 19, + "text": "\tif _failures == 0: print(\"PASS: %d checks\" % _checks)" + }, + { + "line": 20, + "text": "\telse: push_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s6/test_performance_budget.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 24 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1ae5490637d73b519bf9ed0e00bda0e034d5ea0e589493bcf8886e4d1ff63689", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 74, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 76, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s6/test_runtime_save_lifecycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 96 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "de94d588d6ce204aa8d0221c2989b8dd900cf4e36ca51f83b464518a382a887f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 33, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 35, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 220, + "text": "\t\t\"checksum-valid but ledger-invalid formal package is preserved as a package\"" + }, + { + "line": 285, + "text": "\t\t\"multiple domain-invalid formal generations remain checksum-valid recovery candidates\"" + }, + { + "line": 348, + "text": "\t\t\"all-invalid fixture exposes three checksum-valid but domain-invalid candidates\"" + }, + { + "line": 465, + "text": "\t_expect(SaveService.save_snapshot(path, snapshot)[&\"ok\"], \"checksum-valid orphan repair fixture writes as a package\")" + }, + { + "line": 565, + "text": "\t_expect(SaveService.save_snapshot(path, snapshot)[&\"ok\"], \"%s fixture is checksum-valid\" % label)" + } + ] + }, + { + "test": "tests/s6/test_runtime_snapshot_serialization.gd", + "template": "Runtime snapshot serialization: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime snapshot serialization: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d9106f9defb6cc195b969ff040d24f1f8ceebe055ba66c39d4634a68620cde84", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 120, + "text": "\tprint(\"Runtime snapshot serialization: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s6/test_save_operations.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 127 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "199df0b7fcc3d212cd32c79e0108dd9e5b83f9895f07553b325571c7674ed96c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 29, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 31, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 124, + "text": "\tvar contract_keys := [\"format_version\", \"game_version\", \"save_id\", \"display_name\", \"created_at_real\", \"updated_at_real\", \"absolute_minute\", \"day\", \"seed\", \"checksum_world\", \"content_pack_ids\", \"enabled_filter_profile\"]" + }, + { + "line": 165, + "text": "\t\t_expect(loaded[&\"ok\"], \"autosave generation %d remains checksum-valid\" % generation)" + }, + { + "line": 326, + "text": "\t_expect(formal_corrupt[&\"ok\"] and formal_corrupt[&\"selected\"][&\"id\"] == &\"backup\", \"checksum failure in formal save selects the verified formal backup\")" + }, + { + "line": 327, + "text": "\t_expect(formal_corrupt[&\"candidates\"][0][&\"code\"] == &\"CHECKSUM_MISMATCH\", \"corrupt formal world is reported as checksum mismatch\")" + }, + { + "line": 411, + "text": "\t_expect(migration_backup_info[&\"id\"] == &\"pre_migration_backup\" and migration_backup_info[&\"package_valid\"] and migration_backup_info[&\"requires_migration\"], \"recovery inventory retains a checksum-valid legacy migration backup without treating it as runtime-loadable\")" + }, + { + "line": 497, + "text": "\tmanifest[\"checksum_world\"] = _sha256(compressed_world)" + } + ] + }, + { + "test": "tests/s6/test_save_preflight.gd", + "template": "Save preflight: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Save preflight: 128 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "49e24247649839b6f50d19b2284368efb7f8f8b189c5010491ad428e97c1c8b9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\tprint(\"Save preflight: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 60, + "text": "\tfor key: String in [\"format_version\",\"uncompressed_size\",\"checksum_world\"]:" + }, + { + "line": 64, + "text": "\t\tif key == \"checksum_world\": values = [true,123,null,\"bad_hash\"]" + }, + { + "line": 116, + "text": "\t_write_bytes(world_path,corrupt); _assert_read_rejection(path,&\"CHECKSUM_MISMATCH\",\"corrupt world checksum\")" + } + ] + }, + { + "test": "tests/s6/test_save_recovery_preservation.gd", + "template": "Recovery read-only candidate: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Recovery read-only candidate: 234 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0fc0f75e06d4371f049dc4692e9ab0d871016b633e5ac4693661a33a7390641e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 19, + "text": "\tprint(\"Recovery read-only candidate: %d checks, %d failures\" % [_checks,_failures])" + } + ] + }, + { + "test": "tests/staff/test_dormitory_dimension_migration.gd", + "template": "Dormitory dimension migration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dormitory dimension migration: 25 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "90737c91005975601f400fe88c9a474d10713639b73d8e1354f99bc52e812a49", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 65, + "text": "\truntime.free();clock.free();state.free();print(\"Dormitory dimension migration: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_dormitory_shift_cycle.gd", + "template": "Dormitory shift cycle: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dormitory shift cycle: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ada3c0177f00648137d02450fce5f33da0a644dae9ec861ac51648770dafebda", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 54, + "text": "\tprint(\"Dormitory shift cycle: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_rescue_elevator.gd", + "template": "Elevator rescue: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator rescue: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c32cce5010eb0167c261b767cac1371baa3ca731368de47ff878a5852ed9f840", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Elevator rescue: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_shift_rush.gd", + "template": "Shift rush: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Shift rush: 42 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "af3d47d848c1220741992612e57736df4af05969d424b79829d9977c774e8149", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"Shift rush: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_commute.gd", + "template": "Staff commute: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff commute: 85 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0837c92a49f8f83c03ca820eaeb23f7b1dfbc1072f8eeffcb557bb6b57b20c49", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 77, + "text": "\tprint(\"Staff commute: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/staff/test_staff_dormitory.gd", + "template": "Staff dormitory: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff dormitory: 18 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3159c45db121e9f76ce6f8e6748a26201ecfe5720b0f5c9d0b568fb82101ef8f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 56, + "text": "\tprint(\"Staff dormitory: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_rescue.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 46 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8d8c02444c35b2c41c6db42ac0195dd614835541235cd92641ca9b220f59c44c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 92, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_rest.gd", + "template": "Staff rest: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff rest: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fe9b9d2a8a9051850980a393b8c06b8be41de8a82d5efc998c69df3a06cf54ec", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 50, + "text": "\tprint(\"Staff rest: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_task_work_clock.gd", + "template": "Staff task clock: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff task clock: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b131fbe922e744c2075338638e3a01811618e93ae786df3ee435318048d24f9b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 31, + "text": "\tprint(\"Staff task clock: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/supply/test_mimi_purchase_quote.gd", + "template": "Mimi purchase quote: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/candidates/tests/supply/test_mimi_purchase_quote.gd", + "sha256": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "bytes": 34683, + "scope": "exact r06 source marker declaration only; no native check count or PASS inferred" + }, + "source_marker_lines": [ + { + "line": 461, + "text": "\tprint(\"Mimi purchase quote: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/supply/test_normal_auto_purchase_chain.gd", + "template": "Normal auto purchase chain: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Normal auto purchase chain: 117 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e9809dfc465576125b1ee09a8c86a966e13cf383e8c47797c5b2919eb2bf8a9b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 96, + "text": "\tvar evidence := {\"checks\": _checks, \"failures\": _failures, \"meal_sales\": _sales(&\"cafeteria\"), \"lodging_sales\": _sales(&\"standard_room\"), \"raw_deliveries\": _completed_deliveries(SupplyCatalog.RAW_FOOD), \"meal_deliveries\": _completed_deliveries(SupplyCatalog.BASIC_MEAL), \"cash\": _state.get_resource(&\"gold\"), \"closes\": _runtime.operations_coordinator.get_day_summaries(), \"supply\": _runtime.get_supply_management_report(), \"upkeep\": _runtime.upkeep_settlement.get_report(), \"maintenance_expense\": _runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE)}" + }, + { + "line": 229, + "text": "\tprint(\"Normal auto purchase chain: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 262, + "text": "\tprint(\"SUPPLY_PREVIEW_READY checks=%d failures=0\" % _checks)" + } + ] + }, + { + "test": "tests/supply/test_normal_supply_chain.gd", + "template": "Normal supply chain: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Normal supply chain: 49 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a27646d207c67be075c5249d8448f38e9bb57586a6ad78ec3c89e96d83b2dc1d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tvar evidence := {\"checks\": _checks, \"failures\": _failures, \"meal_sales\": _sales(&\"cafeteria\"), \"lodging_sales\": _sales(&\"standard_room\"), \"raw_deliveries\": _completed_deliveries(SupplyCatalog.RAW_FOOD), \"meal_deliveries\": _completed_deliveries(SupplyCatalog.BASIC_MEAL), \"cash\": _state.get_resource(&\"gold\"), \"closes\": _runtime.operations_coordinator.get_day_summaries(), \"supply\": _runtime.get_supply_management_report(), \"upkeep\": _runtime.upkeep_settlement.get_report(), \"maintenance_expense\": _runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE)}" + }, + { + "line": 185, + "text": "\tprint(\"Normal supply chain: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 218, + "text": "\tprint(\"SUPPLY_PREVIEW_READY checks=%d failures=0\" % _checks)" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase.gd", + "template": "SUPPLY_AUTO_PURCHASE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "SUPPLY_AUTO_PURCHASE checks=175 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2551b145d583e1b63b0731290ef097603aae302347ea306770f53f750237ddfe", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 364, + "text": "\tprint(\"SUPPLY_AUTO_PURCHASE checks=%d failures=%d\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase_boundaries.gd", + "template": "Supply auto purchase boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply auto purchase boundaries: 120 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cc1a9e8fda5fc25f9fcfb391463a05957fd2ec649ae50acf2b219c3c9efaa970", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 65, + "text": "\t\tfile.store_string(JSON.stringify({\"checks\": checks, \"failures\": failures, \"actual_user_data_dir\": OS.get_user_data_dir(), \"actual_resource_dir\": ProjectSettings.globalize_path(\"res://\"), \"actual_profile\": profile, \"output\": output, \"external_evidence\": external_evidence, \"observations\": observations, \"normal_business_acceptance\": false, \"p05_full_acceptance\": false}, \"\\t\", true, true) + \"\\n\")" + }, + { + "line": 67, + "text": "\tprint(\"Supply auto purchase boundaries: %d checks, %d failures\" % [checks, failures])" + }, + { + "line": 113, + "text": "\tobservations.append({\"boundary\": label, \"report\": report, \"before_digest\": SimulationDigest.compute(before), \"after_digest\": SimulationDigest.compute(after), \"whole_bytes_equal\": var_to_bytes(before) == var_to_bytes(after), \"checks_so_far\": checks, \"failures_so_far\": failures})" + }, + { + "line": 224, + "text": "\texpect(runtime.staff_service.get_staff(PORTER).state == StaffRuntime.State.AVAILABLE, \"porter physically checks in\")" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase_destruction.gd", + "template": "Supply auto purchase destruction: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply auto purchase destruction: 65 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "229c24e67bc6e59146f91e9ae111794ad39a4d9245c84aa8731b65c43c33a6f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 85, + "text": "\tsummary[\"checks\"] = checks" + }, + { + "line": 96, + "text": "\tprint(\"P05_AUTO_DESTRUCTION phase=%s checks=%d failures=%d\" % [mode, checks, failures])" + }, + { + "line": 97, + "text": "\tprint(\"Supply auto purchase destruction: %d checks, %d failures\" % [checks, failures])" + } + ], + "auxiliary": [ + { + "template": "P05_AUTO_DESTRUCTION phase=standard checks= failures=", + "required": true, + "equal_primary_check_count": true + } + ] + }, + { + "test": "tests/supply/test_supply_logistics_bridge.gd", + "template": "PASS: checks / failures", + "required": true, + "historical_marker_for_schema_only": "PASS: 121 checks / 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2d6ed12048b9f17e2f44208bb386d9e06eefa7962e21507fc8e63500aa66052e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t_expect(_worker().state == StaffRuntime.State.AVAILABLE and StaffCommuteService.is_at_facility(_worker(), _runtime.facility_service.get_instance(_source)), \"porter checks in physically before taking a supply job\")" + }, + { + "line": 361, + "text": "\tprint(\"%s: %d checks / %d failures\" % [\"PASS\" if _failures == 0 else \"FAIL\", _checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_management_service.gd", + "template": "Supply management service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply management service: 84 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "de70213e9a07cb461614114e55d3b5371249a36250222a6eb48654103faea3ce", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 336, + "text": "\tprint(\"Supply management service: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_save_migration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 20 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a281937594be9f177103e83d0cba48d54f2d69c7303c937d0303b5f8c787f0ae", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 84, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 86, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/transport/test_elevator_service.gd", + "template": "Elevator service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator service: 123 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e70de48a8e6f7928690d0495110451c26d9dec180c2dd7033c793155f099c2f6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\tprint(\"Elevator service: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_stairs.gd", + "template": "Runtime stairs: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime stairs: 67 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "72f368006ddecd26c3b29f3bcf622e89b8becee337c071d2f246d2b583617bda", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 82, + "text": "\tprint(\"Runtime stairs: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_transport.gd", + "template": "Runtime transport: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime transport: 43 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d9530f391e124d80cecec70c3f40e791298bea9314a065adc7a2b9698eb49d01", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 86, + "text": "\t\tprint(\"Runtime transport: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 190, + "text": "\tprint(\"Runtime transport: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_transport_comparison.gd", + "template": "Runtime transport comparison: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime transport comparison: 51 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 70, + "text": "\t\t&\"improvement_accepted\": _is_improved(before, after), &\"checks\": _checks, &\"failures\": _failures}" + }, + { + "line": 336, + "text": "\tprint(\"Runtime transport comparison: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_boundaries.gd", + "template": "Transport boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport boundaries: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1a5a647c45ffab674a8f58b8bc72692c5a30090c9b99a1e6f67194bbe3586b34", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 96, + "text": "\tprint(\"Transport boundaries: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_comparison.gd", + "template": "Path and cabin comparison: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path and cabin comparison: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7e80e9ef80500278c56f0d1ada5c911f43e3c0a9bc370045fdbebd33c2f02fd5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\t&\"improvement_accepted\": _is_improved(before, after), &\"checks\": _checks, &\"failures\": _failures}" + }, + { + "line": 270, + "text": "\tprint(\"Path and cabin comparison: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_density_migration.gd", + "template": "Transport density migration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport density migration: 36 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0860fdd20576ad2044d44a99d8533393bf95f4b4c69997cc50f2391299343f54", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 106, + "text": "\tprint(\"Transport density migration: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_save.gd", + "template": "Transport save: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport save: 29 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d4b41a71f2ddc43b1beb14ba8548541ce4875b6640815440e0c14a183c8ddfea", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 93, + "text": "\tprint(\"Transport save: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_actor_selection.gd", + "template": "Actor selection: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Actor selection: 100 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ecc08464c20990f0a4bd5d1e5d8d6fceb6f3636c959790842a08539fc54ad911", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 183, + "text": "\tprint(\"Actor selection: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_anri_recruitment.gd", + "template": "Anri recruitment UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Anri recruitment UI: 22 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 110, + "text": "\tprint(\"Anri recruitment UI: %d checks, %d failures\" % [checks,failures]); quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_approved_free_unique_arrears_ui.gd", + "template": "APPROVED_FREE_UNIQUE_UI phase=write checks= failures=", + "required": true, + "historical_marker_for_schema_only": "APPROVED_FREE_UNIQUE_UI phase=write checks=29 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b713a7591c1d9d871c4301159a0333154ffe622373f1807e6f13a0cf0a3cbb3c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 163, + "text": "\tprint(\"APPROVED_FREE_UNIQUE_UI phase=%s checks=%d failures=%d\" % [\"read\" if \"--read\" in OS.get_cmdline_user_args() else \"write\",checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_business_management.gd", + "template": "Business management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business management UI: 72 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7ba2445ca0f9591fbc2ca21b3eab3043283f982b94c485c32832493af5989da6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 298, + "text": "\tprint(\"Business management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_chapel_gameplay.gd", + "template": "Chapel gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel gameplay: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ae56f5b96d89c7fde3fca3c8fab42fc612ad2d4e066a226fcaa784b96d075b1f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 85, + "text": "\tprint(\"Chapel gameplay: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_cleaning_gameplay.gd", + "template": "Cleaning gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning gameplay: 90 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "781a7386b2b70cae0d4a6a3d17b7540d8a32655368a2c03268e7e6427d85af82", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 129, + "text": "\tprint(\"Cleaning gameplay: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_dashboard_coalescing.gd", + "template": "Dashboard coalescing: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dashboard coalescing: 10 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93899d08d0b14d72cbbac732bda90aff76bdc54da23acd97ec9ac371fe45fb7c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 40, + "text": "\tprint(\"Dashboard coalescing: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_defense_management.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 105 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4511eb00a0539af9fb31302269431e3722988d900996085f9c7b10f5dba86035", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 184, + "text": "\t\t_expect(root.get_texture().get_image().save_png(capture) == OK, \"capture actual defense popup after source-label and saved-selection checks\")" + }, + { + "line": 202, + "text": "\tif _failures == 0: print(\"PASS: %d checks\" % _checks)" + }, + { + "line": 203, + "text": "\telse: push_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_dimension_migration_ui.gd", + "template": "Dimension migration UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dimension migration UI: 12 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "97320ec735651ef78f17f750d957827a952be24eecf326ec70d7a6f460abbb3e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 54, + "text": "\tprint(\"Dimension migration UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_elevator_management.gd", + "template": "Elevator management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator management UI: 20 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1d7ed05d39f34d91f2ef485f3930de26e7136224f07ff9fec1b768726a365d68", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 94, + "text": "\tprint(\"Elevator management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_first_operating_day.gd", + "template": "First operating day: checks, failures", + "required": true, + "historical_marker_for_schema_only": "First operating day: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f95a04f98bc6d11441e6485c8f8fa809daebb4d26938850e523de557c2827304", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 244, + "text": "\tprint(\"First operating day: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_guest_decision_log.gd", + "template": "Guest decision log: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest decision log: 28 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "79fd5ffce10f86af50e43c32e04837c8c5f8823d5af8c2432beac05b68f10725", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 91, + "text": "\tprint(\"Guest decision log: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_guest_observation.gd", + "template": "Guest observer: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest observer: 22 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "64e265aa819fff4a52460ac9bc3c4bf82b12316e38fc364bb17677a22080432c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 67, + "text": "\tprint(\"Guest observer: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_layer_presentation.gd", + "template": "Layer presentation: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Layer presentation: 27 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2080a64df66c66a28c6d73fc5678a2e8d221e230e6afb38f2550d12bfd6deea7", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"Layer presentation: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_layer_skin.gd", + "template": "Layer skin: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Layer skin: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f4ddf3882ae7a95eeec04a9cca195efd02cd89c2629db3f184df7eb457ac7593", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 66, + "text": "\tprint(\"Layer skin: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_main_audio_feedback.gd", + "template": "Main audio feedback: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6b190284a9d9a7eb32dce08a73bd3ae7aced23f45fd1c654cc1789da5745a15b", + "source_marker_lines": [ + { + "line": 85, + "text": "\tprint(\"Main audio feedback: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_main_audio_feedback.gd", + "sha256": "6b190284a9d9a7eb32dce08a73bd3ae7aced23f45fd1c654cc1789da5745a15b", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_main_dashboard.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 168 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "867c4794be574b31e2739cb7afff92b1e07fbc3de24adf6660798ce7b9ace11d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 209, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 211, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_opening_assignment.gd", + "template": "Opening assignment: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Opening assignment: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "143cbd7ca4589540b5f04693dc7c43df8808b87934844d3dd21d33e34f813fa2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 67, + "text": "\tprint(\"Opening assignment: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_original_build_pointer.gd", + "template": "Original build viewport input: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original build viewport input: 36 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a04a79c5e766659d10c4564d1f11c6554a27cb8af71431295e10bc4967012913", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Original build viewport input: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_original_facility_controls.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 14 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7aed4ad0564e3690f64bc3ab3149e6b36df6ccd8bea0530d1ecd8dd1f87fb576", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 72, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/ui/test_pixel_tower.gd", + "template": "PASS: pixel tower checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 243 pixel tower checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93bdbb195f2ceec07dbe052e0d0a3017d84b56f1e8ec46b87be8d98105ec0f88", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 60, + "text": "\t\tprint(\"PASS: %d pixel tower checks\" % _checks)" + }, + { + "line": 62, + "text": "\t\tpush_error(\"FAIL: %d of %d pixel tower checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_rank_pointer.gd", + "template": "Rank viewport input: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Rank viewport input: 11 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2c51510f80fb57068c6c06aa56566936685e679720b0fd853d8f814566e730f2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 94, + "text": "\tprint(\"Rank viewport input: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_room_presentation.gd", + "template": "PASS: room presentation checks; failures=", + "required": true, + "historical_marker_for_schema_only": "PASS: 279 room presentation checks; failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fb460cfcc82fcd28624910594558f0542847364d0f6da6c0a68c322508e9bee1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 152, + "text": "\tprint(\"PASS: %d room presentation checks; failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_selection_autosave.gd", + "template": "First operating day: checks, failures", + "required": true, + "historical_marker_for_schema_only": "First operating day: 75 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/ui/test_selection_lifecycle.gd", + "template": "Selection lifecycle: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Selection lifecycle: 38 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b1172419e53fbffccb8f6cda046539fee8be1fe0ad711c3aff7f840b73f77895", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 149, + "text": "\tprint(\"Selection lifecycle: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_management.gd", + "template": "Staff management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff management UI: 131 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6bd701cf3ac2386768db34b59d6298bed3f59a3aed0795e0854134051cdcf2e0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 324, + "text": "\tprint(\"Staff management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_rescue.gd", + "template": "Rescue UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Rescue UI: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2ab4a58ef7cce5d1b2e03250dbe92ed6957f38c71e538cdc4f425fa198193bc8", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\tprint(\"Rescue UI: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_rest.gd", + "template": "Staff rest UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff rest UI: 8 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "accfc9c0ee2eed5192085d01d19bbdce65f6171551b22e5a0b564f68c7080eaa", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Staff rest UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_stair_transport_details.gd", + "template": "Stair and transport details UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Stair and transport details UI: 28 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8c5f5d695b5baa430dc4a10c01ff9ffdd4461dc33f60efb237552949683d1357", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 158, + "text": "\tprint(\"Stair and transport details UI: %d checks, %d failures\" % [_checks,_failures])" + } + ] + }, + { + "test": "tests/ui/test_supply_auto_purchase_ui.gd", + "template": "SUPPLY_AUTO_PURCHASE_UI checks= failures=", + "required": true, + "historical_marker_for_schema_only": "SUPPLY_AUTO_PURCHASE_UI checks=28 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a187b5b5badd8d5e1b9539dbb3a7fad9dae64101e97f5b89a87e8bc55ed10132", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 137, + "text": "\tprint(\"SUPPLY_AUTO_PURCHASE_UI checks=%d failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_supply_management.gd", + "template": "Supply management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply management UI: 42 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b5a16eac691f2ab36283729c461b5730c6a66db64ff1b2dc29df0c30308fd9df", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 264, + "text": "\tprint(\"Supply management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_ui_audio_feedback.gd", + "template": "UI audio feedback: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "source_marker_lines": [ + { + "line": 162, + "text": "\tprint(\"UI audio feedback: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_ui_audio_feedback.gd", + "sha256": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_ui_presentation_audio_contract.gd", + "template": "UI presentation audio contract: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "332047b31123715996d6ee55254b36c043ea481b206dd21a418077ad74e05648", + "source_marker_lines": [ + { + "line": 165, + "text": "\tprint(\"UI presentation audio contract: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_ui_presentation_audio_contract.gd", + "sha256": "332047b31123715996d6ee55254b36c043ea481b206dd21a418077ad74e05648", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_upkeep_closed_ui.gd", + "template": "PASS: checks (phase write)", + "required": true, + "historical_marker_for_schema_only": "PASS: 23 checks (phase write)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3127cabc2c04361403160ca0f0def65a3dbb6b37f545fd4ca964e15dc3a055ef", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 134, + "text": "\tprint(\"%s: %d checks (phase %s)\" % [\"PASS\" if failures == 0 else \"FAIL\", checks, \"read\" if \"--read\" in OS.get_cmdline_user_args() else \"write\"])" + } + ] + }, + { + "test": "tests/ui/test_upkeep_inspector.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 6 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bd27b4c44cca6b95c83a8d2e328f8222079b2c36768d26504fdb3fdc8a9739ff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 53, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/ui/test_world_tree_rank.gd", + "template": "World tree rank UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "World tree rank UI: 27 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1f628d0b4f326b021db7d9b34cff9cc3f94a5197edbfb193979a3ce0ac9cf289", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"World tree rank UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/world/test_elevator_topology.gd", + "template": "Elevator topology: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator topology: 93 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4710a6bba103d82a4d2febea1a2c30d029c0f1ccd2a3d9a6c1712b4fb372033b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 38, + "text": "\tprint(\"Elevator topology: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_legacy_stairs.gd", + "template": "Legacy stairs: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Legacy stairs: 85 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "258d759537aec8b7b90cab107985c9c6a16f10e85ae7ae92a9c54cc94417d48e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 37, + "text": "\tprint(\"Legacy stairs: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_path_transit_density.gd", + "template": "Path transit density: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path transit density: 46 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8de5fc8bdf8631ff41fcd4862cea40daa3316040f381f182dc6fe09dba9f3cb9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 15, + "text": "\tprint(\"Path transit density: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_path_vertical_policy.gd", + "template": "Path vertical policy: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path vertical policy: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "140cdf552309f89cd93bf1a4d38827a671a66461203f0858f1c2651a8e6e8473", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 13, + "text": "\tprint(\"Path vertical policy: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_stair_infrastructure.gd", + "template": "Stair infrastructure: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Stair infrastructure: 106 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f6d1a29b43875b7b1fc945611c2ed15fbdcb8bb7220addf283b30fedf2ccba28", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\tprint(\"Stair infrastructure: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_event_log_ui.gd", + "template": "Event log UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Event log UI: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p20-log-limited-native-actual-root-review-r03/review.json", + "bytes": 993, + "sha256": "8eec3a8492aa74f571eb21447c6911cea64d176e0f813152f226b2b4122ba387", + "scope": "successful limited actual UI marker/source schema only; full169 pending" + }, + "source_marker_lines": [ + { + "line": 120, + "text": "\tprint(\"Event log UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_paired_bgm.gd", + "template": "PASS: checks", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fcafe858caef15f99faa9ad8aa7971828e9053adc1357077f0a26f0f17d9bbc0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p22-paired-bgm-playback-main-window-actual-root-review-r01/review.json", + "sha256": "aca77047dc5e5efc1c0c2a6aeeb8e8c34c51e74243ab762f596150194869823b", + "scope": "same PASS completion shape; original63 core assertions excluded from dedicated new BGM suite; actual count from raw only" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 321, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 323, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_tomari_floor_trouble.gd", + "template": "TOMARI_FLOOR_TROUBLE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "TOMARI_FLOOR_TROUBLE checks=64 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "09581fbc32b29d2b5773e0859f22ca58c947927d11e6523027ffe277bd7ac303", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-tomari-floor-trouble-focused-actual-root-review-r07/review.json", + "bytes": 1367, + "sha256": "419bab962014351da3d727e4d9cb51e686410271dac809b526b3668600fc583d", + "scope": "actual focused controlled Runtime/native47 then64check marker; counts remain collected from raw, normal joining not accepted" + }, + "source_marker_lines": [ + { + "line": 135, + "text": "\tprint(\"TOMARI_FLOOR_TROUBLE checks=%d failures=%d\" % [checks,failures])" + } + ] + }, + { + "test": "tests/s1/test_p08_combined_migration.gd", + "template": "P08 combined migration: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "authority": { + "path": "docs/spec/acceptance/P08-02.json", + "scope": "new standalone mixed suite; native execution pending" + } + }, + { + "test": "tests/ui/test_p08_normal_placement_ops.gd", + "template": "P08 normal placement: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + }, + { + "test": "tests/transport/test_runtime_build_preview.gd", + "template": "Runtime build preview: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c881b60bce7276047884303a6823da9abfaaf8c1e85dbc5f2c0ff13f0d742449", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + } + ,{ + "test": "tests/ui/test_p23_life_observation.gd", + "template": "P23 life observation: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8", + "authority": { + "path": "docs/spec/acceptance/P23-03.json", + "scope": "component and read-only observation only; native unexecuted; whole unit blocked" + } + } + ], + "unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting", + "roster_count_is_not_hardcoded": true, + "original155_authority_sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "current164_review_sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "audio3_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "production_changes": [], + "engine_runs": 0, + "mimi_purchase_v2_candidate_binding": { + "existing4_GD_source_changes": { + "autoload/simulation_runtime.gd": { + "base_sha256": "dafee4833aa68fb97c8a8c43efc4aa6b9d29d83e00129bd2b99fd99210946b89", + "candidate_sha256": "8efbe3aafe519d5b67ba96025a3324b9b724e169aa757b2d31262066ab7b83bb" + }, + "scripts/core/save_service.gd": { + "base_sha256": "79a8c3654868c81d3e623a429d8fdfe51f15d098cef8d5b60be091d5200b285d", + "candidate_sha256": "3e3d2aeeff7b7610b48bba24ca83610225f93aa8e8deef50c93186ee360fd8b7" + }, + "scripts/supply/supply_management_service.gd": { + "base_sha256": "bd99c1418537415b574ff936c74817c721071fdcf4b31b1891347a7b6ae84e30", + "candidate_sha256": "e4ad420191020485c027004f64bb00fad9da195fe956ef10dc0c787412018f7f" + }, + "scripts/ui/supply_management_panel.gd": { + "base_sha256": "b3425f797f2e56990bffd64a9770fcfd0e4b487b7420e848b43bb7c44f41bc4d", + "candidate_sha256": "c23c84c5afb2960b51384d80703c3e454059b46eb04e237df52f62291de7db06" + } + }, + "old169_suite_source_SHA_rewritten": false, + "source_schema_scope": "prospective Runtime15/formal2; original v1-only14/formal1/generic/legacy1-13 immutable branches remain required", + "actual_new_marker": null, + "actual_check_count": null, + "actual_new_UID3": null, + "adoption": false, + "source_r06_seal": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/input-seal.json", + "sha256": "7c01c096524d9560fb691f7fb1e0b5a718592b964574f8fe0153beb1ec76a60d", + "bytes": 3818 + } + } +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt new file mode 100644 index 0000000..a3bba86 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt @@ -0,0 +1,3319 @@ +extends Control + +const UIAudioFeedbackScript := preload("res://scripts/ui/ui_audio_feedback.gd") +const UIAudioSettingsDialogScript := preload("res://scripts/ui/ui_audio_settings_dialog.gd") +const UIAudioOwnerShutdownScript := preload("res://scripts/ui/ui_audio_owner_shutdown.gd") +const PairedBGMScript := preload("res://scripts/ui/paired_bgm.gd") +var _paired_bgm: Variant +signal ui_audio_window_close_drained(report: Dictionary) +var audio_settings_path := "user://settings/audio.cfg" +var _audio_feedback: Variant +var _audio_settings_dialog: Variant +var _window_close_in_progress := false + +const RankDialogScript := preload("res://scripts/ui/world_tree_rank_dialog.gd") +const EventLogDialogScript := preload("res://scripts/ui/event_log_dialog.gd") +const ManualSaveSlotsDialogScript := preload("res://scripts/ui/manual_save_slots_dialog.gd") + +const OpeningGuideScript := preload("res://scripts/ui/inn_opening_guide.gd") +const OperatingLedgerPanelScript := preload("res://scripts/ui/operating_ledger_panel.gd") +const StaffManagementPanelScript := preload("res://scripts/ui/staff_management_panel.gd") +const SupplyManagementPanelScript := preload("res://scripts/ui/supply_management_panel.gd") +const BusinessManagementPanelScript := preload("res://scripts/ui/business_management_panel.gd") +const DefenseManagementPanelScript := preload("res://scripts/ui/defense_management_panel.gd") +const WorldPreviewScript := preload("res://scenes/main/world_preview.gd") +const OperationsCameraControlsScript := preload("res://scripts/ui/operations_camera_controls.gd") +const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd") +const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd") +const DefaultVisualTheme := preload("res://assets/themes/japanese_fantasy/visual-theme-v1.tres") +var _last_presented_close_minute := 0 + +const MANUAL_SAVE_PATH := "user://saves/manual_ui" +const MVP_FACILITY_IDS: Array[StringName] = [ + &"cleaning_room", + &"chapel", + &"reception", + &"standard_room", + &"premium_room", + &"cafeteria", + &"warehouse", + &"kitchen", + &"public_bath", + &"staff_waiting_room", + &"staff_dormitory", + &"infirmary", + &"security_station", + &"tavern", + &"massage_room", + &"guest_elevator", + &"service_elevator", + &"guest_stairs", + &"service_stairs", + &"staff_door", + &"guest_corridor", + &"service_corridor", + &"adult_private_room", +] +const FACILITY_STATE_NAMES := [ + "故障", + "到達不能", + "魔力不足", + "人員不足", + "閉鎖中", + "低下運転", + "営業中", +] +const ELEVATOR_PHASE_NAMES := {&"IDLE": "待機", &"PICKUP": "迎車中", &"BOARDING": "乗車中", &"TRAVEL": "運行中", &"ALIGHTING": "降車中"} +const FACILITY_REASON_NAMES := { + &"BROKEN": "故障", + &"DESTROYED": "破壊による休止", + &"RAIDED": "摘発による休止", + &"RELATED_FACILITY_INACTIVE": "関連施設が未稼働", + &"NO_ACCESS": "経路なし", + &"NO_POWER": "魔力供給なし", + &"NO_STAFF": "必要人員不足", + &"CLOSED_BY_OPERATOR": "手動閉鎖", + &"OUTSIDE_OPENING_HOURS": "営業時間外", + &"LOW_DURABILITY": "耐久度低下", +} +const PREVIEW_REASON_NAMES := { + "INVALID_DEFINITION": "施設データが不正です", + "MISSING_DEPENDENCY": "建設システムを利用できません", + "OUT_OF_BOUNDS": "世界の範囲外です", + "ZONE_MISMATCH": "地上・境界・地下の条件が合いません", + "LAYER_MISMATCH": "表層/裏層が合いません", + "OVERLAP": "ほかの施設または工事と重なります", + "UNSUPPORTED": "構造を支えられません", + "INSUFFICIENT_RESOURCES": "建設資源が不足しています", +} +const EXCAVATION_REASON_NAMES := { + &"OUT_OF_BOUNDS": "世界の範囲外です", + &"NOT_UNDERGROUND": "地下のマスだけ掘削できます", + &"ALREADY_EXCAVATED": "すでに掘削済みです", + &"ALREADY_RESERVED": "このマスは掘削中です", + &"NO_EXCAVATED_NEIGHBOR": "掘削済みの隣接マスから延ばしてください", + &"NO_ENTRANCE_CONNECTION": "地上入口までつながる経路がありません", + &"SUPPORT_REQUIRED": "硬い地盤を支える補強施設が必要です", + &"INSUFFICIENT_RESOURCES": "掘削資源が不足しています", + &"MISSING_DEPENDENCY": "掘削システムを利用できません", +} +const EVENT_CHOICE_NAMES := { + &"event.relationship.threshold.acknowledge": "向き合う", + &"event.relationship.threshold.defer": "今は保留する", +} +const STAFF_ROLE_NAMES := { + &"attendant": "接客", + &"bartender": "酒場", + &"clerk": "会計", + &"cleaner": "清掃", + &"cook": "調理", + &"guard": "警備", + &"maintenance": "保守", + &"masseur": "施術", + &"medic": "医療", + &"porter": "物流", + &"receptionist": "受付", &"innkeeper": "女将", &"cleric": "聖職者", +} +const STAFF_SHIFT_NAMES := { + &"day": "日勤", + &"night": "夜勤", + &"any": "柔軟勤務", +} +const STAFF_SKILL_NAMES := { + &"service": "接客", + &"operation": "運営", + &"combat": "戦闘", + &"underworld": "裏社会", +} + +@export var visual_theme: VisualThemeProfile = DefaultVisualTheme +var manual_save_path := MANUAL_SAVE_PATH +var manual_save_slots_dialog: ConfirmationDialog + +var time_label: Label +var phase_label: Label +var rank_button: Button +var rank_dialog: ConfirmationDialog +var event_log_dialog: AcceptDialog +var weather_label: Label +var guest_count_label: Button +var dimension_migration_dialog: ConfirmationDialog +var _dimension_migration_digest := "" +var guest_observer: AcceptDialog +var lodging_count_label: Label +var alert_count_label: Label +var defense_manager: AcceptDialog +var alert_icon_node: TextureRect +var resource_labels: Dictionary = {} +var resource_icon_nodes: Dictionary = {} +var world_preview: WorldPreview +var operations_camera_controls: OperationsCameraControls +var important_warning_cutin: OperationFeedback.ImportantWarningCutinDialog +var front_button: Button +var back_button: Button +var excavation_button: Button +var demolition_button: Button +var demolition_dialog: ConfirmationDialog +var _demolition_mode := false +var _demolition_target: StringName = &"" +var _pending_demolition_commands: Dictionary = {} +var pan_button: Button +var pause_button: Button +var speed_buttons: Dictionary = {} +var dashboard_summary_label: Label +var foreground_event_panel: VBoxContainer +var foreground_event_label: Label +var foreground_event_choices: VBoxContainer +var staff_management_panel: VBoxContainer +var staff_management_toggle_button: Button +var _staff_management_panel_open := false +var staff_candidate_panel: VBoxContainer +var staff_candidate_summary_label: Label +var staff_candidate_toggle_button: Button +var staff_candidate_buttons: Dictionary = {} +var selected_heading_label: Label +var selected_detail_label: Label +var related_facility_picker: OptionButton +var _pending_related_commands: Dictionary = {} +var selected_entity_icon: TextureRect +var selected_elevator_controls: VBoxContainer +var elevator_operation_button: Button +var elevator_operation_hint: Label +var build_status_label: Label +var build_buttons: Dictionary = {} +var navigation_buttons: Dictionary = {} +var _interface_layer := -1 +var opening_guide: Control +var operating_ledger_panel: VBoxContainer +var supply_management_panel: VBoxContainer +var business_management_panel: VBoxContainer +var _business_panel_open := false +var _supply_panel_open := false +var operations_controls: VBoxContainer +var _ledger_panel_open := false +var management_panel: Control +var management_toggle_button: Button +var palette_scroll: ScrollContainer +var palette_toggle_button: Button +var _management_panel_open := false +var _build_palette_open := false + +var _runtime: Variant = null +var _selected_entity_id: StringName = &"" +var _selected_entity_kind: StringName = &"" +var _selected_definition_id: StringName = &"" +var _selected_definition: Variant = null +var _build_rotation := 0 +var _build_sequence := 1 +var _excavation_sequence := 1 +var _hire_sequence := 1 +var _elevator_sequence := 1 +var _pending_elevator_commands: Dictionary = {} +var _pending_build_commands: Dictionary = {} +var _excavation_mode := false +var _pending_excavation_commands: Dictionary = {} +var _pending_hire_commands: Dictionary = {} +var _staff_candidate_panel_open := false +var _operations_start_report: Dictionary = {} +var _subscribed_event_stream: Variant = null + + +func _ready() -> void: + _runtime = get_node_or_null("/root/SimulationRuntime") + _pause_fresh_opening() + if _runtime != null and _runtime.has_method("enable_production_save_policy"): + _runtime.enable_production_save_policy() + if _runtime != null \ + and _runtime.has_method("get_operations_report") \ + and _runtime.has_method("enable_production_operations"): + var current_operations: Dictionary = _runtime.call(&"get_operations_report") + if not bool(current_operations.get(&"enabled", false)): + _operations_start_report = _runtime.call(&"enable_production_operations") + _synchronize_ui_command_state() + _setup_ui_audio() + UIAudioOwnerShutdownScript.register_owner(get_tree(), self) + get_tree().root.close_requested.connect(_on_window_close_requested) + _build_interface() + _connect_signals() + _populate_build_palette() + _populate_staff_candidates() + _on_time_changed(GameClock.day, GameClock.get_hour(), GameClock.get_minute()) + _on_phase_changed(GameClock.get_phase()) + for resource_name: StringName in GameState.resources: + _on_resource_changed(resource_name, GameState.get_resource(resource_name)) + _set_layer(GameState.active_layer) + _update_pause_and_speed() + _refresh_dashboard() + if not _operations_start_report.is_empty() \ + and not bool(_operations_start_report.get(&"ok", false)): + _set_status( + "本番運営を開始できませんでした: %s" % _result_message(_operations_start_report), + false + ) + _focus_initial_view.call_deferred() + + +func _exit_tree() -> void: + _unbind_domain_event_stream() + var tree := get_tree() + if tree != null: + if tree.root.close_requested.is_connected(_on_window_close_requested): + tree.root.close_requested.disconnect(_on_window_close_requested) + UIAudioOwnerShutdownScript.unregister_owner(tree, self) + + +func _on_window_close_requested() -> void: + # The shared coordinator drains every registered Main before one quit. + # Main stays alive while its audio child is freed and the backend releases. + var tree := get_tree() + UIAudioOwnerShutdownScript.close_owners(tree) + + +func _report_audio_close_drained(report: Dictionary) -> void: + ui_audio_window_close_drained.emit(report) + + +func _prepare_owned_audio_close() -> Array[Dictionary]: + _window_close_in_progress = true + set_process(false) + set_process_input(false) + set_process_unhandled_input(false) + set_process_unhandled_key_input(false) + hide() + var audio: Node = _audio_feedback as Node + _audio_feedback = null + var targets := UIAudioOwnerShutdownScript.capture_and_release(audio) + if is_instance_valid(_paired_bgm): + targets.append_array(_paired_bgm.capture_owned_targets()) + _paired_bgm.prepare_owner_shutdown() + _paired_bgm.queue_free() + _paired_bgm = null + return targets + + +func _focus_initial_view() -> void: + await get_tree().process_frame + world_preview.focus_operating_area() + + +func _pause_fresh_opening() -> void: + if _runtime == null or GameClock.absolute_minute != 360: + return + if not (_runtime.get(&"latest_restore_safety_report") as Dictionary).is_empty(): + return + var operations: Dictionary = _runtime.call(&"get_operations_report") + if bool(operations.get(&"enabled", false)): + return + if not _call_array(_service(&"construction_service"), &"get_facilities").is_empty() \ + or not _call_array(_service(&"construction_service"), &"get_jobs").is_empty() \ + or not _call_array(_service(&"staff_service"), &"get_staff_members").is_empty(): + return + var queue: Variant = _runtime.get(&"command_queue") + if queue != null and not queue.get_accepted_command_metadata().is_empty(): + return + GameClock.set_paused(true) + + +func _unhandled_key_input(event: InputEvent) -> void: + if not event.pressed or event.echo: + return + var handled := true + match event.keycode: + KEY_ESCAPE: + _clear_active_tool() + KEY_E: + _toggle_excavation_mode() + KEY_H: + _toggle_staff_candidate_panel() + KEY_B: + _toggle_build_palette() + KEY_I: + _set_management_panel_open(not _management_panel_open) + KEY_SPACE: + _toggle_pause() + KEY_TAB: + _set_layer(1 - world_preview.layer_mode) + KEY_1: + _set_speed(1) + KEY_2: + _set_speed(2) + KEY_3: + _set_speed(3) + KEY_R: + if _selected_definition == null: + handled = false + else: + _rotate_build_preview() + _: + handled = false + if handled: + if _audio_feedback != null: + _audio_feedback.request_click() + get_viewport().set_input_as_handled() + + +func _build_interface() -> void: + var background := ColorRect.new() + background.name = "InterfaceBackground" + background.color = _visual_color(&"window_background", Color("24170f")) + background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + background.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(background) + + var root_margin := MarginContainer.new() + root_margin.add_theme_constant_override("margin_left", 6) + root_margin.add_theme_constant_override("margin_top", 6) + root_margin.add_theme_constant_override("margin_right", 6) + root_margin.add_theme_constant_override("margin_bottom", 6) + root_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(root_margin) + + var layout := VBoxContainer.new() + layout.add_theme_constant_override("separation", 4) + root_margin.add_child(layout) + layout.add_child(_create_top_bar()) + + var content := HBoxContainer.new() + content.name = "MainContent" + content.size_flags_vertical = Control.SIZE_EXPAND_FILL + content.add_theme_constant_override("separation", 4) + layout.add_child(content) + content.add_child(_create_navigation_rail()) + + var map_panel := PanelContainer.new() + map_panel.name = "MapPanel" + map_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + map_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + map_panel.add_theme_stylebox_override( + "panel", + _themed_panel_style(&"panel_outer", _visual_color(&"window_background", Color("1e1712")), _visual_color(&"border_gold", Color("9b6a32")), 8, 5) + ) + content.add_child(map_panel) + world_preview = WorldPreviewScript.new() + world_preview.name = "WorldPreview" + world_preview.custom_minimum_size = Vector2(600, 380) + world_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL + world_preview.size_flags_vertical = Control.SIZE_EXPAND_FILL + world_preview.set_visual_theme(visual_theme) + map_panel.add_child(world_preview) + opening_guide = OpeningGuideScript.new() + world_preview.add_child(opening_guide) + opening_guide.setup(_runtime, GameClock, GameState.active_layer) + opening_guide.position = Vector2(8, 8) + opening_guide.size = Vector2(302, 0) + opening_guide.action_requested.connect(_on_opening_action) + # The inspector overlays the map only when requested, leaving the full + # cross-section available while watching the simulation. + management_panel = _create_side_panel() + world_preview.add_child(management_panel) + management_panel.set_anchors_and_offsets_preset(Control.PRESET_RIGHT_WIDE) + management_panel.offset_left = -320 + management_panel.offset_right = -8 + management_panel.offset_top = 8 + management_panel.offset_bottom = -8 + management_panel.visible = false + + operations_camera_controls = OperationsCameraControlsScript.new() + operations_camera_controls.name = "OperationsCameraControls" + operations_camera_controls.configure(world_preview, self) + world_preview.add_child(operations_camera_controls) + operations_camera_controls.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT) + operations_camera_controls.offset_right = -12 + operations_camera_controls.offset_bottom = -12 + + important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new() + important_warning_cutin.visibility_changed.connect(_update_modal_input_guard) + add_child(important_warning_cutin) + + layout.add_child(_create_bottom_bar()) + + +func _create_top_bar() -> Control: + var panel := PanelContainer.new() + panel.name = "TopBar" + panel.custom_minimum_size.y = 46 + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_wood", _visual_color(&"dark_panel", Color("6b3e1d")), _visual_color(&"border_gold", Color("d5a552")), 8, 8)) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 7) + panel.add_child(row) + + var title_box := VBoxContainer.new() + title_box.add_theme_constant_override("separation", 0) + row.add_child(title_box) + var title := Label.new() + title.text = "THE TREE" + title.custom_minimum_size.x = 98 + title.add_theme_font_size_override("font_size", 17) + title.add_theme_color_override("font_color", Color("fff0c8")) + title_box.add_child(title) + rank_button=Button.new() + rank_button.name="WorldTreeRankButton" + rank_button.text="世界樹ランク 1" + rank_button.flat=true + rank_button.alignment=HORIZONTAL_ALIGNMENT_LEFT + rank_button.add_theme_font_size_override("font_size",10) + rank_button.add_theme_stylebox_override("normal",StyleBoxEmpty.new()) + rank_button.tooltip_text="世界樹のランクと昇格条件を確認" + title_box.add_child(rank_button) + rank_dialog=RankDialogScript.new() + rank_dialog.configure(_runtime) + rank_dialog.configure_audio(Callable(_audio_feedback, "submit_command")) + _audio_feedback.bind_button(rank_dialog.get_ok_button()) + _audio_feedback.bind_button(rank_dialog.get_cancel_button()) + rank_dialog.status_requested.connect(func(message: String, success: bool) -> void: + _set_status(message,success) + _refresh_dashboard() + ) + rank_dialog.visibility_changed.connect(_update_modal_input_guard) + add_child(rank_dialog) + rank_button.pressed.connect(rank_dialog.show_rank) + + row.add_child(VSeparator.new()) + time_label = Label.new() + time_label.custom_minimum_size.x = 104 + time_label.add_theme_font_size_override("font_size", 12) + time_label.add_theme_color_override("font_color", Color("fff5d9")) + row.add_child(time_label) + phase_label = Label.new() + phase_label.custom_minimum_size.x = 48 + phase_label.add_theme_font_size_override("font_size", 12) + phase_label.add_theme_color_override("font_color", Color("ffd37a")) + row.add_child(phase_label) + weather_label = _hud_value_label("晴れ", 48) + weather_label.name = "WeatherLabel" + weather_label.tooltip_text = "嵐の間は新規入館が停止します。現在のお客様は館内に残ります。" + weather_label.mouse_filter = Control.MOUSE_FILTER_STOP + row.add_child(weather_label) + + guest_count_label = Button.new() + guest_count_label.text = "来客 0人" + guest_count_label.tooltip_text = "来客の欲求と現在の目的地を見る" + guest_count_label.custom_minimum_size.x = 72 + guest_count_label.add_theme_font_size_override("font_size", 12) + guest_count_label.pressed.connect(_show_guest_observer) + lodging_count_label = _hud_value_label("宿泊 0人", 72) + row.add_child(guest_count_label) + row.add_child(lodging_count_label) + for resource_name: StringName in [&"gold", &"mana", &"materials", &"faith"]: + var resource_block := HBoxContainer.new() + resource_block.add_theme_constant_override("separation", 2) + var resource_icon := TextureRect.new() + resource_icon.name = "%sIcon" % String(resource_name).capitalize() + resource_icon.texture = visual_theme.resource_icon(resource_name) if visual_theme != null else null + resource_icon.custom_minimum_size = Vector2(16, 16) + resource_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + resource_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + resource_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + resource_icon_nodes[resource_name] = resource_icon + resource_block.add_child(resource_icon) + var label := _hud_value_label("", 54) + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + resource_labels[resource_name] = label + resource_block.add_child(label) + row.add_child(resource_block) + alert_count_label = _hud_value_label("警戒 0", 64) + alert_count_label.mouse_filter = Control.MOUSE_FILTER_STOP + alert_count_label.focus_mode = Control.FOCUS_ALL + alert_count_label.gui_input.connect(_on_defense_notice_input) + alert_count_label.add_theme_color_override("font_color", Color("ffb095")) + alert_icon_node = _icon_rect(visual_theme.control_icon(&"alert") if visual_theme != null else null, 20) + alert_icon_node.name = "AlertIcon" + row.add_child(alert_icon_node) + row.add_child(alert_count_label) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(spacer) + pause_button = _button("停止", _toggle_pause, 52) + row.add_child(pause_button) + for value: int in [1, 2, 3]: + var speed_button := _button("x%d" % value, _set_speed.bind(value), 42) + speed_button.toggle_mode = true + speed_buttons[value] = speed_button + row.add_child(speed_button) + return panel + + +func _create_navigation_rail() -> Control: + var panel := PanelContainer.new() + panel.name = "NavigationRail" + panel.custom_minimum_size.x = 64 + panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_leather", _visual_color(&"dark_panel_alt", Color("513018")), _visual_color(&"border_gold", Color("bd8540")), 8, 7)) + var column := VBoxContainer.new() + column.add_theme_constant_override("separation", 4) + panel.add_child(column) + var entries := [ + [&"staff", "従業員"], + [&"facility", "施設"], + [&"product", "商品"], + [&"business", "営業"], + [&"explore", "探索"], + [&"investment", "投資"], + [&"ledger", "帳簿"], + ] + for entry: Array in entries: + var mode := StringName(entry[0]) + var button := _button(String(entry[1]), _activate_management_mode.bind(mode), 50) + button.custom_minimum_size.y = 46 + button.toggle_mode = true + button.add_theme_font_size_override("font_size", 12) + _apply_button_icon(button, visual_theme.navigation_icon(&"ledger" if mode == &"business" else mode) if visual_theme != null else null, 16) + navigation_buttons[mode] = button + column.add_child(button) + var nav_spacer := Control.new() + nav_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL + column.add_child(nav_spacer) + var layer_hint := Label.new() + layer_hint.text = "TAB\n表 / 裏" + layer_hint.tooltip_text = "ホイール: 拡大縮小\n画面移動ボタン → 左ドラッグ: 移動\n中ボタンドラッグ: 移動\nHome: 全体表示" + layer_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + layer_hint.add_theme_font_size_override("font_size", 10) + layer_hint.add_theme_color_override("font_color", Color("e2c38c")) + column.add_child(layer_hint) + return panel + + +func _create_side_panel() -> Control: + var panel := PanelContainer.new() + panel.name = "SidePanel" + panel.custom_minimum_size.x = 312 + panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_parchment", _visual_color(&"light_panel", Color("f4e3bd")), _visual_color(&"border_gold", Color("a96f2f")), 8, 10)) + var scroll := ScrollContainer.new() + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_child(scroll) + var column := VBoxContainer.new() + column.custom_minimum_size.x = 278 + column.size_flags_horizontal = Control.SIZE_EXPAND_FILL + column.add_theme_constant_override("separation", 8) + scroll.add_child(column) + var root_column := column + + var inspector_heading := HBoxContainer.new() + var inspector_title := _section_heading("運営状況") + inspector_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL + inspector_heading.add_child(inspector_title) + inspector_heading.add_child(_button("閉じる I", _set_management_panel_open.bind(false), 64)) + column.add_child(inspector_heading) + dashboard_summary_label = Label.new() + dashboard_summary_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + dashboard_summary_label.add_theme_color_override("font_color", Color("4d341f")) + dashboard_summary_label.add_theme_font_size_override("font_size", 13) + column.add_child(dashboard_summary_label) + foreground_event_panel = VBoxContainer.new() + foreground_event_panel.name = "ForegroundEventPanel" + foreground_event_panel.add_theme_constant_override("separation", 6) + foreground_event_panel.visible = false + column.add_child(foreground_event_panel) + foreground_event_panel.add_child(_section_heading("要対応イベント")) + foreground_event_label = Label.new() + foreground_event_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + foreground_event_label.add_theme_color_override("font_color", Color("8f3f29")) + foreground_event_label.add_theme_font_size_override("font_size", 13) + foreground_event_panel.add_child(foreground_event_label) + foreground_event_choices = VBoxContainer.new() + foreground_event_choices.name = "ForegroundEventChoices" + foreground_event_choices.add_theme_constant_override("separation", 5) + foreground_event_panel.add_child(foreground_event_choices) + column.add_child(HSeparator.new()) + operations_controls = VBoxContainer.new() + operations_controls.name = "OperationsControls" + operations_controls.add_theme_constant_override("separation", 8) + column.add_child(operations_controls) + column = operations_controls + + var candidate_heading_row := HBoxContainer.new() + candidate_heading_row.add_child(_section_heading("スタッフ")) + var candidate_heading_spacer := Control.new() + candidate_heading_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + candidate_heading_row.add_child(candidate_heading_spacer) + var defense_button := _button("防衛", _show_defense_manager, 52) + defense_button.name = "DefenseManagementButton" + candidate_heading_row.add_child(defense_button) + staff_management_toggle_button = _button("雇用済み", _toggle_staff_management_panel, 62) + staff_management_toggle_button.name = "StaffManagementToggleButton" + staff_management_toggle_button.toggle_mode = true + candidate_heading_row.add_child(staff_management_toggle_button) + staff_candidate_toggle_button = _button( + "候補を見る H", + _toggle_staff_candidate_panel, + 108 + ) + staff_candidate_toggle_button.name = "StaffCandidateToggleButton" + candidate_heading_row.add_child(staff_candidate_toggle_button) + column.add_child(candidate_heading_row) + staff_management_panel = StaffManagementPanelScript.new() + staff_management_panel.configure(_runtime, _apply_button_style) + staff_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + staff_management_panel.status_requested.connect(_on_staff_management_status) + staff_management_panel.visible = false + column.add_child(staff_management_panel) + staff_candidate_panel = VBoxContainer.new() + staff_candidate_panel.name = "StaffCandidatePanel" + staff_candidate_panel.visible = false + staff_candidate_panel.add_theme_constant_override("separation", 7) + column.add_child(staff_candidate_panel) + staff_candidate_summary_label = Label.new() + staff_candidate_summary_label.name = "StaffCandidateSummary" + staff_candidate_summary_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + staff_candidate_summary_label.add_theme_color_override("font_color", Color("765438")) + staff_candidate_summary_label.add_theme_font_size_override("font_size", 12) + staff_candidate_panel.add_child(staff_candidate_summary_label) + var candidate_list := VBoxContainer.new() + candidate_list.name = "StaffCandidateList" + candidate_list.add_theme_constant_override("separation", 7) + staff_candidate_panel.add_child(candidate_list) + column.add_child(HSeparator.new()) + + selected_heading_label = _section_heading("選択中の施設") + column.add_child(selected_heading_label) + var selected_row := HBoxContainer.new() + selected_row.add_theme_constant_override("separation", 8) + column.add_child(selected_row) + selected_entity_icon = TextureRect.new() + selected_entity_icon.name = "SelectedEntityIcon" + selected_entity_icon.custom_minimum_size = Vector2(24, 24) + selected_entity_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + selected_entity_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + selected_entity_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + selected_row.add_child(selected_entity_icon) + selected_detail_label = Label.new() + selected_detail_label.mouse_filter = Control.MOUSE_FILTER_PASS + selected_detail_label.text = "地図上の施設をクリックすると、営業状態を確認できます。" + selected_detail_label.custom_minimum_size.y = 112 + selected_detail_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + selected_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + selected_detail_label.add_theme_color_override("font_color", Color("4d341f")) + selected_detail_label.add_theme_font_size_override("font_size", 13) + selected_row.add_child(selected_detail_label) + related_facility_picker = OptionButton.new() + related_facility_picker.name = "RelatedFacilityPicker" + related_facility_picker.visible = false + related_facility_picker.tooltip_text = "自施設の稼働に必要な関連施設を指定します。休止状態は解除しません。" + related_facility_picker.item_selected.connect(_on_related_facility_selected) + column.add_child(related_facility_picker) + selected_elevator_controls = VBoxContainer.new() + selected_elevator_controls.name = "SelectedElevatorControls" + selected_elevator_controls.visible = false + column.add_child(selected_elevator_controls) + elevator_operation_button = _button("昇降機を停止", _on_elevator_operation_pressed, 160) + elevator_operation_button.name = "ElevatorOperationButton" + selected_elevator_controls.add_child(elevator_operation_button) + elevator_operation_hint = Label.new() + elevator_operation_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + elevator_operation_hint.add_theme_font_size_override("font_size", 12) + elevator_operation_hint.add_theme_color_override("font_color", Color("654d37")) + selected_elevator_controls.add_child(elevator_operation_hint) + + operating_ledger_panel = OperatingLedgerPanelScript.new() + root_column.add_child(operating_ledger_panel) + operating_ledger_panel.setup(_runtime, GameClock, _apply_button_style, GameState.active_layer) + operating_ledger_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + operating_ledger_panel.status_requested.connect(_on_staff_management_status) + operating_ledger_panel.visible = false + supply_management_panel = SupplyManagementPanelScript.new() + supply_management_panel.configure(_runtime, _apply_button_style) + supply_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + supply_management_panel.status_requested.connect(_on_staff_management_status) + supply_management_panel.action_requested.connect(_activate_management_mode) + supply_management_panel.visible = false + root_column.add_child(supply_management_panel) + business_management_panel = BusinessManagementPanelScript.new() + business_management_panel.configure(_runtime, _apply_button_style) + business_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + business_management_panel.status_requested.connect(_on_staff_management_status) + business_management_panel.action_requested.connect(_activate_management_mode) + business_management_panel.visible = false + root_column.add_child(business_management_panel) + return panel + + +func _create_bottom_bar() -> Control: + var panel := PanelContainer.new() + panel.name = "BottomBar" + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_wood", _visual_color(&"dark_panel", Color("24382e")), _visual_color(&"border_gold", Color("7d8862")), 0, 5)) + var column := VBoxContainer.new() + column.add_theme_constant_override("separation", 4) + panel.add_child(column) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 5) + column.add_child(row) + + palette_toggle_button = _button("建築 B", _toggle_build_palette, 76) + palette_toggle_button.name = "BuildPaletteToggleButton" + palette_toggle_button.toggle_mode = true + row.add_child(palette_toggle_button) + front_button = _button("客用・表層", _set_layer.bind(0), 96) + back_button = _button("裏方・裏層", _set_layer.bind(1), 96) + _apply_button_icon(front_button, visual_theme.control_icon(&"front_layer") if visual_theme != null else null, 16) + _apply_button_icon(back_button, visual_theme.control_icon(&"back_layer") if visual_theme != null else null, 16) + front_button.toggle_mode = true + back_button.toggle_mode = true + row.add_child(front_button) + row.add_child(back_button) + excavation_button = _button("掘削 E", _toggle_excavation_mode, 72) + excavation_button.name = "ExcavationButton" + excavation_button.toggle_mode = true + _apply_button_icon(excavation_button, visual_theme.control_icon(&"excavate") if visual_theme != null else null, 16) + row.add_child(excavation_button) + row.add_child(VSeparator.new()) + var rotate_button := _button("回転 R", _rotate_build_preview, 72) + _apply_button_icon(rotate_button, visual_theme.control_icon(&"rotate") if visual_theme != null else null, 16) + row.add_child(rotate_button) + row.add_child(_button("解除 Esc", _clear_active_tool, 80)) + pan_button = _button("画面移動", _toggle_pan_mode, 90) + pan_button.name = "PanModeButton" + pan_button.toggle_mode = true + pan_button.tooltip_text = "左ドラッグとホイールで画面を上下移動。Ctrl+ホイールで拡大縮小。もう一度押すか Esc で解除、Homeで全体表示。" + row.add_child(pan_button) + var focus_button := _button("営業階", func() -> void: world_preview.focus_operating_area(), 64) + focus_button.name = "FocusOperatingAreaButton" + focus_button.tooltip_text = "現在の層の建物を見やすい大きさで表示。Homeで全体表示。" + row.add_child(focus_button) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(spacer) + management_toggle_button = _button("運営 I", _toggle_management_panel, 72) + management_toggle_button.name = "ManagementToggleButton" + management_toggle_button.toggle_mode = true + row.add_child(management_toggle_button) + event_log_dialog = EventLogDialogScript.new() + event_log_dialog.configure(_runtime) + event_log_dialog.visibility_changed.connect(_update_modal_input_guard) + add_child(event_log_dialog) + var event_log_button := _button("記録", event_log_dialog.show_events, 62) + event_log_button.name = "EventLogButton" + event_log_button.tooltip_text = "最近のイベントを確認" + row.add_child(event_log_button) + var save_button := _button("保存", _save_manual, 62) + save_button.name = "ManualSaveButton" + _apply_button_icon(save_button, visual_theme.control_icon(&"save") if visual_theme != null else null, 16) + row.add_child(save_button) + var audio_button := _button("音設定", _show_audio_settings, 62) + audio_button.name = "AudioSettingsButton" + row.add_child(audio_button) + var load_menu := MenuButton.new() + load_menu.name = "SaveLoadMenu" + load_menu.text = "保存・読込 ▾" + load_menu.custom_minimum_size = Vector2(64,28) + load_menu.add_theme_font_size_override("font_size",12) + _apply_button_style(load_menu) + load_menu.get_popup().add_item("手動セーブを読み込む",0) + load_menu.get_popup().add_item("旧寸法を修正して読み込む",1) + load_menu.get_popup().add_item("保存スロットを選んで読込",2) + load_menu.get_popup().add_item("新しい名前で別スロット保存",3) + load_menu.get_popup().id_pressed.connect(func(id: int): + if id == 0: _load_manual() + elif id == 1: _preview_dimension_migration() + elif id == 2: _show_manual_load_slots() + elif id == 3: _ensure_manual_save_slots().show_new_save(manual_save_path)) + row.add_child(load_menu) + + palette_scroll = ScrollContainer.new() + palette_scroll.name = "BuildPaletteScroll" + palette_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO + palette_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + palette_scroll.custom_minimum_size.y = 62 + palette_scroll.visible = false + column.add_child(palette_scroll) + var palette_row := HBoxContainer.new() + palette_row.name = "BuildPaletteRow" + palette_row.add_theme_constant_override("separation", 4) + palette_scroll.add_child(palette_row) + var grid := GridContainer.new() + grid.name = "BuildPalette" + grid.columns = MVP_FACILITY_IDS.size() + grid.add_theme_constant_override("h_separation", 4) + grid.add_theme_constant_override("v_separation", 0) + palette_row.add_child(grid) + demolition_button = _button("撤去", _toggle_demolition_mode, 72) + demolition_button.name = "DemolitionButton" + demolition_button.toggle_mode = true + palette_row.add_child(demolition_button) + + build_status_label = Label.new() + build_status_label.name = "BuildStatusLabel" + build_status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + build_status_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + build_status_label.add_theme_font_size_override("font_size", 11) + build_status_label.add_theme_color_override("font_color", Color("c7d3a8")) + column.add_child(build_status_label) + return panel + + +func _toggle_build_palette() -> void: + _set_build_palette_open(not _build_palette_open) + + +func _set_build_palette_open(open: bool) -> void: + _build_palette_open = open + if palette_scroll != null: + palette_scroll.visible = open + if palette_toggle_button != null: + palette_toggle_button.set_pressed_no_signal(open) + palette_toggle_button.text = "建築を閉じる B" if open else "建築 B" + if not open: + var bottom := find_child("BottomBar", true, false) as Control + if bottom != null: bottom.reset_size.call_deferred() + + +func _toggle_management_panel() -> void: + _set_management_panel_open(not _management_panel_open) + + +func _set_management_panel_open(open: bool) -> void: + _management_panel_open = open + if management_panel != null: + management_panel.visible = open + if management_toggle_button != null: + management_toggle_button.set_pressed_no_signal(open) + if not open: + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + + +func _activate_management_mode(mode: StringName) -> void: + _set_management_panel_open(mode != &"facility") + _set_supply_panel_open(mode == &"product") + _set_business_panel_open(mode == &"business") + _set_ledger_panel_open(mode == &"ledger") + for raw_mode: Variant in navigation_buttons: + (navigation_buttons[raw_mode] as Button).set_pressed_no_signal(StringName(raw_mode) == mode) + match mode: + &"staff": + _set_staff_management_panel_open(true) + &"facility": + _set_build_palette_open(true) + _set_staff_candidate_panel_open(false) + _set_status("建築する施設を下のパレットから選んでください。", true, true) + &"business": + _set_status("施設ごとの料金と新規受付の開閉を設定できます。", true, true) + &"product": + _set_status("倉庫への仕入れ、厨房の自動生産、施設への配送を確認できます。", true, true) + &"explore": + _set_status("探索管理は一覧画面の実装準備中です。", true, true) + &"investment": + _set_status("投資管理は一覧画面の実装準備中です。", true, true) + &"ledger": + _set_status("本日の資金増減と、00:00に確定した営業日の損益を表示します。", true, true) + + +func _set_ledger_panel_open(open: bool) -> void: + _ledger_panel_open = open + if open: + _set_supply_panel_open(false) + _set_business_panel_open(false) + if operating_ledger_panel != null: + operating_ledger_panel.visible = open + if open: + operating_ledger_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _supply_panel_open and not _business_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _supply_panel_open and not _business_panel_open + if open: + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + + +func _set_supply_panel_open(open: bool) -> void: + _supply_panel_open = open + if open: + _set_business_panel_open(false) + _set_ledger_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + if supply_management_panel != null: + supply_management_panel.visible = open + if open: + supply_management_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _ledger_panel_open and not _business_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _ledger_panel_open and not _business_panel_open + + +func _set_business_panel_open(open: bool) -> void: + _business_panel_open = open + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + if business_management_panel != null: + business_management_panel.visible = open + if open: + business_management_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _ledger_panel_open and not _supply_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _ledger_panel_open and not _supply_panel_open + + +func _on_opening_action(action: StringName) -> void: + match action: + &"build_reception", &"build_room", &"build_corridor", &"build_stairs": + _set_ledger_panel_open(false) + _set_management_panel_open(false) + _set_build_palette_open(true) + var definitions := {&"build_reception": &"reception", &"build_room": &"standard_room", &"build_corridor": &"guest_corridor", &"build_stairs": &"guest_stairs"} + _select_build_definition(definitions[action]) + &"staff": + _activate_management_mode(&"staff") + if _call_array(_service(&"staff_service"), &"get_staff_members").is_empty(): + _set_staff_candidate_panel_open(true) + &"resume": + if GameClock.paused: + _toggle_pause() + &"ledger": + _activate_management_mode(&"ledger") + + +func _connect_signals() -> void: + GameClock.time_changed.connect(_on_time_changed) + GameClock.phase_changed.connect(_on_phase_changed) + GameState.resource_changed.connect(_on_resource_changed) + world_preview.facility_selected.connect(_on_facility_selected) + world_preview.facility_selected.connect(_on_audio_world_selection) + world_preview.staff_assignment_dropped.connect(func(data: Dictionary, facility_id: StringName) -> void: + staff_management_panel.assign_from_drop(data, facility_id) + ) + world_preview.staff_assignment_clicked.connect(func(staff_id: StringName, facility_id: StringName) -> void: + assign_staff_by_click(facility_id, staff_id) + ) + world_preview.legacy_stair_selected.connect(_on_legacy_stair_selected) + world_preview.legacy_stair_selected.connect(_on_audio_world_selection) + world_preview.construction_selected.connect(_on_construction_selected) + world_preview.construction_selected.connect(_on_audio_world_selection) + world_preview.cell_clicked.connect(_on_map_cell_clicked) + world_preview.build_preview_changed.connect(_on_build_preview_changed) + world_preview.excavation_preview_changed.connect(_on_excavation_preview_changed) + if _runtime != null and _runtime is Object: + if _runtime.has_signal(&"state_updated"): + _runtime.connect(&"state_updated", Callable(self, "_on_snapshot_published")) + elif _runtime.has_signal(&"snapshot_published"): + _runtime.connect(&"snapshot_published", Callable(self, "_on_snapshot_published")) + if _runtime.has_signal(&"checkpoint_presentation_requested"): + _runtime.connect(&"checkpoint_presentation_requested", Callable(self, "_on_checkpoint_presentation_requested")) + if _runtime.has_signal(&"save_operation_completed"): + _runtime.connect(&"save_operation_completed", Callable(self, "_on_save_operation_completed")) + if _runtime.has_signal(&"command_completed"): + _runtime.connect(&"command_completed", Callable(self, "_on_command_completed")) + _bind_domain_event_stream() + + +func _populate_build_palette() -> void: + var palette := find_child("BuildPalette", true, false) as GridContainer + if palette == null: + return + var registry: Variant = _service(&"definition_registry") + for definition_id: StringName in MVP_FACILITY_IDS: + var definition: Variant = null + if registry != null and registry.has_method(&"get_facility"): + definition = registry.call(&"get_facility", definition_id) + if definition == null: + continue + var button := Button.new() + button.toggle_mode = true + button.custom_minimum_size = Vector2(116, 50) + button.text = _build_button_text(definition) + button.tooltip_text = String(_definition_value(definition, &"description", "")) + button.add_theme_font_size_override("font_size", 12) + _apply_button_icon(button, visual_theme.facility_icon(definition_id) if visual_theme != null else null, 22) + _apply_button_style(button) + button.toggled.connect(_on_build_button_toggled.bind(definition_id)) + build_buttons[definition_id] = button + palette.add_child(button) + if build_buttons.is_empty(): + _set_status("施設定義を読み込めませんでした。", false) + else: + _set_status("B 建築 / H 雇用 / I 運営 ホイール 拡大縮小・中ボタン 移動・Home 全体表示", true, true) + + +func _populate_staff_candidates() -> void: + var list := find_child("StaffCandidateList", true, false) as VBoxContainer + if list == null: + return + for child: Node in list.get_children(): + list.remove_child(child) + child.queue_free() + staff_candidate_buttons.clear() + var summaries := _staff_candidate_summaries() + for summary: Dictionary in summaries: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + if candidate_id.is_empty(): + continue + var card := PanelContainer.new() + card.name = "Candidate_%s" % String(candidate_id) + card.add_theme_stylebox_override( + "panel", + _themed_panel_style(&"panel_leather", Color("132630"), Color("31505d"), 7, 8) + ) + var content := VBoxContainer.new() + content.add_theme_constant_override("separation", 4) + card.add_child(content) + var detail := Label.new() + detail.name = "CandidateDetail_%s" % String(candidate_id) + detail.text = _staff_candidate_text(summary) + detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + detail.add_theme_color_override("font_color", Color("c8d7dc")) + detail.add_theme_font_size_override("font_size", 12) + content.add_child(detail) + var hire_button := Button.new() + hire_button.name = "Hire_%s" % String(candidate_id) + hire_button.text = "無料で加入" if bool(summary.get(&"unique", false)) else "雇用する" + hire_button.custom_minimum_size.y = 30 + _apply_button_style(hire_button) + hire_button.pressed.connect(_hire_staff_candidate.bind(candidate_id)) + content.add_child(hire_button) + staff_candidate_buttons[candidate_id] = hire_button + list.add_child(card) + _refresh_staff_candidates() + + +func _staff_candidate_summaries() -> Array[Dictionary]: + if _runtime == null or not _runtime.has_method(&"get_mvp_staff_candidate_summaries"): + return [] + var value: Variant = _runtime.call(&"get_mvp_staff_candidate_summaries") + var result: Array[Dictionary] = [] + if value is Array: + for item: Variant in value: + if item is Dictionary: + result.append((item as Dictionary).duplicate(true)) + return result + + +func _staff_candidate_text(summary: Dictionary) -> String: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + var display_name := String(summary.get(&"display_name", candidate_id)) + var role_id := StringName(summary.get(&"role_id", &"")) + var shift_id := StringName(summary.get(&"preferred_shift", &"any")) + var skills_value: Variant = summary.get(&"skills", {}) + var skills: Dictionary = skills_value if skills_value is Dictionary else {} + var skill_parts: Array[String] = [] + for skill_id: StringName in [&"service", &"operation", &"combat", &"underworld"]: + skill_parts.append("%s%d" % [ + String(STAFF_SKILL_NAMES.get(skill_id, String(skill_id))), + int(skills.get(skill_id, 0)), + ]) + return "%s(%d歳) %s / %s\n契約 G%d 日給 G%d\n技能 %s" % [ + display_name, + int(summary.get(&"age", 0)), + String(STAFF_ROLE_NAMES.get(role_id, String(role_id))), + String(STAFF_SHIFT_NAMES.get(shift_id, String(shift_id))), + int(summary.get(&"signing_cost", 0)), + int(summary.get(&"daily_wage", 0)), + "・".join(skill_parts), + ] + + +func _toggle_staff_management_panel() -> void: + _set_staff_management_panel_open(not _staff_management_panel_open) + + +func _set_staff_management_panel_open(open: bool) -> void: + _staff_management_panel_open = open + if staff_management_panel != null: + staff_management_panel.visible = open + if staff_management_toggle_button != null: + staff_management_toggle_button.set_pressed_no_signal(open) + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_management_panel_open(true) + staff_management_panel.refresh() + _set_status("従業員を選び、配属先と毎日の勤務帯を変更できます。", true, true) + + +func _on_staff_management_status(message: String, success: bool) -> void: + _set_status(message, success) + + +func _toggle_staff_candidate_panel() -> void: + _set_staff_candidate_panel_open(not _staff_candidate_panel_open) + + +func _set_staff_candidate_panel_open(open: bool) -> void: + _staff_candidate_panel_open = open + if staff_candidate_panel != null: + staff_candidate_panel.visible = open + _refresh_staff_candidates() + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + _set_staff_management_panel_open(false) + _set_management_panel_open(true) + _set_status("雇用候補を選んでください。契約金と日給は確定前に表示されます。", true, true) + + +func _refresh_staff_candidates() -> void: + if staff_candidate_panel == null or staff_candidate_summary_label == null: + return + var summaries := _staff_candidate_summaries() + var hired_count := 0 + for summary: Dictionary in summaries: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + var hired := bool(summary.get(&"hired", false)) + if hired: + hired_count += 1 + var button := staff_candidate_buttons.get(candidate_id) as Button + if button == null: + continue + var pending := _pending_hire_commands.values().has(candidate_id) + button.disabled = hired or pending + button.text = ("加入済み" if bool(summary.get(&"unique", false)) else "雇用済み") if hired else "手続き中…" if pending else ("無料で加入" if bool(summary.get(&"unique", false)) else "雇用する") + staff_candidate_summary_label.text = "未雇用 %d人 / 雇用済み %d人" % [ + maxi(summaries.size() - hired_count, 0), + hired_count, + ] + if staff_candidate_toggle_button != null: + staff_candidate_toggle_button.text = ( + "候補を閉じる H" if _staff_candidate_panel_open + else "候補を見る H(%d)" % maxi(summaries.size() - hired_count, 0) + ) + + +func _hire_staff_candidate(candidate_id: StringName) -> void: + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("雇用手続きを送信できません。", false) + return + if _pending_hire_commands.values().has(candidate_id): + _set_status("この候補の雇用手続きはすでに受理されています。", false) + return + var summaries := _staff_candidate_summaries() + var selected: Dictionary = {} + for summary: Dictionary in summaries: + if StringName(summary.get(&"candidate_id", &"")) == candidate_id: + selected = summary + break + if selected.is_empty(): + _set_status("雇用候補を読み込めませんでした。", false) + return + if bool(selected.get(&"hired", false)): + _set_status("%sはすでに雇用済みです。" % String(selected.get(&"display_name", candidate_id)), false) + _refresh_staff_candidates() + return + var sequence := _hire_sequence + _hire_sequence += 1 + var idempotency_key := StringName( + "ui.hire.%s.%08d" % [String(candidate_id), sequence] + ) + var result: Variant = _submit_ui_command(&"hire_staff_candidate", + {&"candidate_id": candidate_id}, + &"player", + candidate_id, + 75, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("雇用手続きへ応答がありません。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_hire_failure_message(result), false) + _refresh_staff_candidates() + return + var display_name := String(selected.get(&"display_name", candidate_id)) + if result.details is Dictionary and result.details.has(&"staff_id"): + _set_status("%sを雇用しました(契約 G%d / 日給 G%d)。" % [ + display_name, + int(selected.get(&"signing_cost", 0)), + int(selected.get(&"daily_wage", 0)), + ], true) + else: + _pending_hire_commands[int(result.command_id)] = candidate_id + _set_status("%sの雇用手続きを受理しました。" % display_name, true) + _refresh_dashboard() + + +func _hire_failure_message(result: Variant) -> String: + var details: Variant = result.details + if details is Dictionary: + var reason := StringName(details.get(&"reason", &"")) + match reason: + &"ARREARS_REMAIN", &"UNPAID_EXPENSES": + return "雇用不可: 未払経費 G%dを清算してください。" % int(details.get(&"arrears_total", 0)) + &"BANKRUPT": + return "雇用不可: 資金破綻後は新しい契約を結べません。" + &"EXTERNAL_ROLLBACK_FAILED": + return "雇用不可: 運営データの復旧が必要です。" + var message := String(result.message).to_lower() + if int(result.code) == int(CommandResult.Code.DUPLICATE_COMMAND_ID): + return "この候補はすでに雇用済みです。" + if "insufficient" in message or "balance" in message: + return "雇用不可: 契約金が不足しています。" + return _command_failure_message(result) + + +func _build_button_text(definition: Variant) -> String: + var name := String(_definition_value(definition, &"display_name", _definition_value(definition, &"id", "施設"))) + var cost_value: Variant = _definition_value(definition, &"construction_cost", {}) + var gold := 0 + var materials := 0 + if cost_value is Dictionary: + gold = int(cost_value.get(&"gold", cost_value.get("gold", 0))) + materials = int(cost_value.get(&"materials", cost_value.get("materials", 0))) + return "%s\nG %d / 資材 %d" % [name, gold, materials] + + +func _on_build_button_toggled(pressed: bool, definition_id: StringName) -> void: + if not pressed: + if _selected_definition_id == definition_id: + _clear_build_selection() + return + _select_build_definition(definition_id) + + +func _select_build_definition(definition_id: StringName) -> void: + var registry: Variant = _service(&"definition_registry") + if registry == null or not registry.has_method(&"get_facility"): + _set_status("建設パレットを利用できません。", false) + return + var definition: Variant = registry.call(&"get_facility", definition_id) + if definition == null: + _set_status("選択した施設定義が見つかりません。", false) + return + _set_demolition_mode(false, false) + _set_excavation_mode(false, false) + _set_pan_mode(false) + _selected_definition_id = definition_id + _selected_definition = definition + _build_rotation = 0 + for raw_id: Variant in build_buttons: + (build_buttons[raw_id] as Button).set_pressed_no_signal(StringName(raw_id) == definition_id) + var target_layer := clampi(int(_definition_value(definition, &"layer", 0)), 0, 1) + _set_layer(target_layer) + world_preview.set_build_definition(definition, _build_rotation) + _set_status("%sを選択中。配置するマスをクリックしてください。" % String(_definition_value(definition, &"display_name", definition_id)), true, true) + + +func _clear_build_selection(announce := true) -> void: + _selected_definition_id = &"" + _selected_definition = null + _build_rotation = 0 + for button_value: Variant in build_buttons.values(): + (button_value as Button).set_pressed_no_signal(false) + if world_preview != null: + world_preview.clear_build_definition() + if announce: + _set_status("建設選択を解除しました。", true, true) + + +func _clear_active_tool() -> void: + if _demolition_mode: + _set_demolition_mode(false) + return + if world_preview.pan_mode: + _set_pan_mode(false) + _set_status("画面移動を解除しました。施設をクリックすると情報を表示します。", true, true) + return + if _supply_panel_open or _business_panel_open: + _set_management_panel_open(false) + return + if _staff_management_panel_open: + _set_staff_management_panel_open(false) + return + if _staff_candidate_panel_open: + _set_staff_candidate_panel_open(false) + return + if _excavation_mode: + _set_excavation_mode(false) + return + _clear_build_selection() + _set_build_palette_open(false) + _set_management_panel_open(false) + + +func _toggle_excavation_mode() -> void: + _set_excavation_mode(not _excavation_mode) + + +func _toggle_pan_mode() -> void: + _set_pan_mode(not world_preview.pan_mode) + _set_status("画面移動中:左ドラッグで移動、ホイールで上下移動、Ctrl+ホイールで拡大縮小。Escで解除。" if world_preview.pan_mode else "画面移動を解除しました。施設をクリックすると情報を表示します。", true, true) + + +func _set_pan_mode(enabled: bool) -> void: + if enabled: + _set_demolition_mode(false, false) + _clear_build_selection(false) + _set_excavation_mode(false, false) + _set_build_palette_open(false) + world_preview.set_pan_mode(enabled) + if pan_button != null: + pan_button.set_pressed_no_signal(enabled) + + +func _set_excavation_mode(enabled: bool, announce := true) -> void: + if enabled: + _set_demolition_mode(false, false) + _set_pan_mode(false) + _excavation_mode = enabled + if excavation_button != null: + excavation_button.set_pressed_no_signal(_excavation_mode) + if world_preview != null: + world_preview.set_excavation_mode(_excavation_mode) + if not _excavation_mode: + if announce: + _set_status("掘削モードを解除しました。", true, true) + return + _clear_build_selection(false) + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "掘削モード" + selected_detail_label.text = "地下のマスにカーソルを合わせると、費用と所要時間を確認できます。" + var gate_message := _excavation_gate_message() + _set_status( + gate_message if not gate_message.is_empty() else "地下の掘削候補を選んでください。FRONT / BACKは同時に開通します。", + gate_message.is_empty(), + true + ) + + +func _rotate_build_preview() -> void: + if _selected_definition == null: + _set_status("先に建設する施設を選択してください。", false, true) + return + var allowed_value: Variant = _definition_value(_selected_definition, &"allowed_rotations", [0]) + var allowed: Array[int] = [] + if allowed_value is Array: + for rotation_value: Variant in allowed_value: + allowed.append(int(rotation_value)) + allowed.sort() + if allowed.is_empty(): + allowed = [0] + var index := allowed.find(_build_rotation) + _build_rotation = allowed[(index + 1) % allowed.size()] + world_preview.set_build_rotation(_build_rotation) + _set_status("回転 %d°。地図のマスをクリックして建設します。" % _build_rotation, true, true) + + +func _on_map_cell_clicked(cell: Vector3i) -> void: + if _audio_feedback != null: + _audio_feedback.request_click() + if _demolition_mode: + _preview_demolition(cell) + return + if _excavation_mode: + _start_excavation(cell) + return + if _selected_definition == null: + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_elevator_controls.visible = false + selected_entity_icon.texture = null + selected_heading_label.text = "選択中のマス" + selected_detail_label.text = "座標 (%d, %d)\nレイヤー %s\n\n施設のないマスです。" % [cell.x, cell.y, "FRONT / 表層" if cell.z == 0 else "BACK / 裏層"] + return + var preview := world_preview.get_build_preview_report(cell) + if not bool(preview.get(&"can_build", false)): + if _audio_feedback != null: + _audio_feedback.reject_local(&"BUILD_PREVIEW_REJECTED") + _set_status(_preview_failure_message(preview), false) + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var idempotency_key := StringName("ui.build.%08d" % _build_sequence) + _build_sequence += 1 + var result: Variant = _submit_ui_command(&"build_facility", + {&"definition_id": _selected_definition_id, &"origin": cell, &"rotation": _build_rotation}, + &"player", + &"", + 50, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("建設指示を送信できませんでした。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_command_failure_message(result), false) + return + var name := String(_definition_value(_selected_definition, &"display_name", _selected_definition_id)) + if result.details is Dictionary and result.details.has(&"job_id"): + _set_status("%sの建設を開始しました(%d, %d / %d°)。" % [name, cell.x, cell.y, _build_rotation], true) + else: + _pending_build_commands[int(result.command_id)] = _selected_definition_id + _set_status("%sの建設指示を受理しました(%d, %d / %d°)。" % [name, cell.x, cell.y, _build_rotation], true) + _refresh_dashboard() + + +func _on_build_preview_changed(report: Dictionary) -> void: + if _selected_definition == null or world_preview.get_hovered_cell() == null: + return + if bool(report.get(&"can_build", false)): + var cell: Vector3i = world_preview.get_hovered_cell() + var connection: Dictionary = report.get(&"connection", {}) + var warning := " · 入口への経路未接続" if connection.get(&"status", &"") == &"DISCONNECTED" else " · 経路確認不可" if connection.get(&"status", &"") == &"UNAVAILABLE" else "" + _set_status("配置可能 (%d, %d)・%d°%s — クリックで建設" % [cell.x, cell.y, _build_rotation, warning], true, true) + else: + _set_status(_preview_failure_message(report), false, true) + + +func _on_excavation_preview_changed(report: Dictionary) -> void: + if not _excavation_mode or world_preview.get_hovered_cell() == null: + return + var gate_message := _excavation_gate_message() + if not gate_message.is_empty(): + _set_status("掘削不可: %s%s" % [gate_message, _excavation_cost_and_time(report)], false, true) + return + var cell: Vector3i = world_preview.get_hovered_cell() + if bool(report.get(&"ok", false)): + _set_status( + "掘削可能 (%d, %d)・%s%s — クリックでFRONT / BACKを開通" % [ + cell.x, + cell.y, + "FRONT / 表層" if cell.z == 0 else "BACK / 裏層", + _excavation_cost_and_time(report), + ], + true, + true + ) + else: + _set_status(_excavation_failure_message(report), false, true) + + +func _start_excavation(cell: Vector3i) -> void: + var report := world_preview.get_excavation_preview_report(cell) + var gate_message := _excavation_gate_message() + if not gate_message.is_empty(): + _set_status("掘削不可: %s%s" % [gate_message, _excavation_cost_and_time(report)], false) + return + if not bool(report.get(&"ok", false)): + _set_status(_excavation_failure_message(report), false) + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var spatial_position := Vector2i(cell.x, cell.y) + if _pending_excavation_commands.values().has(spatial_position): + _set_status("このマスの掘削指示はすでに受理されています。", false) + return + var idempotency_key := _excavation_idempotency_key(cell) + var result: Variant = _submit_ui_command(&"start_excavation", + {&"cell": Vector3i(cell.x, cell.y, world_preview.layer_mode)}, + &"player", + &"", + 50, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("掘削指示を送信できませんでした。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_excavation_command_failure_message(result), false) + return + if result.details is Dictionary and result.details.has(&"job_id"): + _select_excavation_job(StringName(result.details.get(&"job_id", &""))) + _set_status("掘削を開始しました(%d, %d / FRONT・BACK)。" % [cell.x, cell.y], true) + else: + _pending_excavation_commands[int(result.command_id)] = spatial_position + _set_status("掘削指示を受理しました(%d, %d / FRONT・BACK)。" % [cell.x, cell.y], true) + _refresh_dashboard() + + +func _excavation_idempotency_key(cell: Vector3i) -> StringName: + # One click gets one stable retry identity. A later player retry receives a + # new sequence so a previously cached validation failure cannot block it. + var sequence := _excavation_sequence + _excavation_sequence += 1 + return StringName( + "ui.excavation.%d.%d.%08d" % [cell.x, cell.y, sequence] + ) + + +func _on_facility_selected(instance_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = instance_id + _selected_entity_kind = &"facility" + _refresh_selected_entity() + + +func _on_legacy_stair_selected(stair_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = stair_id + _selected_entity_kind = &"legacy_stairs" + world_preview.selected_entity_id = stair_id + _refresh_selected_entity() + + +func _on_construction_selected(job_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = job_id + _selected_entity_kind = &"construction" + _refresh_selected_entity() + + +func _refresh_dashboard() -> void: + if event_log_dialog != null and event_log_dialog.visible: + event_log_dialog.refresh() + if rank_button != null and _service(&"progression_service") != null: + rank_button.text="世界樹ランク %d" % _service(&"progression_service").current_rank + if rank_dialog != null and rank_dialog.visible: rank_dialog.refresh() + if weather_label != null and is_instance_valid(_runtime) and _runtime.has_method("get_weather_report"): + var weather_report: Dictionary = _runtime.get_weather_report() + var weather_names := {&"clear": "晴れ", &"rain": "雨", &"storm": "嵐", &"mana_fog": "マナ霧"} + weather_label.text = weather_names.get(weather_report.get(&"kind", &""), "天候 —") + var guests := _call_array(_service(&"guest_service"), &"get_guests") + var queued_guests := 0 + var served_guests := 0 + for guest: Variant in guests: + if guest == null: + continue + if int(guest.state) == 4: + queued_guests += 1 + elif int(guest.state) == 5: + served_guests += 1 + var staff_members := _call_array(_service(&"staff_service"), &"get_staff_members") + var available_staff := 0 + var working_staff := 0 + for staff: Variant in staff_members: + if staff == null: + continue + if int(staff.state) == 2: + available_staff += 1 + elif int(staff.state) in [3, 4]: + working_staff += 1 + var event_service: Variant = _service(&"event_service") + var events := _call_array(event_service, &"get_instances") + var active_events := 0 + for event_instance: Variant in events: + if event_instance != null and int(event_instance.state) in [0, 1, 2, 7]: + active_events += 1 + var active_jobs := 0 + for job: Variant in _call_array(_service(&"construction_service"), &"get_jobs"): + if job != null and int(job.state) not in [2, 3]: + active_jobs += 1 + var heat_text := "—" + var heat_value := 0 + var authority: Variant = _service(&"authority_service") + if authority != null and authority.has_method(&"get_authority_state"): + var heat_state: Variant = authority.call(&"get_authority_state") + if heat_state != null: + heat_value = int(heat_state.heat) + heat_text = "%d / 100" % heat_value + var foreground_text := "なし" + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + if foreground != null: + foreground_text = String(foreground.resolved_event_id) + _refresh_foreground_event(event_service) + var defense_summary: Dictionary = {} + var defense: Variant = _service(&"defense_service") + if defense != null and defense.has_method(&"get_threat_summary"): + defense_summary = defense.call(&"get_threat_summary") + var threat_count := int(defense_summary.get(&"total", 0)) + var defense_text := "防衛 準備中 %d / 戦闘中 %d / 次波待ち %d" % [ + int(defense_summary.get(&"preparing", 0)), int(defense_summary.get(&"active", 0)), + int(defense_summary.get(&"between_waves", 0))] + var operations_text := _operations_dashboard_text() + dashboard_summary_label.text = ( + "客 %d人(待機 %d / 利用 %d)\n" % [guests.size(), queued_guests, served_guests] + + "従業員 %d人(待機 %d / 作業 %d)\n" % [staff_members.size(), available_staff, working_staff] + + "イベント %d件 前景: %s\n" % [active_events, foreground_text] + + "警戒度 %s  建設中 %d件" % [heat_text, active_jobs] + # Keep a stable row so threat changes do not move staff drag targets. + + "\n" + defense_text + + operations_text + ) + if guest_count_label != null: + guest_count_label.text = "来客 %d人" % guests.size() + if lodging_count_label != null: + var lodging_guests := 0 + for guest: Variant in guests: + if guest != null and int(guest.planned_exit_at) >= GameClock.absolute_minute + 360: + lodging_guests += 1 + lodging_count_label.text = "宿泊 %d人" % lodging_guests + if alert_count_label != null: + alert_count_label.text = "警戒 %d" % heat_value + (" / 襲撃 %d" % threat_count if threat_count > 0 else "") + alert_count_label.tooltip_text = (defense_text + "\n防衛参加中の従業員は配属変更できません。") if threat_count > 0 else "進行中の襲撃はありません。" + alert_count_label.add_theme_color_override( + "font_color", + Color("ffb498") if heat_value >= 60 or threat_count > 0 else Color("ebca84") if heat_value >= 30 else Color("c7d3a8") + ) + _refresh_staff_candidates() + _refresh_selected_entity() + _update_pause_and_speed() + if opening_guide != null: + opening_guide.refresh() + if operating_ledger_panel != null and _ledger_panel_open: + operating_ledger_panel.refresh() + + +func _refresh_foreground_event(event_service: Variant) -> void: + if foreground_event_panel == null \ + or foreground_event_label == null \ + or foreground_event_choices == null: + return + for child: Node in foreground_event_choices.get_children(): + foreground_event_choices.remove_child(child) + child.queue_free() + var foreground: Variant = null + if event_service != null and event_service.has_method(&"get_foreground_instance"): + foreground = event_service.call(&"get_foreground_instance") + if foreground == null or int(foreground.state) != 2: + foreground_event_panel.visible = false + return + foreground_event_panel.visible = true + if _audio_feedback != null: + _audio_feedback.observe_foreground(foreground) + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + var actors: Array[String] = [] + for actor_value: Variant in foreground.actor_ids: + actors.append(String(actor_value)) + foreground_event_label.text = ( + "%s\n関係者: %s" % [ + String(foreground.resolved_event_id), + "・".join(actors) if not actors.is_empty() else "なし", + ] + ) + var definition: Variant = null + if event_service.has_method(&"get_definition"): + definition = event_service.call(&"get_definition", foreground.resolved_event_id) + var choices_value: Variant = _definition_value(definition, &"choices", []) + if not choices_value is Array: + return + for choice: Variant in choices_value: + if choice == null: + continue + var choice_id := StringName(_definition_value(choice, &"choice_id", &"")) + if choice_id.is_empty(): + continue + var choice_button := Button.new() + choice_button.text = _event_choice_button_text(choice) + choice_button.custom_minimum_size.y = 34 + _apply_button_style(choice_button) + choice_button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + choice_button.pressed.connect( + _submit_foreground_event_choice.bind( + foreground.event_instance_id, + foreground.choice_instance_id, + choice_id + ) + ) + foreground_event_choices.add_child(choice_button) + + +func _event_choice_button_text(choice: Variant) -> String: + var choice_id := StringName(_definition_value(choice, &"choice_id", &"")) + var text_key := StringName(_definition_value(choice, &"text_key", choice_id)) + var title := String(EVENT_CHOICE_NAMES.get(text_key, String(choice_id))) + var cost_value: Variant = _definition_value(choice, &"cost", {}) + if not cost_value is Dictionary or cost_value.is_empty(): + return title + var cost_parts: Array[String] = [] + var cost_keys: Array = cost_value.keys() + cost_keys.sort_custom(func(left: Variant, right: Variant) -> bool: + return String(left) < String(right) + ) + for raw_key: Variant in cost_keys: + cost_parts.append("%s %d" % [String(raw_key), int(cost_value[raw_key])]) + return "%s(%s)" % [title, " / ".join(cost_parts)] + + +func _submit_foreground_event_choice( + event_instance_id: StringName, + choice_instance_id: StringName, + choice_id: StringName +) -> void: + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("イベント選択を送信できません。", false) + return + var idempotency_key := StringName( + "ui.event.choice.%s.%s" % [String(choice_instance_id), String(choice_id)] + ) + var result: Variant = _submit_ui_command(&"submit_event_choice", + { + &"event_instance_id": event_instance_id, + &"choice_instance_id": choice_instance_id, + &"choice_id": choice_id, + }, + &"player", + event_instance_id, + 100, + -1, + idempotency_key + ) + var succeeded: bool = result != null \ + and result.has_method(&"is_success") \ + and bool(result.call(&"is_success")) + _set_status( + "イベントの選択を確定しました。" if succeeded \ + else _command_failure_message(result) if result != null else "イベント選択に失敗しました。", + succeeded + ) + _refresh_dashboard() + _update_pause_and_speed() + + +func _operations_dashboard_text() -> String: + if _runtime == null or not _runtime.has_method(&"get_operations_report"): + return "" + var report_value: Variant = _runtime.call(&"get_operations_dashboard_report") if _runtime.has_method(&"get_operations_dashboard_report") else _runtime.call(&"get_operations_report") + if not report_value is Dictionary: + return "" + var report: Dictionary = report_value + if not bool(report.get(&"enabled", false)): + return "\n運営日次処理 未開始" + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = coordinator_value if coordinator_value is Dictionary else {} + var terminal := StringName(coordinator.get(&"terminal_state", &"RUNNING")) + var state_text := "営業中" + if terminal == &"BANKRUPT": + state_text = "ゲーム終了" + elif bool(report.get(&"runtime_failure_latched", false)): + state_text = "処理失敗・再試行待ち" + elif bool(report.get(&"tick_blocked", false)): + state_text = "確認待ち" + var day_profit_text := "未確定" + var summaries_value: Variant = coordinator.get(&"day_summaries", []) + if summaries_value is Array and not summaries_value.is_empty(): + var latest_value: Variant = summaries_value.back() + if latest_value is Dictionary: + day_profit_text = "G %+d" % int(latest_value.get(&"net_profit", 0)) + var old_financial_history: bool = summaries_value is Array and not summaries_value.is_empty() and int(summaries_value.back().get(&"day", 0)) <= int(coordinator.get(&"legacy_history_last_day", 0)) + return ( + "\n行動点 %d / 6  未払経費 G %d\n" % [ + int(report.get(&"personal_action_points", 0)), + int(report.get(&"arrears_total", coordinator.get(&"arrears_total", 0))), + ] + + "運営状態 %s  前日損益 %s" % [state_text, day_profit_text] + + "\n%s %d / 3回" % ["旧版の連続未払い" if old_financial_history else "連続赤字", int(coordinator.get(&"consecutive_insolvency_count", 0))] + ) + + +func _refresh_selected_entity() -> void: + related_facility_picker.visible = false + selected_elevator_controls.visible = false + selected_detail_label.tooltip_text = "" + if _selected_entity_id.is_empty(): + _move_selected_detail_first(false) + selected_entity_icon.texture = null + selected_heading_label.text = "掘削モード" if _excavation_mode else "選択中の施設" + selected_detail_label.text = ( + "地下のマスにカーソルを合わせると、費用と所要時間を確認できます。" + if _excavation_mode else "地図上の施設をクリックすると、営業状態を確認できます。" + ) + return + if _selected_entity_kind == &"construction": + _move_selected_detail_first(false) + _refresh_selected_construction() + return + if _selected_entity_kind == &"excavation": + _move_selected_detail_first(false) + _refresh_selected_excavation() + return + if _refresh_selected_stairs(): + return + var shaft := _selected_elevator_report() + if not shaft.is_empty(): + _refresh_selected_elevator(shaft) + return + _move_selected_detail_first(false) + var facility_service: Variant = _service(&"facility_service") + if facility_service == null or not facility_service.has_method(&"get_instance"): + selected_detail_label.text = "施設サービスを利用できません。" + return + var facility: Variant = facility_service.call(&"get_instance", _selected_entity_id) + if facility == null: + var construction_facility := _construction_facility(_selected_entity_id) + if construction_facility.is_empty(): + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "選択中の施設" + selected_detail_label.text = "選択していた施設は現在の世界に存在しません。" + selected_entity_icon.texture = null + return + var construction_definition_id := StringName(construction_facility.get(&"definition_id", &"")) + var infrastructure_definition: Variant = _facility_definition(construction_definition_id) + selected_heading_label.text = String(_definition_value(infrastructure_definition, &"display_name", construction_definition_id)) + selected_entity_icon.texture = visual_theme.facility_icon(construction_definition_id) if visual_theme != null else null + selected_detail_label.text = "設備完成\n" + _facility_upkeep_text(_selected_entity_id) + return + var definition: Variant = _facility_definition(facility.definition_id) + selected_entity_icon.texture = visual_theme.facility_icon(facility.definition_id) if visual_theme != null else null + related_facility_picker.clear() + related_facility_picker.add_item("関連施設:指定なし") + related_facility_picker.set_item_metadata(0, &"") + for related: FacilityInstance in _runtime.facility_service.get_instances(): + if related.instance_id == facility.instance_id: + continue + var related_definition: Variant = _facility_definition(related.definition_id) + related_facility_picker.add_item("関連施設:%s (%d, %d)" % [String(_definition_value(related_definition, &"display_name", related.definition_id)), related.origin.x, related.origin.y]) + var index := related_facility_picker.item_count - 1 + related_facility_picker.set_item_metadata(index, related.instance_id) + if related.instance_id == facility.related_facility_id: + related_facility_picker.select(index) + related_facility_picker.visible = true + var display_name := String(_definition_value(definition, &"display_name", facility.definition_id)) + var state_index := clampi(int(facility.state), 0, FACILITY_STATE_NAMES.size() - 1) + var reasons := _facility_reasons(facility.state_reasons) + var queue_count: int = facility.queue_entries.size() + var service_count: int = facility.active_services.size() + var staff_count: int = facility.active_staff_ids.size() + var occupied_slots: int = service_count + var related_name := "なし" + if not facility.related_facility_id.is_empty(): + var related: FacilityInstance = _runtime.facility_service.get_instance(facility.related_facility_id) + related_name = "撤去済み" if related == null else "%s (%d, %d)" % [String(_definition_value(_facility_definition(related.definition_id), &"display_name", related.definition_id)), related.origin.x, related.origin.y] + selected_heading_label.text = display_name + selected_detail_label.text = ( + "状態 %s%s\n" % [FACILITY_STATE_NAMES[state_index], "(%s)" % reasons if not reasons.is_empty() else ""] + + "待機列 %d / %d  サービス %d / %d\n" % [queue_count, int(facility.queue_capacity), occupied_slots, int(facility.capacity)] + + "稼働区分 %s\n顧客同時利用 %d / %s\n" % [["未稼働", "稼働", "休止"][facility.operation_state], facility.customer_count(), str(facility.customer_capacity) if facility.customer_capacity >= 0 else "未設定"] + + "関連施設 %s(%s)\n従業員上限 %d\n" % [related_name, "稼働条件成立" if facility.related_facility_operating else "未成立", facility.staff_max] + + "配置人員 %d / 必要 %d  耐久 %d%%\n" % [staff_count, int(facility.staff_min), int(facility.durability) / 100] + + _facility_upkeep_text(_selected_entity_id) + + "基本料金 G %d  所要 %d分\n" % [int(facility.base_price), int(facility.base_service_minutes)] + + "提供サービス %s\n" % {&"": "未設定", &"appetite": "食欲", &"relaxation": "リラックス欲", &"sexual": "性欲", &"gambling": "ギャンブル欲"}.get(facility.service_attribute, "未設定") + + "座標 (%d, %d) %s" % [facility.origin.x, facility.origin.y, "FRONT" if facility.origin.z == 0 else "BACK"] + ) + + +func _facility_upkeep_text(facility_id: StringName) -> String: + if _runtime == null or not _runtime.has_method(&"get_facility_upkeep_report"): + return "" + var report: Dictionary = _runtime.call(&"get_facility_upkeep_report", facility_id) + var pending := float(report[&"unsettled_units"]) / float(report[&"gold_denominator"]) + return "維持費 %dG / 日\n精算前 約%.2fG 未払い %dG\n閉店・休止中も計上\n完成から撤去までの時間で按分\n" % [int(report[&"daily_rate"]), pending, int(report[&"arrears"])] + + +func _on_related_facility_selected(index: int) -> void: + if _runtime == null or _selected_entity_id.is_empty(): + return + var related_id := StringName(related_facility_picker.get_item_metadata(index)) + var result: Variant = _submit_ui_command(&"set_facility_conditions", {&"facility_id": _selected_entity_id, &"conditions": {&"related_facility_id": related_id}}) + if result.is_success() and not result.details.has(&"instance_id"): + _pending_related_commands[int(result.command_id)] = true + _set_status("関連施設の変更を受け付けました。" if result.is_success() else result.message, result.is_success(), true) + _refresh_selected_entity() + + +func _refresh_selected_stairs() -> bool: + var from_cell := Vector3i.ZERO + var to_cell := Vector3i.ZERO + var definition_id: StringName = &"" + var legacy := _selected_entity_kind == &"legacy_stairs" + var legacy_access: StringName = &"" + if legacy: + for stair: Dictionary in world_preview.get_legacy_stair_records(): + if StringName(stair[&"id"]) == _selected_entity_id: + from_cell = stair[&"from"] + legacy_access = StringName(stair.get(&"access_id", &"")) + to_cell = stair[&"to"] + definition_id = &"guest_stairs" if from_cell.z == 0 else &"service_stairs" + break + else: + var facility := _construction_facility(_selected_entity_id) + definition_id = StringName(facility.get(&"definition_id", &"")) + if definition_id not in [&"guest_stairs", &"service_stairs"]: + return false + var origin: Variant = facility.get(&"origin", Vector3i.ZERO) + if origin is Array and origin.size() == 3: + origin = Vector3i(int(origin[0]), int(origin[1]), int(origin[2])) + to_cell = origin + from_cell = to_cell+Vector3i(0,-1,0) + if definition_id.is_empty(): + return false + _move_selected_detail_first(true) + var staff_only := legacy_access == &"staff" if legacy else definition_id == &"service_stairs" + selected_heading_label.text = "既設の階段" if legacy else ("業務用階段" if staff_only else "客用階段") + selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null + selected_detail_label.text = ( + "接続階 %s ↔ %s\n" % [_elevator_floor_name(mini(from_cell.y,to_cell.y)), _elevator_floor_name(maxi(from_cell.y,to_cell.y))] + + "利用者 %s\n" % ("従業員専用・物資運搬可" if staff_only else "客・従業員用") + + "階段で昇り降りします。魔力は不要です。\n" + + "座標 (%d, %d) %s" % [to_cell.x,maxi(from_cell.y,to_cell.y),"BACK" if to_cell.z == 1 else "FRONT"] + ) + return true + + +func _selected_elevator_report() -> Dictionary: + if _selected_entity_kind != &"facility" or _selected_entity_id.is_empty(): + return {} + var transport: Variant = _service(&"elevators") + if transport == null or not transport.has_method(&"get_report"): + return {} + for shaft: Dictionary in transport.call(&"get_report").get(&"shafts", []): + if (shaft.get(&"module_ids", []) as Array).has(_selected_entity_id): + return shaft + return {} + + +func _refresh_selected_elevator(shaft: Dictionary) -> void: + _move_selected_detail_first(true) + var construction_facility := _construction_facility(_selected_entity_id) + var definition_id := StringName(construction_facility.get(&"definition_id", &"guest_elevator")) + var definition: Variant = _facility_definition(definition_id) + selected_heading_label.text = String(_definition_value(definition, &"display_name", "昇降機")) + selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null + var powered := bool(shaft.get(&"powered", true)) + var operator_open := bool(shaft.get(&"operator_open", true)) + var state_text := String(ELEVATOR_PHASE_NAMES.get(StringName(shaft.get(&"phase", &"IDLE")), "待機")) + if not operator_open: + state_text = "手動停止" + elif not powered: + state_text = "魔力供給なし" + var floor_index := int(shaft.get(&"current_floor", 0)) + var target_index := int(shaft.get(&"boarding_floor", floor_index) if StringName(shaft.get(&"phase", &"IDLE")) == &"PICKUP" else shaft.get(&"target_floor", floor_index)) + var stops: Array = shaft.get(&"stops", []) + var stop_text := "—" + if not stops.is_empty(): + stop_text = "%s 〜 %s" % [_elevator_floor_name(stops.front().y), _elevator_floor_name(stops.back().y)] + selected_detail_label.text = ( + "状態 %s\n" % state_text + + "現在階 %s  目的階 %s\n" % [_elevator_floor_name(floor_index), _elevator_floor_name(target_index)] + + "乗客 %d / %d人  待機 %d人\n" % [int(shaft.get(&"passenger_count", 0)), int(shaft.get(&"capacity", 0)), int(shaft.get(&"waiting_count", 0))] + + "停止階 %s\n" % stop_text + + "魔力 %s  %s" % ["供給中" if powered else "不足", "客・従業員用" if definition_id == &"guest_elevator" else "従業員・物資用"] + ) + selected_detail_label.text += _elevator_wait_text(StringName(shaft.get(&"shaft_id", &""))) + selected_elevator_controls.visible = true + elevator_operation_button.disabled = _pending_elevator_commands.values().has(shaft.get(&"shaft_id", &"")) + elevator_operation_button.text = "指示を受理済み…" if elevator_operation_button.disabled else ("昇降機を停止" if operator_open else "運行を再開") + elevator_operation_hint.text = "停止すると最寄りの階で降車し、待機中の呼び出しを解除します。" if operator_open else ("再開すると新しい呼び出しを受け付けます。" if powered else "魔力が供給されると運行を再開できます。") + + +func _elevator_wait_text(shaft_id: StringName) -> String: + selected_detail_label.tooltip_text = "保持している直近256件の出来事のうち、過去60分の乗車を集計。現在の最長待ちは、いま並んでいる人の待ち時間です。" + if _runtime == null or not _runtime.has_method(&"get_elevator_wait_statistics"): + return "\n最近の乗車記録 計測待ち" + var statistics: Dictionary = _runtime.call(&"get_elevator_wait_statistics", shaft_id) + var samples := int(statistics.get(&"sample_count", 0)) + var text := "\n最近の乗車記録 %d人" % samples + if samples == 0: + text += "(計測待ち)" + else: + text += "\n平均待ち %.1f分 / 95%% %.1f分" % [float(statistics.get(&"mean_wait_minutes",0)),float(statistics.get(&"p95_wait_minutes",0))] + return text+"\n現在の最長待ち %d分" % int(statistics.get(&"oldest_wait_minutes",0)) + + +func _move_selected_detail_first(first: bool) -> void: + var column := selected_heading_label.get_parent() + var selected_row := selected_detail_label.get_parent() + # The transport action fits without scrolling; other management views keep + # their existing staff-first layout. + var desired_index := 0 if first else column.get_child_count()-4 + if selected_heading_label.get_index() == desired_index: + return + column.move_child(selected_heading_label, 0 if first else -1) + column.move_child(selected_row, 1 if first else -1) + column.move_child(selected_elevator_controls, 2 if first else -1) + column.move_child(related_facility_picker, 3 if first else -1) + + +func _elevator_floor_name(floor_index: int) -> String: + return "%dF" % (floor_index+1) if floor_index >= 0 else "B%d" % -floor_index + + +func _on_elevator_operation_pressed() -> void: + var shaft := _selected_elevator_report() + if shaft.is_empty() or _runtime == null: + return + var shaft_id := StringName(shaft[&"shaft_id"]) + if _pending_elevator_commands.values().has(shaft_id): + return + var key := StringName("ui.elevator.%08d" % _elevator_sequence) + _elevator_sequence += 1 + var open_value := not bool(shaft.get(&"operator_open", true)) + var result: Variant = _submit_ui_command(&"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": open_value}, &"player", &"", 50, -1, key) + if result == null or not result.is_success(): + _set_status("昇降機の指示を反映できませんでした。", false) + return + if not result.details.has(&"shaft_id"): + _pending_elevator_commands[int(result.command_id)] = shaft_id + _set_status("昇降機の運行指示を受理しました。", true) + else: + var message := "昇降機を停止しました。" + if open_value: + message = "昇降機の運行を再開しました。" if bool(shaft.get(&"powered", true)) else "運行を許可しました。魔力の供給待ちです。" + _set_status(message, true) + _refresh_selected_entity() + + +func _synchronize_elevator_pending() -> void: + var queue: Variant = _service(&"command_queue") + if queue == null: + return + if queue.pending_count() == 0: + _pending_elevator_commands.clear() + return + _pending_elevator_commands.clear() + for command: Dictionary in queue.create_snapshot().get("pending", []): + if StringName(command.get("command_type", &"")) == &"set_elevator_operation": + _pending_elevator_commands[int(command.get("command_id", 0))] = StringName((command.get("payload", {}) as Dictionary).get(&"shaft_id", &"")) + + +func _refresh_selected_construction() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_job"): + selected_detail_label.text = "建設サービスを利用できません。" + return + var job: Variant = construction.call(&"get_job", _selected_entity_id) + if job == null: + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "選択中の施設" + selected_detail_label.text = "選択していた工事は現在の世界に存在しません。" + selected_entity_icon.texture = null + return + var definition: Variant = _facility_definition(job.definition_id) + selected_entity_icon.texture = visual_theme.facility_icon(job.definition_id) if visual_theme != null else null + var display_name := String(_definition_value(definition, &"display_name", job.definition_id)) + var state_names := ["計画済み", "建設中", "完成", "中止", "停止中"] + var job_state := clampi(int(job.state), 0, state_names.size() - 1) + selected_heading_label.text = "%s(工事)" % display_name + selected_detail_label.text = ( + "状態 %s\n" % state_names[job_state] + + "進捗 %d%%  予定 %d分\n" % [int(job.progress) / 100, int(job.build_minutes)] + + "座標 (%d, %d) %s\n" % [job.origin.x, job.origin.y, "FRONT" if int(job.layer) == 0 else "BACK"] + + ("停止理由 %s" % String(job.state_reason) if not String(job.state_reason).is_empty() else "建設はシミュレーション時間とともに進みます。") + ) + + +func _select_excavation_job(job_id: StringName) -> void: + if job_id.is_empty(): + return + _selected_entity_id = job_id + _selected_entity_kind = &"excavation" + world_preview.selected_entity_id = &"" + world_preview.clear_hovered_cell() + _refresh_selected_excavation() + + +func _refresh_selected_excavation() -> void: + selected_entity_icon.texture = visual_theme.control_icon(&"excavate") if visual_theme != null else null + var excavation: Variant = _service(&"excavation_service") + if excavation == null or not excavation.has_method(&"get_job"): + selected_detail_label.text = "掘削サービスを利用できません。" + return + var job_value: Variant = excavation.call(&"get_job", _selected_entity_id) + if not job_value is Dictionary or job_value.is_empty(): + _selected_entity_id = &"" + _selected_entity_kind = &"" + selected_heading_label.text = "掘削モード" if _excavation_mode else "選択中の施設" + selected_detail_label.text = "選択していた掘削作業は現在の世界に存在しません。" + return + var job: Dictionary = job_value + var state := StringName(job.get(&"state", &"")) + var state_text := "掘削中" + if state == &"PAUSED": + state_text = "一時停止" + elif state == &"COMPLETED": + state_text = "完了" + var position: Vector2i = job.get(&"position", Vector2i.ZERO) + var cost_value: Variant = job.get(&"cost", {}) + var cost: Dictionary = cost_value if cost_value is Dictionary else {} + var guidance := "両レイヤーが開通しました。" + if state == &"ACTIVE": + guidance = "掘削はシミュレーション時間とともに進みます。" + elif state == &"PAUSED": + guidance = "停止理由 %s" % String(job.get(&"state_reason", "")) + selected_heading_label.text = "地下掘削(%d, %d)" % [position.x, position.y] + selected_detail_label.text = ( + "状態 %s  進捗 %d%%\n" % [state_text, clampi(int(job.get(&"progress", 0)) / 100, 0, 100)] + + "所要 %d分  費用 G %d / 資材 %d\n" % [ + int(job.get(&"build_minutes", 0)), + int(cost.get(&"gold", cost.get("gold", 0))), + int(cost.get(&"materials", cost.get("materials", 0))), + ] + + "対象 FRONT / 表層・BACK / 裏層\n" + + guidance + ) + + +func _preview_failure_message(report: Dictionary) -> String: + var codes_value: Variant = report.get(&"error_codes", []) + if codes_value is Array and not codes_value.is_empty(): + var code := String(codes_value[0]) + if PREVIEW_REASON_NAMES.has(code): + return "配置不可: %s" % PREVIEW_REASON_NAMES[code] + var code_reasons: Variant = report.get(&"reasons", []) + if code_reasons is Array and not code_reasons.is_empty(): + var reason: Variant = code_reasons[0] + for candidate: Variant in code_reasons: + if candidate is Dictionary and String(candidate.get(&"code", "")) == code: + reason = candidate + break + if reason is Dictionary: + var message := String(reason.get(&"message", "")) + if not message.is_empty(): + return "配置不可: %s" % message + var reasons_value: Variant = report.get(&"reasons", []) + if reasons_value is Array and not reasons_value.is_empty() and reasons_value[0] is Dictionary: + return "配置不可: %s" % String(reasons_value[0].get(&"message", "条件を満たしていません")) + return "このマスには配置できません。" + + +func _excavation_failure_message(report: Dictionary) -> String: + var errors_value: Variant = report.get(&"errors", []) + var reason := "条件を満たしていません" + if errors_value is Array and not errors_value.is_empty(): + var code := StringName(errors_value[0]) + reason = String(EXCAVATION_REASON_NAMES.get(code, String(code))) + if code == &"INSUFFICIENT_RESOURCES": + var missing_value: Variant = report.get(&"missing_resources", {}) + if missing_value is Dictionary and not missing_value.is_empty(): + var missing: Dictionary = missing_value + reason += "(不足: G %d / 資材 %d)" % [ + int(missing.get(&"gold", missing.get("gold", 0))), + int(missing.get(&"materials", missing.get("materials", 0))), + ] + return "掘削不可: %s%s" % [reason, _excavation_cost_and_time(report)] + + +func _excavation_cost_and_time(report: Dictionary) -> String: + var cost_value: Variant = report.get(&"cost", {}) + var cost: Dictionary = cost_value if cost_value is Dictionary else {} + var minutes := int(report.get(&"build_minutes", 0)) + if cost.is_empty() and minutes <= 0: + return "" + return "(費用 G %d / 資材 %d・所要 %d分)" % [ + int(cost.get(&"gold", cost.get("gold", 0))), + int(cost.get(&"materials", cost.get("materials", 0))), + minutes, + ] + + +func _excavation_gate_message() -> String: + var progression: Variant = _service(&"progression_service") + if progression == null: + return "世界樹ランクを確認できません" + var current_rank := int(_definition_value(progression, &"current_rank", 1)) + if current_rank < 2: + return "世界樹ランク2で地下掘削が解禁されます(現在ランク%d)" % current_rank + if _runtime == null or not _runtime.has_method(&"get_operations_report"): + return "運営状態を確認できません" + var report_value: Variant = _runtime.call(&"get_operations_report") + if not report_value is Dictionary: + return "運営状態を確認できません" + var report: Dictionary = report_value + if not bool(report.get(&"enabled", false)): + return "本番運営が始まるまで掘削できません" + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = coordinator_value if coordinator_value is Dictionary else {} + if StringName(coordinator.get(&"terminal_state", &"RUNNING")) == &"BANKRUPT": + return "資金破綻により新しい掘削を開始できません" + if bool(coordinator.get(&"external_quarantined", false)): + return "運営データの復旧が必要なため掘削を開始できません" + var arrears := int(report.get(&"arrears_total", coordinator.get(&"arrears_total", 0))) + if arrears > 0: + return "未払経費 G %dを清算するまで掘削できません" % arrears + return "" + + +func _excavation_command_failure_message(result: Variant) -> String: + var details: Variant = result.details + if details is Dictionary: + var report_value: Variant = details.get(&"report", null) + if report_value is Dictionary: + return _excavation_failure_message(report_value) + var reason := StringName(details.get(&"reason", &"")) + match reason: + &"ARREARS_REMAIN", &"UNPAID_EXPENSES": + return "掘削不可: 未払経費 G %dを清算してください" % int(details.get(&"arrears_total", 0)) + &"BANKRUPT": + return "掘削不可: 資金破綻により新しい工事は開始できません" + &"EXTERNAL_ROLLBACK_FAILED": + return "掘削不可: 運営データの復旧が必要です" + if details.has(&"required_rank"): + return "掘削不可: 世界樹ランク%dが必要です(現在ランク%d)" % [ + int(details.get(&"required_rank", 2)), + int(details.get(&"current_rank", 1)), + ] + if int(result.code) == int(CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY): + return "この掘削指示はすでに受理されています。" + return _command_failure_message(result) + + +func _command_failure_message(result: Variant) -> String: + var message := String(result.message) + var details: Variant = result.details + if details is Dictionary: + var report_value: Variant = details.get(&"placement_report", null) + if report_value is Dictionary: + return _preview_failure_message(report_value) + return "操作に失敗しました: %s" % (message if not message.is_empty() else "不明なエラー") + + +func _save_manual() -> void: + _save_manual_at_path(manual_save_path, "手動保存") + + +func _save_manual_at_path(save_path: String, display_name: String) -> bool: + if _runtime == null or not _runtime.has_method(&"save_to_path"): + _set_status("保存サービスへ接続できません。", false) + return false + var directory_error := DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(save_path).get_base_dir()) + if directory_error != OK and directory_error != ERR_ALREADY_EXISTS: + _set_status("保存フォルダを作成できませんでした。", false) + return false + var metadata := {&"display_name": display_name, &"interface_selection": _capture_interface_selection()} + var thumbnail := _capture_manual_thumbnail_png() + if not thumbnail.is_empty(): metadata[&"thumbnail_png"] = thumbnail + var result: Variant = _runtime.call(&"save_to_path", save_path, metadata) + if result is Dictionary and bool(result.get(&"ok", false)): + manual_save_path = save_path + _set_status("手動セーブを保存しました。", true) + return true + if result is Dictionary and StringName(result.get(&"code", &"")) == &"FORMAT_MIGRATION_REQUIRED": + _set_status("元の保存を残すため、別のスロットへ保存してください。", false) + _ensure_manual_save_slots().show_new_save(save_path) + else: + _set_status("保存に失敗しました: %s" % _result_message(result), false) + return false + + +func _capture_manual_thumbnail_png() -> PackedByteArray: + # Capture the existing rendered game; no simulation update or new art source. + # Dummy/headless rendering retains the existing SaveService fallback. + if DisplayServer.get_name() == "headless": return PackedByteArray() + var viewport := get_viewport() + if viewport == null: return PackedByteArray() + var texture := viewport.get_texture() + if texture == null: return PackedByteArray() + var image := texture.get_image() + if image == null or image.is_empty(): return PackedByteArray() + var scale := minf(320.0 / float(image.get_width()), 180.0 / float(image.get_height())) + var width := maxi(1, roundi(float(image.get_width()) * scale)) + var height := maxi(1, roundi(float(image.get_height()) * scale)) + image.resize(width, height, Image.INTERPOLATE_LANCZOS) + return image.save_png_to_buffer() + + +func _load_manual(strict: bool = false) -> void: + _load_manual_at_path(manual_save_path, strict) + + +func _load_manual_at_path(save_path: String, strict: bool = false) -> void: + if _runtime == null or ( + not _runtime.has_method(&"load_with_recovery") + and not _runtime.has_method(&"load_from_path") + ): + _set_status("読込サービスへ接続できません。", false) + return + var result: Variant = ( + _runtime.call(&"load_with_recovery", save_path) + if not strict and _runtime.has_method(&"load_with_recovery") + else _runtime.call(&"load_from_path", save_path) + ) + if result is Dictionary and bool(result.get(&"ok", false)): + manual_save_path = save_path + # Real Runtime already emitted its common completion notification. + if not _runtime.has_signal(&"save_operation_completed"): + _apply_loaded_interface(result) + _set_status("手動セーブを読み込みました。", true) + else: + _set_status("読み込みに失敗しました: %s" % _result_message(result), false) + + +func _ensure_manual_save_slots() -> Variant: + if manual_save_slots_dialog == null: + manual_save_slots_dialog = ManualSaveSlotsDialogScript.new() + manual_save_slots_dialog.connect(&"new_slot_requested", _save_new_manual_slot) + manual_save_slots_dialog.connect(&"load_slot_requested", _load_selected_manual_slot) + manual_save_slots_dialog.visibility_changed.connect(_update_modal_input_guard) + add_child(manual_save_slots_dialog) + return manual_save_slots_dialog + + +func _save_new_manual_slot(path: String, display_name: String) -> void: + if not ManualSaveSlotsDialogScript.is_fresh_slot(path) or path.get_base_dir() != manual_save_path.get_base_dir(): + _set_status("新しい保存先を用意できませんでした。元の保存は変更していません。", false) + return + _save_manual_at_path(path, display_name) + + +func _show_manual_load_slots() -> void: + _ensure_manual_save_slots().show_load_slots(manual_save_path) + + +func _load_selected_manual_slot(path: String) -> void: + if path.get_base_dir() != manual_save_path.get_base_dir() or not ManualSaveSlotsDialogScript.is_owned_slot_name(path.get_file()): + _set_status("この保存先を読み込めません。", false) + return + _load_manual_at_path(path) + +func _capture_interface_selection() -> Dictionary: + var selection := {&"version": 1, &"kind": String(_selected_entity_kind), &"id": String(_selected_entity_id), &"staff_open": _staff_management_panel_open, &"guest_open": false} + selection[&"product_open"] = _supply_panel_open + if supply_management_panel != null: + selection[&"product_tab"] = String(supply_management_panel.selected_tab) + if staff_management_panel != null: + selection[&"staff_id"] = String(staff_management_panel.get("selected_staff_id")) + if guest_observer != null: + var panel := guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + if panel != null: + selection[&"guest_id"] = String(panel.selected_guest_id) + selection[&"guest_open"] = guest_observer.visible + if defense_manager != null: + var panel := defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + if panel != null: + selection[&"defense_id"] = String(panel.selected_raid_id) + selection[&"defense_open"] = defense_manager.visible + return selection + + +func _on_checkpoint_presentation_requested(presentation: Dictionary) -> void: + presentation[&"interface_selection"] = _capture_interface_selection() + + +func _on_save_operation_completed(result: Dictionary) -> void: + if not bool(result.get(&"ok", false)) or StringName(result.get(&"operation", &"")) not in [&"load", &"recovery", &"recovery_candidate"]: + return + _apply_loaded_interface(result) + + +func _apply_loaded_interface(result: Dictionary) -> void: + if _audio_feedback != null: + _audio_feedback.begin_loaded_epoch(_current_audio_foreground()) + _pending_build_commands.clear() + _pending_demolition_commands.clear() + _set_demolition_mode(false, false) + _pending_excavation_commands.clear() + _pending_hire_commands.clear() + _pending_elevator_commands.clear() + _synchronize_ui_command_state() + if staff_management_panel != null: + staff_management_panel.refresh() + if supply_management_panel != null: + supply_management_panel.refresh() + if business_management_panel != null: + business_management_panel.refresh() + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + world_preview.set_runtime(_runtime) + _set_layer(GameState.active_layer) + _restore_interface_selection(result.get(&"manifest", {})) + _restore_actor_interface_selection(result.get(&"manifest", {})) + _restore_defense_interface_selection(result.get(&"manifest", {})) + _restore_product_interface_selection(result.get(&"manifest", {})) + _refresh_dashboard() + _update_pause_and_speed() + _bind_domain_event_stream() + + +func _valid_interface_selection_version(value: Variant) -> bool: + # JSON numbers may be decoded as floats; booleans are never versions. + return typeof(value) in [TYPE_INT, TYPE_FLOAT] and value == 1 + + +func _restore_interface_selection(manifest: Variant) -> void: + # Presentation metadata is optional. It must never invalidate gameplay, + # and recovery must use the manifest of the actually loaded candidate. + if not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)): + return + var kind_value: Variant = selection.get("kind", "") + var id_value: Variant = selection.get("id", "") + if not kind_value is String or not id_value is String or id_value.is_empty() or id_value.length() > 256: + return + var kind := StringName(kind_value) + var entity_id := StringName(id_value) + var exists := false + match kind: + &"facility": + exists = _service(&"facility_service").get_instance(entity_id) != null or not _construction_facility(entity_id).is_empty() + &"construction": + exists = _service(&"construction_service").get_job(entity_id) != null + &"excavation": + exists = not _service(&"excavation_service").get_job(entity_id).is_empty() + &"legacy_stairs": + for stair: Dictionary in world_preview.get_legacy_stair_records(): + if StringName(stair.get(&"id", &"")) == entity_id: + exists = true + break + if not exists: + return + _selected_entity_kind = kind + _selected_entity_id = entity_id + world_preview.selected_entity_id = entity_id if kind != &"excavation" else &"" + _set_management_panel_open(true) + + +func _restore_actor_interface_selection(manifest: Variant) -> void: + # Only successful gameplay loads reach here. Reset stale live selections, + # then restore each optional inspector independently of the map selection. + _set_staff_management_panel_open(false) + if staff_management_panel != null: + staff_management_panel.call("clear_selection") + if guest_observer != null: + guest_observer.hide() + var old_panel := guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + if old_panel != null: + old_panel.clear_selection() + if not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)): + return + var staff_id: Variant = selection.get("staff_id", "") + if staff_id is String and not staff_id.is_empty() and staff_id.length() <= 256 and _service(&"staff_service").has_staff(StringName(staff_id)): + staff_management_panel.call("select_staff", StringName(staff_id)) + if selection.get("staff_open", false) is bool and selection.get("staff_open", false): + _activate_management_mode(&"staff") + var guest_id: Variant = selection.get("guest_id", "") + if guest_id is String and not guest_id.is_empty() and guest_id.length() <= 256 and _service(&"guest_service").has_guest(StringName(guest_id)): + var panel := _ensure_guest_observer() + panel.selected_guest_id = StringName(guest_id) + panel.refresh() + if selection.get("guest_open", false) is bool and selection.get("guest_open", false): + _show_guest_observer() + + +func _restore_product_interface_selection(manifest: Variant) -> void: + # Additive optional presentation: old packages keep their existing behavior. + # No purchase draft, quantity, policy, assignment or command is restored here. + if supply_management_panel == null or not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary: + return + var version: Variant = selection.get("version", 0) + if not (version is int or version is float) or version != 1: + return + var open_value: Variant = selection.get("product_open", null) + var tab_value: Variant = selection.get("product_tab", null) + if not open_value is bool or not tab_value is String: + return + if tab_value not in ["purchase", "kitchen", "delivery"]: + return + # A captured main panel cannot be both staff and product. Ignore a + # contradictory optional product claim without overriding restored staff. + if open_value and selection.get("staff_open", false) is bool and selection.get("staff_open", false): + return + supply_management_panel.select_tab(StringName(tab_value)) + if open_value: + _activate_management_mode(&"product") + else: + _set_supply_panel_open(false) + + +func _result_message(result: Variant) -> String: + if result is Dictionary: + var message := String(result.get(&"message", result.get(&"code", "保存データがありません"))) + return message if not message.is_empty() else "保存データがありません" + return "サービスから応答がありません" + + +func _synchronize_ui_command_state() -> void: + if _runtime == null: + return + var queue: Variant = _runtime.get(&"command_queue") + if queue == null: + return + if queue.has_method(&"get_accepted_command_metadata"): + var metadata_value: Variant = queue.call(&"get_accepted_command_metadata") + if metadata_value is Array: + for raw_metadata: Variant in metadata_value: + if not raw_metadata is Dictionary: + continue + _advance_ui_sequence_from_key(String(raw_metadata.get("idempotency_key", ""))) + if not queue.has_method(&"create_snapshot"): + return + var snapshot_value: Variant = queue.call(&"create_snapshot") + if not snapshot_value is Dictionary: + return + var pending_value: Variant = snapshot_value.get("pending", []) + if not pending_value is Array: + return + for raw_pending: Variant in pending_value: + if not raw_pending is Dictionary: + continue + var command_id := int(raw_pending.get("command_id", 0)) + var command_type := StringName(raw_pending.get("command_type", &"")) + var payload_value: Variant = raw_pending.get("payload", {}) + var payload: Dictionary = payload_value if payload_value is Dictionary else {} + if command_id <= 0: + continue + match command_type: + &"set_elevator_operation": + _pending_elevator_commands[command_id] = StringName(payload.get(&"shaft_id", &"")) + &"build_facility": + var definition_id := StringName(payload.get(&"definition_id", payload.get("definition_id", &""))) + if not definition_id.is_empty(): + _pending_build_commands[command_id] = definition_id + &"start_excavation": + var cell_value: Variant = payload.get(&"cell", payload.get("cell", null)) + if cell_value is Vector3i: + _pending_excavation_commands[command_id] = Vector2i(cell_value.x, cell_value.y) + &"hire_staff_candidate": + var candidate_id := StringName(payload.get(&"candidate_id", payload.get("candidate_id", &""))) + if not candidate_id.is_empty(): + _pending_hire_commands[command_id] = candidate_id + + +func _advance_ui_sequence_from_key(key: String) -> void: + if key.is_empty(): + return + var parts := key.split(".") + if parts.is_empty(): + return + var suffix := String(parts[parts.size() - 1]) + if not suffix.is_valid_int(): + return + var next_sequence := int(suffix) + 1 + if key.begins_with("ui.build."): + _build_sequence = maxi(_build_sequence, next_sequence) + elif key.begins_with("ui.excavation."): + _excavation_sequence = maxi(_excavation_sequence, next_sequence) + elif key.begins_with("ui.hire."): + _hire_sequence = maxi(_hire_sequence, next_sequence) + elif key.begins_with("ui.elevator."): + _elevator_sequence = maxi(_elevator_sequence, next_sequence) + + +var _dashboard_refresh_pending := false + + +func _queue_dashboard_refresh() -> void: + if _dashboard_refresh_pending: + return + _dashboard_refresh_pending = true + _flush_dashboard_refresh.call_deferred() + + +func _flush_dashboard_refresh() -> void: + if not _dashboard_refresh_pending or not is_inside_tree(): + return + _dashboard_refresh_pending = false + _refresh_dashboard() + + +func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void: + _synchronize_elevator_pending() + _queue_dashboard_refresh() + _update_pause_and_speed() + var operation: Dictionary = _snapshot.get(&"systems", {}).get(&"runtime", {}).get(&"operations", {}).get(&"coordinator", {}) + var summaries: Array = operation.get(&"day_summaries", []) + if summaries.is_empty(): + _last_presented_close_minute = 0 + return + var latest: Dictionary = summaries.back() + var closed_at := int(latest.get(&"closed_at", 0)) + if closed_at > _last_presented_close_minute: + _last_presented_close_minute = closed_at + operating_ledger_panel.select_latest_close() + _activate_management_mode(&"ledger") + var losses := int(latest.get(&"consecutive_insolvency_count", 0)) + if int(latest[&"day"]) <= int(operation.get(&"legacy_history_last_day", 0)): + _set_status("旧版で確定した決算を表示しています。", true, true) + return + _set_status("3回連続の赤字決算でゲーム終了です。帳簿を確認できます。" if String(latest.get(&"terminal_state", "")) == "BANKRUPT" else "第%d日 決算:利益 %+dG・連続赤字 %d回" % [int(latest[&"day"]), int(latest[&"net_profit"]), losses], true, true) + else: + _last_presented_close_minute = closed_at + + +func _on_command_completed(result: Variant) -> void: + if result == null: + return + if _pending_demolition_commands.has(int(result.command_id)): + _pending_demolition_commands.erase(int(result.command_id)) + _show_demolition_result(result) + return + if _pending_related_commands.has(int(result.command_id)): + _pending_related_commands.erase(int(result.command_id)) + _set_status("関連施設の設定を反映しました。" if result.is_success() else result.message, result.is_success()) + _refresh_selected_entity() + return + if _pending_elevator_commands.has(int(result.command_id)): + _pending_elevator_commands.erase(int(result.command_id)) + _set_status("昇降機の運行設定を反映しました。" if result.is_success() else "昇降機の運行設定を反映できませんでした。", result.is_success()) + _refresh_selected_entity() + return + if _pending_hire_commands.has(int(result.command_id)): + var candidate_id := StringName(_pending_hire_commands[int(result.command_id)]) + _pending_hire_commands.erase(int(result.command_id)) + var display_name := String(candidate_id) + for summary: Dictionary in _staff_candidate_summaries(): + if StringName(summary.get(&"candidate_id", &"")) == candidate_id: + display_name = String(summary.get(&"display_name", candidate_id)) + break + if bool(result.call(&"is_success")): + _set_status("%sを雇用しました。" % display_name, true) + else: + _set_status(_hire_failure_message(result), false) + _refresh_dashboard() + return + if _pending_excavation_commands.has(int(result.command_id)): + var position: Vector2i = _pending_excavation_commands[int(result.command_id)] + _pending_excavation_commands.erase(int(result.command_id)) + if bool(result.call(&"is_success")): + _select_excavation_job(StringName(result.details.get(&"job_id", &""))) + _set_status("掘削を開始しました(%d, %d / FRONT・BACK)。" % [position.x, position.y], true) + else: + _set_status(_excavation_command_failure_message(result), false) + return + if not _pending_build_commands.has(int(result.command_id)): + return + var definition_id := StringName(_pending_build_commands[int(result.command_id)]) + _pending_build_commands.erase(int(result.command_id)) + if bool(result.call(&"is_success")): + var definition: Variant = _facility_definition(definition_id) + _set_status("%sの建設を開始しました。" % String(_definition_value(definition, &"display_name", definition_id)), true) + else: + _set_status(_command_failure_message(result), false) + + +func _set_layer(value: int) -> void: + var normalized := clampi(value, 0, 1) + if is_instance_valid(_paired_bgm): + _paired_bgm.set_layer(normalized) + if world_preview != null: + world_preview.layer_mode = normalized + GameState.active_layer = normalized + if front_button != null: + front_button.set_pressed_no_signal(normalized == 0) + if back_button != null: + back_button.set_pressed_no_signal(normalized == 1) + for label: Label in [dashboard_summary_label, staff_candidate_summary_label, selected_detail_label, elevator_operation_hint]: + if label != null: LayerSkin.register_text(label, normalized) + # Styles are registered in the saved layer during construction. + # Reapplying them before the initial container layout causes extra sizing. + if _interface_layer >= 0 and _interface_layer != normalized: + LayerSkin.apply_tree(self, normalized) + if rank_dialog != null: rank_dialog.apply_layer_skin(normalized) + if event_log_dialog != null: event_log_dialog.apply_layer_skin(normalized) + if manual_save_slots_dialog != null: manual_save_slots_dialog.apply_layer_skin(normalized) + _interface_layer = normalized + if foreground_event_label != null: + foreground_event_label.add_theme_color_override("font_color", Color("ffb498") if normalized == 1 else Color("8f3f29")) + var background := get_node_or_null("InterfaceBackground") as ColorRect + if background != null: + background.color = Color("10191e") if normalized == 1 else _visual_color(&"window_background", Color("24170f")) + EventBus.layer_changed.emit(normalized) + + +func _toggle_pause() -> void: + var event_service: Variant = _service(&"event_service") + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + if foreground != null and int(foreground.state) == 2: + GameClock.set_paused(true) + _set_status("先に要対応イベントの選択肢を決めてください。", false) + _update_pause_and_speed() + return + if _runtime != null \ + and _runtime.has_method(&"get_operations_report") \ + and _runtime.has_method(&"acknowledge_operations_pause"): + var report_value: Variant = _runtime.call(&"get_operations_report") + if report_value is Dictionary: + var report: Dictionary = report_value + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = ( + coordinator_value if coordinator_value is Dictionary else {} + ) + if StringName(coordinator.get(&"terminal_state", &"RUNNING")) == &"BANKRUPT": + _set_status("資金破綻により運営は終了しています。", false) + return + if bool(report.get(&"enabled", false)) \ + and int(report.get(&"deferred_runtime_minute", -1)) >= 0: + var retry_value: Variant = _runtime.call(&"acknowledge_operations_pause") + var retry_ok := retry_value is Dictionary and bool(retry_value.get(&"ok", false)) + _set_status( + "運営処理を再開しました。" if retry_ok \ + else "運営処理を再開できません: %s" % _result_message(retry_value), + retry_ok + ) + _refresh_dashboard() + _update_pause_and_speed() + return + GameClock.toggle_pause() + _update_pause_and_speed() + + +func _set_speed(value: int) -> void: + GameClock.set_speed(value) + _update_pause_and_speed() + + +func _update_pause_and_speed() -> void: + var operations_deferred := false + var operations_terminal := false + var event_waiting := false + if _runtime != null and _runtime.has_method(&"get_operations_report"): + var report_value: Variant = _runtime.call(&"get_operations_dashboard_report") if _runtime.has_method(&"get_operations_dashboard_report") else _runtime.call(&"get_operations_report") + if report_value is Dictionary: + operations_deferred = bool(report_value.get(&"enabled", false)) \ + and int(report_value.get(&"deferred_runtime_minute", -1)) >= 0 + var coordinator_value: Variant = report_value.get(&"coordinator", {}) + if coordinator_value is Dictionary: + operations_terminal = StringName( + coordinator_value.get(&"terminal_state", &"RUNNING") + ) == &"BANKRUPT" + var event_service: Variant = _service(&"event_service") + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + event_waiting = foreground != null and int(foreground.state) == 2 + if phase_label != null: + if operations_terminal: + phase_label.text = "運営終了" + elif operations_deferred: + phase_label.text = "要確認" + elif event_waiting: + phase_label.text = "選択待ち" + else: + phase_label.text = "停止中" if GameClock.paused else _phase_name(GameClock.get_phase()) + phase_label.add_theme_color_override("font_color", Color("e88972") if GameClock.paused else Color("f0c574")) + if pause_button != null: + pause_button.text = ( + "破綻" + if operations_terminal else ( + "運営再試行" + if operations_deferred else ( + "イベント待ち" + if event_waiting else ("再開" if GameClock.paused else "停止") + ) + ) + ) + pause_button.disabled = operations_terminal or event_waiting + for raw_speed: Variant in speed_buttons: + var button := speed_buttons[raw_speed] as Button + button.set_pressed_no_signal(not GameClock.paused and int(raw_speed) == GameClock.speed) + + +func _on_time_changed(current_day: int, hour: int, minute: int) -> void: + time_label.text = "%d日目 %02d:%02d" % [current_day, hour, minute] + _update_pause_and_speed() + + +func _on_phase_changed(phase: GameClock.Phase) -> void: + if not GameClock.paused: + phase_label.text = _phase_name(phase) + + +func _phase_name(phase: GameClock.Phase) -> String: + match phase: + GameClock.Phase.MORNING: + return "朝" + GameClock.Phase.DAY: + return "昼" + GameClock.Phase.EVENING: + return "夕方" + _: + return "夜" + + +const RESOURCE_TITLES := {&"gold": "所持金", &"mana": "マナ", &"materials": "資材", &"faith": "信仰"} + +func _resource_title(resource_name: StringName) -> String: + return String(RESOURCE_TITLES.get(resource_name, String(resource_name))) + + +func _on_resource_changed(resource_name: StringName, value: int) -> void: + if not resource_labels.has(resource_name): + return + var label := resource_labels[resource_name] as Label + label.text = "%s %d" % [_resource_title(resource_name), value] + if resource_name == &"mana": + label.mouse_filter = Control.MOUSE_FILTER_STOP + label.tooltip_text = "残量0で全館停止。接客・調理・搬入・エレベーターと新規入館が停止します。\n残量と配電が回復すると再開します。破壊・摘発による休止は解除されません。" + if value <= 0: + label.text += "・停止" + label.add_theme_color_override("font_color", Color("ffb095")) + else: + label.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff2d0"))) + + +func _button(text_value: String, callback: Callable, minimum_width := 82) -> Button: + var button := Button.new() + button.text = text_value + button.custom_minimum_size = Vector2(minimum_width, 28) + button.add_theme_font_size_override("font_size", 12) + button.pressed.connect(callback) + _apply_button_style(button) + return button + + +func _hud_value_label(text_value: String, minimum_width: float) -> Label: + var label := Label.new() + label.text = text_value + label.custom_minimum_size.x = minimum_width + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.add_theme_font_size_override("font_size", 12) + label.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff2d0"))) + return label + + +func _icon_rect(icon_texture: Texture2D, side: float) -> TextureRect: + var icon := TextureRect.new() + icon.texture = icon_texture + icon.custom_minimum_size = Vector2(side, side) + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + return icon + + +func _apply_button_icon(button: Button, icon_texture: Texture2D, maximum_width: int) -> void: + if icon_texture == null: + return + button.icon = icon_texture + button.add_theme_constant_override("icon_max_width", maximum_width) + button.expand_icon = true + + +func _apply_button_style(button: Button) -> void: + if _audio_feedback != null: + _audio_feedback.bind_button(button) + button.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff0c8"))) + button.add_theme_color_override("font_hover_color", Color("fff8e5")) + button.add_theme_color_override("font_pressed_color", Color("fff8e5")) + button.add_theme_color_override("font_disabled_color", Color("a98e68")) + button.add_theme_stylebox_override("normal", _themed_panel_style(&"button_normal", _visual_color(&"button_normal", Color("75451f")), _visual_color(&"border_gold", Color("b77b37")), 7, 7)) + button.add_theme_stylebox_override("hover", _themed_panel_style(&"button_hover", _visual_color(&"button_hover", Color("8d5628")), _visual_color(&"border_gold", Color("e4b45c")), 7, 7)) + button.add_theme_stylebox_override("pressed", _themed_panel_style(&"button_pressed", _visual_color(&"button_pressed", Color("496528")), _visual_color(&"border_gold", Color("d8bd61")), 7, 7)) + button.add_theme_stylebox_override("focus", _themed_panel_style(&"button_pressed", _visual_color(&"button_pressed", Color("496528")), _visual_color(&"border_gold", Color("f2d97d")), 7, 7)) + button.add_theme_stylebox_override("disabled", _themed_panel_style(&"button_disabled", _visual_color(&"button_disabled", Color("574334")), Color("7b6955"), 7, 7)) + + +func _section_heading(text_value: String) -> Label: + var heading := Label.new() + heading.text = text_value + heading.add_theme_font_size_override("font_size", 14) + heading.add_theme_color_override("font_color", _visual_color(&"text_dark", Color("55371e"))) + LayerSkin.register_text(heading, GameState.active_layer) + return heading + + +func _panel_style(color: Color, border_color: Color, radius := 8, margin := 12) -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = color + style.border_color = border_color + style.set_border_width_all(1) + style.set_corner_radius_all(mini(radius, 1)) + style.set_content_margin_all(margin) + style.shadow_size = 0 + return style + + +func _themed_panel_style(skin_id: StringName, color: Color, border_color: Color, radius := 0, margin := 5) -> StyleBox: + # Small flat frames keep attention on the animated cross-section. + var style := _panel_style(color, border_color, mini(radius, 1), mini(margin, 6)) + LayerSkin.register_style(style, skin_id, GameState.active_layer) + return style + + +func _set_status(message: String, success: bool, quiet := false) -> void: + if build_status_label == null: + return + build_status_label.text = message + build_status_label.tooltip_text = message + if quiet: + build_status_label.add_theme_color_override("font_color", Color("c7d3a8") if success else Color("ffb498")) + else: + build_status_label.add_theme_color_override("font_color", Color("d8e8ad") if success else Color("ffb498")) + + +func _visual_color(property_name: StringName, fallback: Color) -> Color: + if visual_theme == null: + return fallback + var value: Variant = visual_theme.get(property_name) + return value if value is Color else fallback + + +func _service(property_name: StringName) -> Variant: + if _runtime == null or not _runtime is Object: + return null + for property: Dictionary in _runtime.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + return _runtime.get(property_name) + return null + + +func _call_array(service: Variant, method_name: StringName) -> Array: + if service == null or not service.has_method(method_name): + return [] + var result: Variant = service.call(method_name) + return result if result is Array else [] + + +func _facility_definition(definition_id: StringName) -> Variant: + var registry: Variant = _service(&"definition_registry") + if registry == null or not registry.has_method(&"get_facility"): + return null + return registry.call(&"get_facility", definition_id) + + +func _construction_facility(instance_id: StringName) -> Dictionary: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_facility"): + return {} + var result: Variant = construction.call(&"get_facility", instance_id) + return result if result is Dictionary else {} + + +func _facility_reasons(reasons_value: Variant) -> String: + if not reasons_value is Array: + return "" + var names: Array[String] = [] + for reason_value: Variant in reasons_value: + var reason := StringName(reason_value) + names.append(String(FACILITY_REASON_NAMES.get(reason, reason))) + return "・".join(names) + + +func _definition_value(source: Variant, property_name: StringName, fallback: Variant) -> Variant: + if source is Dictionary: + return source.get(property_name, source.get(String(property_name), fallback)) + if source == null or not source is Object: + return fallback + for property: Dictionary in source.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + return source.get(property_name) + return fallback + + +func _ensure_guest_observer() -> GuestObservationPanel: + if guest_observer == null: + guest_observer = AcceptDialog.new() + guest_observer.title = "来客の様子" + guest_observer.ok_button_text = "閉じる" + var content := preload("res://scripts/ui/guest_observation_panel.gd").new() + content.configure(_runtime) + var scroll := ScrollContainer.new() + scroll.name = "GuestObservationScroll" + scroll.custom_minimum_size = Vector2(420, 340) + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + content.size_flags_horizontal = Control.SIZE_EXPAND_FILL + guest_observer.add_child(scroll) + scroll.add_child(content) + guest_observer.visibility_changed.connect(_update_modal_input_guard) + add_child(guest_observer) + return guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + + +func _show_guest_observer() -> void: + var panel := _ensure_guest_observer() + guest_observer.popup_centered(Vector2i(460, 410)) + panel.refresh() + + +func _preview_dimension_migration() -> void: + var report: Dictionary = _runtime.preview_facility_dimensions_at_path(manual_save_path) + if not report.get(&"ok",false): + _set_status("旧寸法の修正を検証できませんでした。保存データは変更していません。",false) + return + if report.get(&"changes",[]).is_empty(): + _set_status("この手動セーブに旧寸法の施設はありません。通常の読込を利用してください。",true) + return + _dimension_migration_digest=report[&"source_digest"] + if dimension_migration_dialog==null: + dimension_migration_dialog=ConfirmationDialog.new() + dimension_migration_dialog.name="DimensionMigrationDialog" + dimension_migration_dialog.title="旧寸法を修正して読み込む" + dimension_migration_dialog.ok_button_text="修正して読込" + dimension_migration_dialog.cancel_button_text="戻る" + dimension_migration_dialog.confirmed.connect(_confirm_dimension_migration) + dimension_migration_dialog.canceled.connect(func(): _dimension_migration_digest="") + dimension_migration_dialog.visibility_changed.connect(_update_modal_input_guard) + add_child(dimension_migration_dialog) + var lines: PackedStringArray=[] + for change: Dictionary in report[&"changes"]: + lines.append("%s%s → %d×%d" % [change[&"name"],"(建設中)" if change[&"building"] else "",change[&"size"].x,change[&"size"].y]) + dimension_migration_dialog.dialog_text="保存済みの手動セーブを修正します。\n"+"\n".join(lines)+"\n\n修正前のバックアップを残します。\n現在の未保存の進行は読み込みで置き換わります。" + dimension_migration_dialog.popup_centered(Vector2i(520,280)) + +func _confirm_dimension_migration() -> void: + if _dimension_migration_digest.is_empty(): return + var expected := _dimension_migration_digest + _dimension_migration_digest="" + var report: Dictionary=_runtime.migrate_facility_dimensions_at_path(manual_save_path,&"",expected) + if not report.get(&"ok",false): + _set_status("旧寸法の修正に失敗しました。読込は行っていません。保存データを確認してください。",false) + return + _load_manual(true) + if build_status_label.text=="手動セーブを読み込みました。": + _set_status("旧寸法を修正して読み込みました。修正前のバックアップを保持しています。",true) + + +func _on_defense_notice_input(event: InputEvent) -> void: + if (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT) or event.is_action_pressed(&"ui_accept"): + alert_count_label.accept_event() + _show_defense_manager() + + +func _ensure_defense_manager() -> DefenseManagementPanel: + if defense_manager == null: + defense_manager = AcceptDialog.new() + defense_manager.name = "DefenseManager" + defense_manager.title = "世界樹の防衛" + defense_manager.ok_button_text = "閉じる" + var dialog_theme := Theme.new() + dialog_theme.set_stylebox("panel", "AcceptDialog", _themed_panel_style(&"panel_parchment", Color("f4e3bd"), Color("a96f2f"), 8, 10)) + var window_border := _themed_panel_style(&"panel_outer", Color("49311f"), Color("a96f2f"), 8, 10) + window_border.expand_margin_top = 32 + dialog_theme.set_stylebox("embedded_border", "Window", window_border) + dialog_theme.set_color("title_color", "Window", Color("fff0cf")) + defense_manager.theme = dialog_theme + _apply_button_style(defense_manager.get_ok_button()) + var panel := DefenseManagementPanelScript.new() + panel.configure(_runtime, _apply_button_style) + panel.configure_audio(Callable(_audio_feedback, "submit_command")) + panel.status_requested.connect(_on_staff_management_status) + var scroll := ScrollContainer.new() + scroll.name = "DefenseManagementScroll" + scroll.custom_minimum_size = Vector2(480, 460) + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + defense_manager.add_child(scroll) + scroll.add_child(panel) + add_child(defense_manager) + return defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + + +func _show_defense_manager() -> void: + var panel := _ensure_defense_manager() + panel.refresh() + LayerSkin.apply_tree(defense_manager, GameState.active_layer) + defense_manager.popup_centered(Vector2i(520, 540)) + + +func _restore_defense_interface_selection(manifest: Variant) -> void: + if defense_manager != null: + defense_manager.hide() + var existing := defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + if existing != null: existing.clear_selection() + if not manifest is Dictionary: return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or selection.get("version", 0) != 1: return + var id_value: Variant = selection.get("defense_id", "") + if not id_value is String or id_value.is_empty() or id_value.length() > 256: return + var raid_id := StringName(id_value) + if _service(&"defense_service").get_management_detail(raid_id).is_empty(): return + _ensure_defense_manager().select_raid(raid_id) + if selection.get("defense_open", false) is bool and selection.get("defense_open", false): + _show_defense_manager() + + +func _setup_ui_audio() -> void: + _paired_bgm = PairedBGMScript.new() + _paired_bgm.name = "PairedBGM" + _paired_bgm.configure(GameState.active_layer) + add_child(_paired_bgm) + _audio_feedback = UIAudioFeedbackScript.new() + _audio_feedback.name = "UIAudioFeedback" + _audio_feedback.configure(_runtime, audio_settings_path) + add_child(_audio_feedback) + _audio_feedback.begin_loaded_epoch(_current_audio_foreground()) + _audio_settings_dialog = UIAudioSettingsDialogScript.new() + _audio_settings_dialog.configure(_audio_feedback, _apply_button_style) + add_child(_audio_settings_dialog) + + +func _current_audio_foreground() -> Variant: + var events: Variant = _service(&"event_service") + if events != null and events.has_method(&"get_foreground_instance"): + return events.call(&"get_foreground_instance") + return null + + +func _show_audio_settings() -> void: + if _audio_settings_dialog != null: + _audio_settings_dialog.show_settings() + + +func _on_audio_world_selection(_id: StringName) -> void: + if _audio_feedback != null: + _audio_feedback.request_click() + + +func _submit_ui_command( + command_type: StringName, payload: Dictionary = {}, actor_id: StringName = &"player", + target_id: StringName = &"", priority := 0, execute_at := -1, + idempotency_key: StringName = &"" +) -> Variant: + if _audio_feedback != null: + return _audio_feedback.submit_command(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) + return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) + +func _toggle_demolition_mode() -> void: + _set_demolition_mode(not _demolition_mode) + + +func _set_demolition_mode(enabled: bool, announce := true) -> void: + if enabled: + _set_pan_mode(false) + _set_excavation_mode(false, false) + _clear_build_selection(false) + _demolition_mode = enabled + _demolition_target = &"" + if demolition_dialog != null: + demolition_dialog.hide() + if demolition_button != null: + demolition_button.set_pressed_no_signal(enabled) + if world_preview != null: + world_preview.set_demolition_mode(enabled) + if announce: + _set_status("撤去する施設のマスを選択してください。確認画面で取り消せます。" if enabled else "撤去モードを解除しました。", true, true) + + +func _preview_demolition(cell: Vector3i) -> void: + # Selection only; Runtime owns eligibility, occupancy and refunds. + _demolition_target = world_preview.facility_at_canvas(world_preview.cell_to_canvas(cell)) + world_preview.selected_entity_id = _demolition_target + if demolition_dialog == null: + demolition_dialog = ConfirmationDialog.new() + demolition_dialog.name = "DemolitionDialog" + demolition_dialog.title = "施設の撤去" + demolition_dialog.ok_button_text = "撤去する" + demolition_dialog.cancel_button_text = "戻る" + demolition_dialog.confirmed.connect(_confirm_demolition) + demolition_dialog.canceled.connect(_cancel_demolition) + add_child(demolition_dialog) + _apply_button_style(demolition_dialog.get_ok_button()) + _apply_button_style(demolition_dialog.get_cancel_button()) + demolition_dialog.dialog_text = "座標 (%d, %d / %d)\n施設: %s\n\n撤去を指示しますか?" % [cell.x, cell.y, cell.z, String(_demolition_target)] + demolition_dialog.popup_centered(Vector2i(420, 210)) + + +func _cancel_demolition() -> void: + _demolition_target = &"" + _set_status("撤去を取り消しました。", true, true) + + +func _confirm_demolition() -> void: + if not _demolition_mode: + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var target := _demolition_target + _demolition_target = &"" + var result: CommandResult = _runtime.submit_command(&"demolish_facility", {&"instance_id": target}) + # Paused play returns execution synchronously; running play returns acceptance. + if result.is_success() and _runtime.command_queue.get_processed_result(result.command_id) == null: + _pending_demolition_commands[result.command_id] = target + _set_status("撤去を受け付けました", true) + return + _show_demolition_result(result) + + +func _show_demolition_result(result: CommandResult) -> void: + if result.is_success(): + var refund: Dictionary = result.details.get(&"refund", {}) + var keys: Array = refund.keys() + keys.sort() + var amounts: PackedStringArray = [] + for key: Variant in keys: + if int(refund[key]) != 0: + amounts.append("%s %d" % [_resource_title(StringName(key)), int(refund[key])]) + _set_status("撤去しました(返金: %s)" % "、".join(amounts) if not amounts.is_empty() else "撤去しました(返金なし)", true) + else: + _set_status("撤去できません: " + result.message, false) + _refresh_selected_entity() + world_preview.queue_redraw() + + +func assign_staff_by_click(facility_id: StringName, staff_id: StringName = &"") -> void: + if staff_management_panel == null: + return + var target_staff := staff_id + if target_staff.is_empty(): + target_staff = StringName(staff_management_panel.get("selected_staff_id")) + if target_staff.is_empty(): + _set_status("配属する従業員を一覧から選択してください。", false) + return + var queue_id: int = int(staff_management_panel.get("_queue_instance_id")) + var template_val: Variant = staff_management_panel.get("_draft_template_id") + var drop_data := { + &"kind": &"staff_assignment", + &"staff_id": target_staff, + &"template_id": template_val if template_val != null else &"DAY", + &"queue_instance_id": queue_id, + } + staff_management_panel.assign_from_drop(drop_data, facility_id) + + +func _update_modal_input_guard() -> void: + if world_preview == null: + return + var any_visible := false + for dialog: Variant in [manual_save_slots_dialog, event_log_dialog, rank_dialog, dimension_migration_dialog, guest_observer, defense_manager, important_warning_cutin]: + if dialog != null and is_instance_valid(dialog) and dialog is Window and bool(dialog.visible): + any_visible = true + break + world_preview.modal_input_blocked = any_visible + + +func _ensure_important_warning_cutin() -> OperationFeedback.ImportantWarningCutinDialog: + if important_warning_cutin == null: + important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new() + important_warning_cutin.visibility_changed.connect(_update_modal_input_guard) + add_child(important_warning_cutin) + return important_warning_cutin + + +func show_warning_cutin(kind: StringName, heading: String, message: String) -> void: + _ensure_important_warning_cutin().show_cutin(kind, heading, message) + + +func _bind_domain_event_stream() -> void: + _unbind_domain_event_stream() + if _runtime == null or not is_instance_valid(_runtime): + return + var stream: Variant = _runtime.get(&"domain_events") if _runtime is Object and "domain_events" in _runtime else null + if stream != null and is_instance_valid(stream) and stream.has_method(&"subscribe"): + stream.subscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene", Callable(self, "_on_domain_event_published")) + _subscribed_event_stream = stream + + +func _unbind_domain_event_stream() -> void: + if _subscribed_event_stream != null and is_instance_valid(_subscribed_event_stream): + if _subscribed_event_stream.has_method(&"unsubscribe"): + _subscribed_event_stream.unsubscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene") + _subscribed_event_stream = null + + +func _on_domain_event_published(event: Variant) -> void: + var event_type: StringName = &"" + var payload: Dictionary = {} + if event is Dictionary: + event_type = StringName(event.get(&"event_type", event.get("event_type", &""))) + var raw_payload: Variant = event.get(&"payload", event.get("payload", {})) + if raw_payload is Dictionary: + payload = raw_payload + elif event is Object: + if "event_type" in event: + event_type = StringName(event.get(&"event_type")) + if "payload" in event: + var raw_payload: Variant = event.get(&"payload") + if raw_payload is Dictionary: + payload = raw_payload + match event_type: + &"DEFENSE_RAID_CREATED": + show_warning_cutin(OperationFeedbackScript.CUTIN_DEFENSE, "防衛戦開始演出", "敵の襲撃が発生しました!防衛部隊を配置してください。") + &"RELATIONSHIP_SHIELD_CHANGED": + # Existing love-shield vanishing transition + var old_val: int = int(payload.get(&"old_value", payload.get("old_value", -1))) + var new_val: int = int(payload.get(&"new_value", payload.get("new_value", -1))) + if old_val > 0 and new_val == 0: + show_warning_cutin(OperationFeedbackScript.CUTIN_INVASION, "重要警告:NTR侵食開始", "愛の盾が破られ、NTR侵食が始まりました。人物関係を確認してください。") + diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt new file mode 100644 index 0000000..26dd8d7 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt @@ -0,0 +1,2323 @@ +class_name WorldPreview +extends Control + +signal facility_selected(instance_id: StringName) +signal staff_assignment_dropped(data: Dictionary, facility_id: StringName) +signal staff_assignment_clicked(staff_id: StringName, facility_id: StringName) +signal construction_selected(job_id: StringName) +signal legacy_stair_selected(stair_id: StringName) +signal cell_clicked(cell: Vector3i) +signal hovered_cell_changed(cell: Variant) +signal build_preview_changed(report: Dictionary) +signal excavation_preview_changed(report: Dictionary) + +enum LayerMode { + FRONT, + BACK, +} + +const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd") +const RoomPresentation := preload("res://scripts/ui/room_presentation.gd") +const People := preload("res://scripts/ui/pixel_people.gd") +const PixelArt := preload("res://scripts/ui/pixel_tower_art.gd") +const InactiveLayerRooms := preload("res://scripts/ui/inactive_layer_rooms.gd") +const LAYER_LENS_SHADER := preload("res://assets/shaders/layer_lens.gdshader") +const LAYER_TRANSITION_SECONDS := 0.55 +const MAP_PADDING := Vector2(76.0, 70.0) +const INVALID_PREVIEW := {&"can_build": false, &"error_codes": [], &"reasons": [], &"cells": []} +const INVALID_EXCAVATION_PREVIEW := { + &"ok": false, + &"cells": [], + &"cost": {}, + &"build_minutes": 0, + &"errors": [&"MISSING_DEPENDENCY"], +} + +var layer_mode := LayerMode.FRONT: + set(value): + var previous := layer_mode + layer_mode = clampi(value, LayerMode.FRONT, LayerMode.BACK) as LayerMode + if previous != layer_mode and is_node_ready(): + _start_layer_transition() + if _hovered_cell != null: + _hovered_cell = Vector3i(_hovered_cell.x, _hovered_cell.y, layer_mode) + queue_redraw() + _update_build_preview() + _update_excavation_preview() + +var selected_entity_id: StringName = &"": + set(value): + selected_entity_id = value + queue_redraw() + +var build_definition: Variant = null +var build_rotation := 0 +var excavation_mode := false +var demolition_mode := false +var visual_theme: VisualThemeProfile = null + +var zoom_level := 1.0 +var pan_offset := Vector2.ZERO +var show_grid := false +var pan_mode := false +var modal_input_blocked := false: + set(value): + modal_input_blocked = value + if modal_input_blocked: + _end_pan_drag() +var _drawing := false +var _draw_cache: Dictionary = {} + +var _inactive_layer_art: Node2D +var _layer_lens: ColorRect +var _layer_transition_elapsed := LAYER_TRANSITION_SECONDS + +var _animation_time := 0.0 +var _animation_frame := -1 +var _actor_tracks: Dictionary = {} +var _elevator_tracks: Dictionary = {} +var _boarded_actor_ids: Dictionary = {} +var _carried_patients: Dictionary = {} +var _treated_patients: Dictionary = {} +var _resting_staff: Dictionary = {} +var _room_presentations: Dictionary = {} +var _queue_positions: Dictionary = {} +var _queue_hidden: Dictionary = {} +var _landing_badges: Array[Dictionary] = [] +var _transport_report: Dictionary = {} +var _life_observation: Dictionary = {} +var _life_overlay: Node2D +var _life_observation_serial := 0 +var _dragging := false +var _drag_button := MOUSE_BUTTON_NONE + +var _runtime: Variant = null +var _hovered_cell: Variant = null +var _preview_topology_key: Array = [] +var _connection_cache_key: Array = [] +var _connection_cache_report: Dictionary = {} +var _last_preview_report: Dictionary = INVALID_PREVIEW.duplicate(true) +var _last_excavation_preview_report: Dictionary = INVALID_EXCAVATION_PREVIEW.duplicate(true) + + +func _ready() -> void: + clip_contents = true + texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + focus_mode = Control.FOCUS_CLICK + mouse_filter = Control.MOUSE_FILTER_STOP + resized.connect(queue_redraw) + mouse_exited.connect(_on_mouse_exited) + if _runtime == null: + _runtime = get_node_or_null("/root/SimulationRuntime") + _connect_runtime() + _inactive_layer_art = InactiveLayerRooms.new() + _inactive_layer_art.name = "InactiveLayerRooms" + add_child(_inactive_layer_art) + _life_overlay = Node2D.new() + _life_overlay.name = "LifeOverlay" + _life_overlay.draw.connect(_draw_life_overlay) + add_child(_life_overlay) + _layer_lens = ColorRect.new() + _layer_lens.name = "LayerLens" + _layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE + _layer_lens.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + var lens_material := ShaderMaterial.new() + lens_material.shader = LAYER_LENS_SHADER + _layer_lens.material = lens_material + _layer_lens.visible = false + add_child(_layer_lens) + queue_redraw() + + +func set_runtime(value: Variant) -> void: + _connection_cache_key.clear() + _connection_cache_report.clear() + _disconnect_runtime() + _runtime = value + _actor_tracks.clear() + _elevator_tracks.clear() + _boarded_actor_ids.clear() + _connect_runtime() + queue_redraw() + _update_build_preview() + _update_excavation_preview() + + +func set_visual_theme(profile: VisualThemeProfile) -> void: + visual_theme = profile + queue_redraw() + + +func set_build_definition(definition: Variant, rotation := 0) -> void: + if definition != null: + demolition_mode = false + if definition != null and excavation_mode: + set_excavation_mode(false) + build_definition = definition + build_rotation = _normalized_rotation(rotation) + _update_build_preview() + queue_redraw() + + +func clear_build_definition() -> void: + build_definition = null + build_rotation = 0 + _last_preview_report = INVALID_PREVIEW.duplicate(true) + build_preview_changed.emit(_last_preview_report.duplicate(true)) + queue_redraw() + + +func set_build_rotation(value: int) -> void: + build_rotation = _normalized_rotation(value) + _update_build_preview() + queue_redraw() + + +func set_excavation_mode(enabled: bool) -> void: + if enabled: + demolition_mode = false + if excavation_mode == enabled: + _update_excavation_preview() + return + excavation_mode = enabled + if excavation_mode and build_definition != null: + build_definition = null + build_rotation = 0 + _last_preview_report = INVALID_PREVIEW.duplicate(true) + build_preview_changed.emit(_last_preview_report.duplicate(true)) + _update_excavation_preview() + queue_redraw() + + +func get_excavation_preview_report(cell: Variant = null) -> Dictionary: + var target: Variant = _hovered_cell if cell == null else cell + if not target is Vector3i: + return INVALID_EXCAVATION_PREVIEW.duplicate(true) + var excavation: Variant = _service(&"excavation_service") + if excavation != null and excavation.has_method(&"validate_excavation"): + var report: Variant = excavation.call( + &"validate_excavation", + Vector3i(target.x, target.y, layer_mode) + ) + if report is Dictionary: + return report.duplicate(true) + return INVALID_EXCAVATION_PREVIEW.duplicate(true) + + +func get_hovered_cell() -> Variant: + return _hovered_cell + + +func clear_hovered_cell() -> void: + _set_hovered_cell(null) + + +func get_build_preview_report(cell: Variant = null) -> Dictionary: + var target: Variant = _hovered_cell if cell == null else cell + if build_definition == null or not target is Vector3i: + return INVALID_PREVIEW.duplicate(true) + if _runtime != null and _runtime.has_method(&"preview_build_facility"): + var result: Dictionary = _runtime.call(&"preview_build_facility", build_definition.id, Vector3i(target.x, target.y, layer_mode), build_rotation) + result[&"connection"] = _build_connection_report( + _cells_from_serialized(result.get(&"cells", [])), + Vector3i(target.x, target.y, layer_mode)) + return result + var construction: Variant = _service(&"construction_service") + if construction != null and construction.has_method(&"validate_placement"): + var report: Variant = construction.call( + &"validate_placement", + build_definition, + Vector3i(target.x, target.y, layer_mode), + build_rotation + ) + if report != null and report.has_method(&"to_dictionary"): + var result: Dictionary = report.call(&"to_dictionary") + result[&"connection"] = _build_connection_report( + _cells_from_serialized(result.get(&"cells", [])), + Vector3i(target.x, target.y, layer_mode)) + return result + return { + &"can_build": _fallback_cells_are_available(Vector3i(target.x, target.y, layer_mode)), + &"error_codes": [], + &"reasons": [], + &"cells": _serialize_cells(_fallback_build_cells(Vector3i(target.x, target.y, layer_mode))), + } + + +func get_map_rect() -> Rect2: + if _drawing and _draw_cache.has(&"rect"): + return _draw_cache[&"rect"] + var bounds := _world_bounds() + if bounds.is_empty(): + return Rect2() + var dimensions := _cell_dimensions() + var map_size := Vector2( + int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1, + int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1 + ) * dimensions + var result_value = Rect2(((size - map_size) * 0.5 + pan_offset).floor(), map_size) + + if _drawing: + _draw_cache[&"rect"] = result_value + return result_value + + +func _cell_dimensions() -> Vector2: + if _drawing and _draw_cache.has(&"dimensions"): + return _draw_cache[&"dimensions"] + var bounds := _world_bounds() + if bounds.is_empty(): + return Vector2(32, 48) + var columns := maxi(int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1, 1) + var rows := maxi(int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1, 1) + var available := (size - MAP_PADDING * 2.0).max(Vector2.ONE) + var fit := minf(available.x / (columns * 16.0), available.y / (rows * 24.0)) + # Native 16px units use integer magnification. Large worlds can be viewed + # at overview scale, then enlarged with the same invertible camera mapping. + var pixel := maxf(floorf(fit), 1.0) if fit >= 1.0 else maxf(fit, 0.25) + var result_value = (Vector2(16, 24) * pixel * zoom_level).round().max(Vector2(4, 6)) + + if _drawing: + _draw_cache[&"dimensions"] = result_value + return result_value + + +func set_zoom(value: float, anchor := Vector2(-1, -1)) -> void: + var before := get_map_rect() + var at := size * 0.5 if anchor.x < 0 else anchor + var relative := (at - before.position) / before.size if before.has_area() else Vector2(0.5, 0.5) + zoom_level = clampf(value, 0.5, 4.0) + var after := get_map_rect() + pan_offset += at - (after.position + relative * after.size) + _clamp_pan() + _set_hovered_cell(canvas_to_cell(at)) + queue_redraw() + + +func reset_view() -> void: + zoom_level = 1.0 + pan_offset = Vector2.ZERO + _actor_tracks.clear() + queue_redraw() + + +func focus_operating_area() -> void: + # Explicit framing, never continuous tracking: the player's pan stays put. + zoom_level = 1.0 + pan_offset = Vector2.ZERO + var area := Rect2() + var construction: Variant = _service(&"construction_service") + if construction != null: + for item: Dictionary in construction.get_facilities(): + if int(item.get(&"layer", 0)) != layer_mode: + continue + var cells := _cells_from_serialized(item.get(&"occupied_cells", [])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + area = area.merge(rect) if area.has_area() else rect + if area.has_area(): + var relative := (area.get_center()-get_map_rect().position) / get_map_rect().size + zoom_level = clampf(minf(size.x*0.68/area.size.x, size.y*0.65/area.size.y), 0.5, 2.0) + var rect := get_map_rect() + pan_offset += size*Vector2(0.5, 0.58) - (rect.position+rect.size*relative) + else: + zoom_level = 2.0 + pan_offset += size*Vector2(0.5, 0.65) - cell_to_canvas(Vector3i(0, 1, layer_mode)) + _clamp_pan() + clear_hovered_cell() + queue_redraw() + + +func set_pan_mode(enabled: bool) -> void: + pan_mode = enabled + _end_pan_drag() + clear_hovered_cell() + + +func _end_pan_drag() -> void: + _dragging = false + _drag_button = MOUSE_BUTTON_NONE + mouse_default_cursor_shape = Control.CURSOR_DRAG if pan_mode else Control.CURSOR_ARROW + + +func _notification(what: int) -> void: + if what == NOTIFICATION_APPLICATION_FOCUS_OUT: + _end_pan_drag() + + +func _clamp_pan() -> void: + var extent := get_map_rect().size * 0.5 + size * 0.3 + pan_offset = pan_offset.clamp(-extent, extent) + + +func _start_layer_transition() -> void: + if _layer_lens == null: return + _layer_transition_elapsed = 0.0 + var center := size * 0.5 + if _hovered_cell != null: + center = cell_to_canvas(_hovered_cell) + var lens_material := _layer_lens.material as ShaderMaterial + lens_material.set_shader_parameter("center", center / size.max(Vector2.ONE)) + lens_material.set_shader_parameter("extent", size) + lens_material.set_shader_parameter("progress", 0.0) + _layer_lens.visible = true + + +func _process(delta: float) -> void: + # UI transitions complete even in active pause; actor time remains frozen. + if _layer_lens != null and _layer_lens.visible: + _layer_transition_elapsed = minf(_layer_transition_elapsed + maxf(delta, 0.0), LAYER_TRANSITION_SECONDS) + var lens_material := _layer_lens.material as ShaderMaterial + lens_material.set_shader_parameter("extent", size) + lens_material.set_shader_parameter("progress", _layer_transition_elapsed / LAYER_TRANSITION_SECONDS) + _layer_lens.visible = _layer_transition_elapsed < LAYER_TRANSITION_SECONDS + if build_definition != null and _hovered_cell is Vector3i and _current_preview_topology() != _preview_topology_key: + _update_build_preview() + queue_redraw() + var clock := get_node_or_null("/root/GameClock") + if clock != null and bool(clock.get("paused")): + return + var speed := float(clock.get("speed")) if clock != null else 1.0 + _animation_time += delta * maxf(speed, 0.0) + var frame := int(_animation_time * 12.0) + if frame != _animation_frame: + _animation_frame = frame + queue_redraw() + + +func _unhandled_key_input(event: InputEvent) -> void: + if not event is InputEventKey or not event.pressed or event.echo: + return + if event.keycode == KEY_HOME: + reset_view() + get_viewport().set_input_as_handled() + elif event.keycode == KEY_G: + show_grid = not show_grid + queue_redraw() + get_viewport().set_input_as_handled() + + +func cell_to_canvas(cell: Vector3i) -> Vector2: + var bounds := _world_bounds() + if bounds.is_empty(): + return Vector2.ZERO + return get_map_rect().position + Vector2( + float(cell.x - int(bounds[&"min_x"])) + 0.5, + float(int(bounds[&"max_y"]) - cell.y) + 0.5 + ) * _cell_dimensions() + + +func canvas_to_cell(canvas_position: Vector2) -> Variant: + var bounds := _world_bounds() + var map_rect := get_map_rect() + if bounds.is_empty() or not map_rect.has_area() or not map_rect.has_point(canvas_position): + return null + var local_position := (canvas_position - map_rect.position) / _cell_dimensions() + var cell := Vector3i(int(bounds[&"min_x"]) + floori(local_position.x), int(bounds[&"max_y"]) - floori(local_position.y), layer_mode) + var world: Variant = _service(&"world_service") + if world != null and world.has_method(&"is_in_bounds") and not bool(world.call(&"is_in_bounds", cell)): + return null + return cell + + +func get_cell_rect(cell: Vector3i) -> Rect2: + if _world_bounds().is_empty(): + return Rect2() + var dimensions := _cell_dimensions() + return Rect2(cell_to_canvas(cell) - dimensions * 0.5, dimensions) + + +func facility_at_canvas(canvas_position: Vector2) -> StringName: + var cell: Variant = canvas_to_cell(canvas_position) + if not cell is Vector3i: + return &"" + var world: Variant = _service(&"world_service") + if world == null or not world.has_method(&"get_occupant"): + return &"" + var occupant_id := StringName(world.call(&"get_occupant", cell)) + if occupant_id.is_empty(): + return &"" + var kind := &"" + if world.has_method(&"get_occupant_kind"): + kind = StringName(world.call(&"get_occupant_kind", cell)) + return occupant_id if kind == &"facility" else &"" + + +func _gui_input(event: InputEvent) -> void: + if modal_input_blocked: + accept_event() + return + if event is InputEventMouseButton: + if not event.pressed and event.button_index == _drag_button: + _end_pan_drag() + accept_event() + return + if event.button_index == MOUSE_BUTTON_MIDDLE or (pan_mode and event.button_index == MOUSE_BUTTON_LEFT): + if event.pressed: + _dragging = true + _drag_button = event.button_index + mouse_default_cursor_shape = Control.CURSOR_DRAG + accept_event() + return + if event.pressed and event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]: + if pan_mode and not event.ctrl_pressed: + var direction := 1.0 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -1.0 + pan_offset.y += direction * _cell_dimensions().y * maxf(event.factor, 0.0) + _clamp_pan() + queue_redraw() + else: + set_zoom(zoom_level + (0.5 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -0.5), event.position) + accept_event() + return + if event is InputEventMouseMotion and _dragging: + var mask := MOUSE_BUTTON_MASK_LEFT if _drag_button == MOUSE_BUTTON_LEFT else MOUSE_BUTTON_MASK_MIDDLE + if not event.button_mask & mask: + _end_pan_drag() + return + pan_offset += event.relative + _clamp_pan() + queue_redraw() + accept_event() + return + if event is InputEventMouseMotion: + if not pan_mode: + _set_hovered_cell(canvas_to_cell(event.position)) + return + if not event is InputEventMouseButton or not event.pressed or event.button_index != MOUSE_BUTTON_LEFT: + return + var cell: Variant = canvas_to_cell(event.position) + if not cell is Vector3i: + return + if build_definition != null or excavation_mode or demolition_mode: + cell_clicked.emit(cell) + accept_event() + return + var stair_id := legacy_stair_at_canvas(event.position) + if not stair_id.is_empty(): + selected_entity_id = stair_id + legacy_stair_selected.emit(stair_id) + accept_event() + return + var world: Variant = _service(&"world_service") + if world != null and world.has_method(&"get_occupant"): + var occupant_id := StringName(world.call(&"get_occupant", cell)) + var kind := &"" + if world.has_method(&"get_occupant_kind"): + kind = StringName(world.call(&"get_occupant_kind", cell)) + if not occupant_id.is_empty() and kind == &"facility": + selected_entity_id = occupant_id + facility_selected.emit(occupant_id) + accept_event() + return + if not occupant_id.is_empty() and kind == &"construction": + selected_entity_id = occupant_id + construction_selected.emit(occupant_id) + accept_event() + return + cell_clicked.emit(cell) + accept_event() + + +func _draw() -> void: + _drawing = true + _draw_cache.clear() + _draw_contents() + _drawing = false + _draw_cache.clear() + + +func _draw_contents() -> void: + _life_observation = {} + if _life_overlay != null: + _life_overlay.queue_redraw() + if _inactive_layer_art != null: + _inactive_layer_art.visible = false + _draw_background() + var bounds := _world_bounds() + if bounds.is_empty(): + _draw_center_message("世界データを待っています") + return + var map_rect := get_map_rect() + if map_rect.size == Vector2.ZERO: + return + _life_observation = _build_life_observation() + _life_observation_serial += 1 + _life_observation[&"observation_serial"] = _life_observation_serial + _life_observation[&"overlay_consumed_serial"] = -1 + _room_presentations = _life_observation[&"rooms"] + _carried_patients = _life_observation[&"carried_patients"] + _treated_patients = _life_observation[&"treated_patients"] + _resting_staff = _life_observation[&"resting_staff"] + _queue_positions = _life_observation[&"queue_positions"] + _queue_hidden = _life_observation[&"queue_hidden"] + _landing_badges.assign(_life_observation[&"landing_badges"]) + _transport_report = _life_observation[&"elevators"] + _boarded_actor_ids = _life_observation[&"boarded"] + _actor_tracks = _life_observation[&"actor_tracks"] + _elevator_tracks = _life_observation[&"elevator_tracks"] + _draw_world_cells(bounds, map_rect) + _draw_world_entity_icons() + _draw_legacy_stairs() + _draw_other_layer() + _draw_construction_labels() + _draw_facility_labels() + _draw_excavation_jobs() + _draw_build_preview() + _draw_excavation_preview() + _draw_hover() + _draw_map_caption(map_rect) + + +## The report describes the last submitted draw, not an independently sampled world. +## Callers cannot mutate either the frame or its interpolation tracks. +func get_life_observation_report() -> Dictionary: + return _life_observation.duplicate(true) + + +## Pure with respect to Runtime and this Control: helpers mutate only fresh local +## dictionaries (including copies of visual tracks). No whole Runtime snapshot +## is copied per frame; whole typed snapshots belong to sparse diagnostics. +func _build_life_observation() -> Dictionary: + var model := _observe_rooms() + model.merge(_observe_queues()) + model[&"layer"] = layer_mode + model[&"zoom"] = zoom_level + model[&"pan"] = pan_offset + model[&"viewport"] = size + model[&"animation_time"] = _animation_time + model[&"actor_tracks"] = _actor_tracks.duplicate(true) + model[&"elevator_tracks"] = _elevator_tracks.duplicate(true) + model[&"boarded"] = {} + var clock := get_node_or_null("/root/GameClock") + model[&"paused"] = clock != null and bool(clock.get("paused")) + model[&"cabins"] = _observe_elevators(model) + model[&"actor_drawings"] = _observe_actors(model) + var guests: Array[Dictionary] = [] + var staff: Array[Dictionary] = [] + var tasks: Array[Dictionary] = [] + var guest_service: Variant = _service(&"guest_service") + if guest_service != null and guest_service.has_method(&"get_guests"): + for guest: GuestRuntime in guest_service.get_guests(): + guests.append({&"id": guest.guest_id, &"state": GuestRuntime.State.keys()[guest.state], + &"cell": guest.current_cell, &"target": guest.target_facility_id, + &"service": guest.current_service_id, &"queue": guest.queue_request_id}) + var staff_service: Variant = _service(&"staff_service") + if staff_service != null: + for member: StaffRuntime in staff_service.get_staff_members(): + staff.append({&"id": member.staff_id, &"state": StaffRuntime.State.keys()[member.state], + &"cell": member.current_cell, &"task": member.current_task_id, + &"facility": member.assigned_facility_id, &"role": member.role_id, + &"carried_inventory_id": member.carried_inventory_id}) + for task: StaffTask in staff_service.get_tasks(): + tasks.append({&"id": task.task_id, &"state": StaffTask.State.keys()[task.state], + &"type": task.task_type, &"staff": task.assigned_staff_id, + &"target": task.target_entity_id, &"rescue_facility": task.rescue_facility_id}) + model[&"guests"] = guests + model[&"staff"] = staff + model[&"tasks"] = tasks + _fit_life_actors(model) + var bridge: Variant = _service(&"supply_logistics") + var goods: Dictionary = bridge.get_report() if bridge != null else {&"requests": [], &"deliveries": []} + model[&"goods"] = goods + var badges: Array[Dictionary] = [] + var facilities: Variant = _service(&"facility_service") + var world: Variant = _service(&"world_service") + if facilities != null and world != null: + for instance: FacilityInstance in facilities.get_instances(): + if instance.origin.z != layer_mode: + continue + var cells: Array[Vector3i] = world.get_entity_cells(instance.instance_id) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + var data: Dictionary = model[&"rooms"][instance.instance_id] + data[&"rect"] = rect + data[&"definition_id"] = instance.definition_id + var count := RoomPresentation.slots(rect, _cell_dimensions().x / 16.0, instance.definition_id).size() + badges.append({&"facility": instance.instance_id, &"kind": &"business", + &"at": rect.position + Vector2(2, 2), + &"text": RoomPresentation.business_label(data) + " " + RoomPresentation.label(data, count)}) + var requests: Array[Dictionary] = [] + for request: Dictionary in goods.get(&"requests", []): + if request[&"source_facility_id"] == instance.instance_id or request[&"destination_facility_id"] == instance.instance_id: + requests.append(request) + if not requests.is_empty(): + badges.append({&"facility": instance.instance_id, &"kind": &"supply", + &"at": rect.position + Vector2(2, 16), &"requests": requests, + &"text": RoomPresentation.supply_label(requests)}) + model[&"badges"] = badges + _resolve_observation_labels(model) + var obstacles: Array[Rect2] = [] + for cabin: Dictionary in model[&"cabins"]: + obstacles.append(cabin[&"rect"]) + obstacles.append(cabin[&"badge_rect"]) + for collection: Array in [badges, model[&"landing_badges"], model.get(&"actor_aggregations", [])]: + for badge: Dictionary in collection: + obstacles.append(_readable_badge_rect(badge[&"at"], badge[&"text"])) + model[&"traffic_obstacles"] = obstacles + var traffic: Array[Dictionary] = [] + var path: Variant = _service(&"path_service") + var bounds := _world_bounds() + if path != null and not bounds.is_empty(): + for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1): + for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1): + var cell := Vector3i(x, y, layer_mode) + var row: Dictionary = path.get_cell_traffic(cell) + if not row.is_empty(): + var cell_rect := get_cell_rect(cell) + var rect := cell_rect.grow(-2.0) + var world_pixel := _cell_dimensions().x / 16.0 + var thickness := clampf(rect.size.y * 0.16, 2.0, 5.0) + var band := Rect2(Vector2(rect.position.x + rect.size.x * 0.12, + cell_rect.end.y - 2 * world_pixel - thickness * 0.5), + Vector2(rect.size.x * 0.76, thickness)) + traffic.append({&"cell": cell, &"traffic": row, &"rect": rect, &"band_rect": band, + &"segment_rects": _traffic_segments(band, rect, obstacles)}) + model[&"traffic"] = traffic + return model + + +## Subtract opaque observation regions, retaining a visible density mark even +## when the ordinary floor band is completely behind a moving cabin. +func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Array[Rect2]: + for y: float in [band.position.y, cell.position.y, cell.end.y - band.size.y]: + var pieces: Array[Rect2] = [Rect2(Vector2(band.position.x, y), band.size)] + for obstacle: Rect2 in obstacles: + var remaining: Array[Rect2] = [] + for piece: Rect2 in pieces: + var cut := piece.intersection(obstacle) + if not cut.has_area(): + remaining.append(piece) + continue + for part: Rect2 in [Rect2(piece.position, Vector2(piece.size.x, cut.position.y-piece.position.y)), + Rect2(Vector2(piece.position.x, cut.end.y), Vector2(piece.size.x, piece.end.y-cut.end.y)), + Rect2(Vector2(piece.position.x, cut.position.y), Vector2(cut.position.x-piece.position.x, cut.size.y)), + Rect2(Vector2(cut.end.x, cut.position.y), Vector2(piece.end.x-cut.end.x, cut.size.y))]: + if part.has_area(): + remaining.append(part) + pieces = remaining + if not pieces.is_empty(): + return pieces + return [] + + +func _resolve_observation_labels(model: Dictionary) -> void: + var placed: Array[Dictionary] = [] + for i: int in model[&"cabins"].size(): + var cabin: Dictionary = model[&"cabins"][i] + placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]}) + placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]}) + for kind: String in ["business", "supply", "landing", "aggregation"]: + var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", []) + for i: int in collection.size(): + var badge: Dictionary = collection[i] + if kind in ["business", "supply"] and badge[&"kind"] != kind: + continue + var anchor: Vector2 = badge[&"at"] + var at := anchor + var displaced_by: Array[String] = [] + # Each collision advances to an existing right edge. Positive widths + # guarantee termination; touching edges are allowed. + while true: + var blocker: Dictionary = {} + for previous: Dictionary in placed: + if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area(): + blocker = previous + break + if blocker.is_empty(): + break + displaced_by.append(blocker[&"id"]) + at.x = (blocker[&"rect"] as Rect2).end.x + badge[&"anchor"] = anchor + badge[&"at"] = at + badge[&"displaced_by"] = displaced_by.duplicate(true) + placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])}) + + +func _draw_life_badges(canvas: CanvasItem) -> void: + for badge: Dictionary in _life_observation.get(&"badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + +func _draw_background() -> void: + var night := false + var clock := get_node_or_null("/root/GameClock") + if clock != null: + var hour := int(clock.call("get_hour")) + night = hour >= 18 or hour < 6 + var sky := Color("b5cbb5") if not night else Color("273c49") + if layer_mode == LayerMode.BACK: + sky = Color("526d68") if not night else Color("233d40") + draw_rect(Rect2(Vector2.ZERO, size), sky) + # Pixel silhouettes make the world frame distinct from the usable floors. + var horizon := size.y * 0.65 + for band in range(3): + var color := [Color("8cae98"), Color("749981"), Color("567e68")][band] as Color + if night: + color = color.darkened(0.48) + for x in range(-24, int(size.x)+64, 48): + var crown_y := horizon + band*46 - posmod(x*17+band*29, 83) + draw_rect(Rect2(x, crown_y, 48, size.y-crown_y), color) + draw_rect(Rect2(x+8, crown_y-12, 32, 12), color) + if night: + for i in range(28): + var at := Vector2(posmod(i*137+31, maxi(int(size.x),1)), posmod(i*47+13,maxi(int(horizon),1))) + draw_rect(Rect2(at,Vector2(2,2)),Color("c6d3b6")) + else: + for i in range(5): + var x := float(posmod(i*263+41, maxi(int(size.x),1))) + var y := 32.0+float(i%3)*27.0 + draw_rect(Rect2(x,y,86,12),Color("d5dec4")) + draw_rect(Rect2(x+17,y-7,46,7),Color("d5dec4")) + + +func _draw_world_cells(bounds: Dictionary, map_rect: Rect2) -> void: + var world: Variant = _service(&"world_service") + if world == null: + return + var p := _cell_dimensions().x / 16.0 + var ground := get_cell_rect(Vector3i(0,0,layer_mode)).end.y + var trunk := Rect2(map_rect.position-Vector2(9*p,22*p), map_rect.size+Vector2(18*p,24*p)) + draw_rect(trunk, Color("574d39")) + draw_rect(trunk.grow(-2*p), Color("776445")) + for x in range(int(trunk.position.x+4*p),int(trunk.end.x),maxi(int(7*p),1)): + var notch := float(posmod(x*11,17))*p + draw_rect(Rect2(x,trunk.position.y+notch,2*p,trunk.size.y-notch),Color("8a7350")) + # Branches, crown and roots belong to the scenery, never facility slots. + for side in [-1,1]: + var edge := trunk.position.x if side < 0 else trunk.end.x + for branch in range(3): + var y := trunk.position.y+(branch*24+22)*p + var x := edge-(18+branch*4)*p if side<0 else edge + draw_rect(Rect2(x,y,(18+branch*4)*p,5*p),Color("69583d")) + _draw_leaf_cluster(Vector2(x+(2 if side<0 else 14)*p,y-5*p),p,branch) + var root_x := edge-28*p if side<0 else edge-2*p + draw_rect(Rect2(root_x,ground-3*p,30*p,6*p),Color("65583e")) + draw_rect(Rect2(root_x+(10*p if side<0 else 0),ground-7*p,20*p,5*p),Color("776445")) + draw_rect(Rect2(trunk.position.x-12*p,trunk.position.y-14*p,trunk.size.x+24*p,17*p),Color("385d45")) + for i in range(17): + var arch := sin(float(i)/16.0*PI)*17.0 + _draw_leaf_cluster(Vector2(trunk.position.x+float(i)*trunk.size.x/16.0,trunk.position.y-4*p-arch*p-float(i%3)*3*p),p,i) + # Unbuilt space stays dark wood; completed rooms cut into it below. + for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"])+1): + for x in range(int(bounds[&"min_x"]),int(bounds[&"max_x"])+1): + var cell := Vector3i(x,y,layer_mode) + var rect := get_cell_rect(cell) + if not rect.intersects(Rect2(Vector2.ZERO,size)): + continue + var excavated := bool(world.call(&"is_excavated",cell)) if world.has_method(&"is_excavated") else true + if not excavated: + draw_rect(rect,Color("47463e")) + if posmod(x+y,3)==0: + draw_rect(Rect2(rect.position+Vector2(5,11)*p,Vector2(5,2)*p),Color("5a594b")) + elif bool(world.call(&"is_walkable",cell)): + draw_rect(rect.grow(-p), Color("aaa083") if layer_mode==LayerMode.FRONT else Color("829a8d")) + draw_rect(Rect2(rect.position.x,rect.end.y-4*p,rect.size.x,3*p),Color("b69465")) + draw_rect(Rect2(rect.position.x,rect.end.y-p,rect.size.x,p),Color("514839")) + if posmod(x,4)==0: + PixelArt.window(self,rect.position+Vector2(5,4)*p,p) + var kind := StringName(world.call(&"get_occupant_kind",cell)) + if kind==&"construction": + draw_rect(rect.grow(-p),Color("8d7951")) + _draw_hatch(rect.grow(-p),Color("e1bd6c"),rect.size.x) + if show_grid or build_definition != null or excavation_mode: + draw_rect(rect,Color("dfd4ad",0.25),false,1.0) + var label_at := Vector2(map_rect.position.x-32*p, get_cell_rect(Vector3i(0,y,layer_mode)).get_center().y) + var floor_name := "%dF" % (y+1) if y>=0 else "B%d" % -y + draw_rect(Rect2(label_at-Vector2(2,8)*p,Vector2(22,12)*p),Color("2f493f")) + draw_string(ThemeDB.fallback_font,label_at+Vector2(0,1)*p,floor_name,HORIZONTAL_ALIGNMENT_CENTER,18*p,clampi(int(7*p),10,18),Color("e4d9b9")) + # A narrow garden frames the entrance without hiding underground floors. + draw_rect(Rect2(map_rect.position.x-35*p,ground,map_rect.size.x+70*p,3*p),Color("58784c")) + var town := visual_theme.pixel_town_tiles if visual_theme != null else null + for side in [-1,1]: + var gx := map_rect.position.x-32*p if side<0 else map_rect.end.x+15*p + PixelArt.tile(self,town,4,Rect2(gx,ground-30*p,16*p,16*p)) + PixelArt.tile(self,town,16,Rect2(gx,ground-14*p,16*p,16*p)) + PixelArt.tile(self,town,2,Rect2(gx-12*p,ground-8*p,12*p,12*p)) + + +func _draw_leaf_cluster(center: Vector2, p: float, variation: int) -> void: + var c := center.round() + var shift := float(variation%3)*2.0 + draw_rect(Rect2(c-Vector2(24,5)*p,Vector2(48,12)*p),Color("385d45")) + draw_rect(Rect2(c-Vector2(20,10)*p,Vector2(40,20)*p),Color("4c7950")) + draw_rect(Rect2(c-Vector2(13+shift,15)*p,Vector2(29,25)*p),Color("4c7950")) + draw_rect(Rect2(c-Vector2(22,5)*p,Vector2(37,10)*p),Color("5e8b57")) + draw_rect(Rect2(c-Vector2(11+shift,13)*p,Vector2(23,7)*p),Color("739957")) + for i in range(9): + var offset := Vector2(posmod(i*11+variation*3,38)-19,posmod(i*7+variation,21)-11)*p + draw_rect(Rect2(c+offset,Vector2(3+i%2,2)*p),Color("95ad69") if i%3==0 else Color("648957")) + + +func _draw_life_overlay() -> void: + if _life_observation.is_empty(): + return + # These are actual child indices and the passes executed by this callback. + _life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()] + _life_observation[&"overlay_passes"] = [] + _life_observation[&"overlay_passes"].append(&"traffic") + _draw_congestion_overlay(_life_overlay) + _life_observation[&"overlay_passes"].append(&"cabins") + _draw_elevators(_life_overlay) + _draw_life_badges(_life_overlay) + _life_observation[&"overlay_passes"].append(&"actors") + for cabin: Dictionary in _life_observation.get(&"cabins", []): + for actor: Dictionary in cabin[&"passengers"]: + _draw_observed_actor(actor, _life_overlay) + _draw_actors(_life_overlay) + for badge: Dictionary in _life_observation.get(&"actor_aggregations", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay) + _life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"] + + +func _draw_congestion_overlay(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"traffic", []): + var traffic: Dictionary = row[&"traffic"] + var density := int(traffic[&"current_density"]) + var blocked := bool(traffic[&"temporary_block"]) + if density <= 0 and not blocked: + continue + var ratio := float(density) / maxi(int(traffic[&"base_capacity"]), 1) + var color := Color("e34332") if blocked or ratio > 1.0 else Color("e3a62f") if ratio >= 0.65 else Color("55c96b") + # At overview zoom a whole cell can be covered by opaque badges. Still + # submit its density band, beneath those later draws, and report no visible + # fragments. No traffic cell or raw density row is dropped. + if row[&"segment_rects"].is_empty(): + canvas.draw_rect(row[&"band_rect"], Color(color, 0.90)) + for segment: Rect2 in row[&"segment_rects"]: + canvas.draw_rect(segment, Color(color, 0.90)) + + +## Visual-only level comes from the existing live FacilityInstance, not the +## construction footprint dictionary. No upgrade or simulation value is written. +func _facility_visual_level(instance_id: StringName) -> int: + var service: Variant = _service(&"facility_service") + if service == null or not service.has_method(&"get_instance"): + return 1 + var instance: Variant = service.get_instance(instance_id) + return int(instance.level) if instance != null else 1 + + +func _completed_facilities_for_draw() -> Array: + if _drawing and _draw_cache.has(&"completed_facilities"): + return _draw_cache[&"completed_facilities"] + var construction: Variant = _service(&"construction_service") + var facilities: Array = construction.get_facilities() if construction != null else [] + if _drawing: + _draw_cache[&"completed_facilities"] = facilities + return facilities + + +func _construction_jobs_for_draw() -> Array: + if _drawing and _draw_cache.has(&"construction_jobs"): + return _draw_cache[&"construction_jobs"] + var construction: Variant = _service(&"construction_service") + var jobs: Array = construction.get_jobs() if construction != null else [] + if _drawing: + _draw_cache[&"construction_jobs"] = jobs + return jobs + + +func _other_layer_rooms() -> Array[Dictionary]: + var rooms: Array[Dictionary] = [] + for facility: Dictionary in _completed_facilities_for_draw(): + if int(facility.get(&"layer", 0)) == layer_mode: continue + var cells := _cells_from_serialized(facility.get(&"occupied_cells", [])) + if cells.is_empty(): continue + var rect := _cell_bounds_rect(cells) + if rect.intersects(Rect2(Vector2.ZERO, size)): + rooms.append({&"id": facility[&"instance_id"], &"rect": rect, &"definition_id": facility[&"definition_id"], &"origin_x": cells[0].x, + &"level": _facility_visual_level(StringName(facility[&"instance_id"]))}) + for job: ConstructionJob in _construction_jobs_for_draw(): + if job.layer == layer_mode or job.state in [ConstructionJob.State.COMPLETED, ConstructionJob.State.CANCELLED]: continue + if job.reserved_cells.is_empty(): continue + var rect := _cell_bounds_rect(job.reserved_cells) + if rect.intersects(Rect2(Vector2.ZERO, size)): + rooms.append({&"id":job.job_id, &"rect":rect, &"definition_id":job.definition_id, + &"origin_x":job.origin.x, &"building":true, &"progress":job.progress}) + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z == layer_mode: continue + var rect := _legacy_stair_rect(upper) + if rect.intersects(Rect2(Vector2.ZERO,size)): + rooms.append({&"id":stair[&"id"], &"rect":rect, &"legacy_stair":true, + &"definition_id":&"service_stairs" if upper.z==LayerMode.BACK else &"guest_stairs", &"origin_x":upper.x}) + return rooms + + +func _draw_other_layer() -> void: + # Opposite-layer footprints are visual context only. Hit testing still uses + # the selected layer's WorldService occupancy, including overlapping rooms. + var rooms := _other_layer_rooms() + if _inactive_layer_art != null: + _inactive_layer_art.visible = layer_mode == LayerMode.FRONT + _inactive_layer_art.set("rooms", rooms) + _inactive_layer_art.set("pixel_scale", _cell_dimensions().x / 16.0) + _inactive_layer_art.set("dungeon", visual_theme.pixel_dungeon_tiles if visual_theme != null else null) + _inactive_layer_art.set("town", visual_theme.pixel_town_tiles if visual_theme != null else null) + _inactive_layer_art.queue_redraw() + for room: Dictionary in rooms: + var rect: Rect2 = room[&"rect"] + if bool(room.get(&"building", false)): + var color := Color(0.95,0.76,0.35,0.45) + draw_rect(rect.grow(-2),color,false,1.0) + _draw_hatch(rect.grow(-3),Color(color,0.24),_cell_dimensions().x) + _draw_readable_badge(rect.position+Vector2(3,3),"%s 工事%d%%" % ["裏" if layer_mode==LayerMode.FRONT else "表",clampi(int(room[&"progress"])/100,0,100)]) + continue + if layer_mode == LayerMode.FRONT: + draw_rect(rect.grow(-2), Color(0.6, 0.87, 0.9, 0.45), false, 1.0) + else: + var color := Color(0.94, 0.83, 0.54, 0.55) + draw_rect(rect.grow(-2), color, false, 1.0) + draw_line(rect.position+Vector2(3,3), rect.end-Vector2(3,3), Color(color,0.2), 1.0) + + +func _draw_world_entity_icons() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_facilities"): + return + var p := _cell_dimensions().x / 16.0 + var dungeon := visual_theme.pixel_dungeon_tiles if visual_theme != null else null + var town := visual_theme.pixel_town_tiles if visual_theme != null else null + for value: Variant in _completed_facilities_for_draw(): + if not value is Dictionary or int(value.get(&"layer",0)) != layer_mode: + continue + var facility: Dictionary = value + var cells := _cells_from_serialized(facility.get(&"occupied_cells",[])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + if not rect.intersects(Rect2(Vector2.ZERO,size)): + continue + var id := StringName(facility.get(&"definition_id",&"")) + PixelArt.room(self,rect,id,p,dungeon,town,layer_mode==LayerMode.BACK,cells[0].x, _room_presentations.get(StringName(facility.get(&"instance_id", &"")), {}), _facility_visual_level(StringName(facility.get(&"instance_id", &"")))) + var instance_id := StringName(facility.get(&"instance_id",&"")) + # Thin status lamp conveys operational state without repainting a room. + draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id)) + if instance_id==selected_entity_id: + draw_rect(rect.grow(-p),Color("f5d582"),false,2*p) + + +func _readable_badge_rect(at: Vector2, text: String) -> Rect2: + # A conservative em-width box makes geometry independently reproducible. + # Text still uses the existing fallback font and size. + return Rect2(at, Vector2(text.length() * 10 + 6, 14)) + + +func _draw_readable_badge(at: Vector2, text: String, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var rect := _readable_badge_rect(at, text) + if not rect.intersects(Rect2(Vector2.ZERO, size)): + return + canvas.draw_rect(rect, Color("34413b")) + canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce")) + + +func _prepare_room_presentations() -> void: + var model := _observe_rooms() + _room_presentations = model[&"rooms"] + _carried_patients = model[&"carried_patients"] + _treated_patients = model[&"treated_patients"] + _resting_staff = model[&"resting_staff"] + + +func _observe_rooms() -> Dictionary: + var _room_presentations: Dictionary = {} + var _carried_patients: Dictionary = {} + var _treated_patients: Dictionary = {} + var _resting_staff: Dictionary = {} + var service: Variant = _service(&"facility_service") + if service == null: + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} + for instance: Variant in service.get_instances(): + if instance.origin.z == layer_mode: + _room_presentations[instance.instance_id] = RoomPresentation.describe(instance) + + var staff: Variant = _service(&"staff_service") + if staff != null: + if _runtime is Object and _runtime.has_method("get_staff_rest_report"): + for member: StaffRuntime in staff.get_staff_members(): + if member.state != StaffRuntime.State.OFF_DUTY: continue + var rest: Dictionary = _runtime.get_staff_rest_report(member.staff_id) + if not rest.get(&"resting", false): continue + _resting_staff[member.staff_id] = rest[&"facility_id"] + var room: Dictionary = _room_presentations.get(rest[&"facility_id"], {}) + if not room.is_empty(): + room[&"people"].append(member.staff_id) + room[&"count"] += 1 + for task: StaffTask in staff.get_tasks(): + if task.task_type != &"rescue_transport" or task.is_terminal() or task.assigned_staff_id.is_empty(): continue + if task.state == StaffTask.State.WORKING: + _treated_patients[task.target_entity_id] = task.rescue_facility_id + var data: Dictionary = _room_presentations.get(task.rescue_facility_id, {}) + if not data.is_empty(): + data[&"people"].append(task.target_entity_id) + data[&"count"] += 1 + else: + _carried_patients[task.target_entity_id] = task.assigned_staff_id + + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} + + +func _prepare_queues() -> void: + var model := _observe_queues() + _queue_positions = model[&"queue_positions"] + _queue_hidden = model[&"queue_hidden"] + _landing_badges.assign(model[&"landing_badges"]) + _transport_report = model[&"elevators"] + + +func _observe_queues() -> Dictionary: + var _queue_positions: Dictionary = {} + var _queue_hidden: Dictionary = {} + var _landing_badges: Array[Dictionary] = [] + var _transport_report: Dictionary = {} + var groups: Dictionary = {} + var facilities: Variant = _service(&"facility_service") + var guests: Variant = _service(&"guest_service") + if facilities != null and guests != null: + for instance: Variant in facilities.get_instances(): + for request_id: StringName in instance.queue_entries: + var request: Variant = facilities.get_request(request_id) + if request == null: + continue + for guest_id: StringName in request.guest_ids: + var guest: Variant = guests.get_guest(guest_id) + if guest != null and guest.state == GuestRuntime.State.QUEUING: + _add_queue_member(groups, "guest/"+String(guest_id), guest.current_cell) + var transport: Variant = _service(&"elevators") + if transport != null: + _transport_report = transport.get_report() + var trips: Array = _transport_report.get(&"trips", []) + trips.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a[&"enqueued_at"]) < int(b[&"enqueued_at"]) or (a[&"enqueued_at"] == b[&"enqueued_at"] and String(a[&"actor_id"]) < String(b[&"actor_id"]))) + for trip: Dictionary in trips: + if trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: + var cell: Variant = trip[&"from"] + if cell is Array: + cell = Vector3i(cell[0], cell[1], cell[2]) + _add_queue_member(groups, _elevator_actor_key(String(trip[&"actor_id"])), cell) + var lift_cells: Dictionary = {} + for shaft: Dictionary in _transport_report.get(&"shafts", []): + for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])): + for offset: int in range(maxi(int(shaft.get(&"width", 2)), 1)): + lift_cells[stop + Vector3i(offset, 0, 0)] = true + var reserved: Dictionary = {} + var cells: Array = groups.keys() + cells.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return a.y < b.y or (a.y == b.y and a.x < b.x)) + for cell: Vector3i in cells: + var members: Array = groups[cell] + var lane: Array[Vector3i] = [] + if not lift_cells.has(cell): + lane.append(cell) + var world: Variant = _service(&"world_service") + for direction: int in [-1, 1]: + for distance: int in range(1, 9): + var next := cell + Vector3i(direction*distance, 0, 0) + if world == null or not world.is_walkable(next): + break + if lift_cells.has(next): + break + var occupant: StringName = world.get_occupant(next) + var facility: Variant = facilities.get_instance(occupant) if facilities != null else null + if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: + break + lane.append(next) + var index := 0 + for target: Vector3i in lane: + if index >= members.size(): + break + if reserved.has(target): + continue + reserved[target] = true + _queue_positions[members[index]] = cell_to_canvas(target) + index += 1 + for hidden: int in range(index, members.size()): + _queue_hidden[members[hidden]] = true + var text := RoomPresentation.queue_label(members.size(), members.size()-index) + _landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text, &"cell": cell, &"members": members.duplicate(), &"visible_count": index, &"overflow": members.size()-index}) + + return {&"queue_positions": _queue_positions, &"queue_hidden": _queue_hidden, &"landing_badges": _landing_badges, &"elevators": _transport_report, &"shaft_footprint": lift_cells.keys()} + + +func _add_queue_member(groups: Dictionary, key: String, cell: Vector3i) -> void: + if cell.z != layer_mode: + return + if not groups.has(cell): + groups[cell] = [] + if not groups[cell].has(key): + groups[cell].append(key) + + +func get_legacy_stair_records() -> Array[Dictionary]: + if _drawing and _draw_cache.has(&"legacy_stair_records"): + return _draw_cache[&"legacy_stair_records"] + var result: Array[Dictionary] = [] + var service: Variant = _service(&"legacy_stairs") + if service == null or not service.has_method(&"create_snapshot"): + return result + var snapshot: Variant = service.call(&"create_snapshot") + if not snapshot is Dictionary: + return result + for row: Variant in snapshot.get(&"stairs", []): + if row is Dictionary and row.get(&"from") is Vector3i and row.get(&"to") is Vector3i: + var record: Dictionary = row.duplicate(true) + var path: Variant = _service(&"path_service") + var connector: Dictionary = path.call(&"get_connector", StringName(row[&"id"])) if path != null else {} + record[&"access_id"] = connector.get(&"access_id", &"") + result.append(record) + if _drawing: + _draw_cache[&"legacy_stair_records"] = result + return result + + +func legacy_stair_at_canvas(canvas_position: Vector2) -> StringName: + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z == layer_mode and _legacy_stair_rect(upper).has_point(canvas_position): + return StringName(stair[&"id"]) + return &"" + + +func _legacy_stair_rect(upper: Vector3i) -> Rect2: + var rect := get_cell_rect(upper) + # A small target leaves the rest of an existing room available for selection. + return Rect2(rect.position+rect.size*Vector2(0.49,0.24), rect.size*Vector2(0.5,0.7)) + + +func _draw_legacy_stairs() -> void: + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z != layer_mode: + continue + var rect := _legacy_stair_rect(upper) + if not rect.intersects(Rect2(Vector2.ZERO, size)): + continue + var p := rect.size.x/14.0 + draw_rect(rect, Color("3c4e40",0.5)) + PixelArt.stairs(self,rect,p,upper.z == LayerMode.BACK,true) + if StringName(stair[&"id"]) == selected_entity_id: + draw_rect(rect,Color("f5d582"),false,maxf(p,1)) + + +func _cell_bounds_rect(cells: Array[Vector3i]) -> Rect2: + if cells.is_empty(): + return Rect2() + var result := get_cell_rect(cells[0]) + for index: int in range(1, cells.size()): + result = result.merge(get_cell_rect(cells[index])) + return result + + +func _draw_hatch(rect: Rect2, color: Color, cell_size: float) -> void: + if cell_size < 8.0: + return + var offset := -rect.size.y + while offset < rect.size.x: + var start := Vector2(rect.position.x + maxf(offset, 0.0), rect.end.y - maxf(-offset, 0.0)) + var finish := Vector2(rect.position.x + minf(offset + rect.size.y, rect.size.x), rect.end.y - minf(rect.size.y, rect.size.x - offset)) + draw_line(start, finish, color, 1.0) + offset += maxf(6.0, cell_size * 0.45) + + +func _draw_construction_labels() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_jobs"): + return + var font := ThemeDB.fallback_font + for job: Variant in _construction_jobs_for_draw(): + if job == null or int(job.layer) != layer_mode or int(job.state) in [2, 3]: + continue + var rect := get_cell_rect(job.origin) + if rect.size.x < 12.0: + continue + var percent := clampi(int(job.progress) / 100, 0, 100) + draw_string(font, rect.position + Vector2(3, -3), "%d%%" % percent, HORIZONTAL_ALIGNMENT_LEFT, -1, clampi(int(rect.size.x * 0.45), 9, 13), Color("f5d58a")) + + +func _draw_facility_labels() -> void: + var construction: Variant = _service(&"construction_service") + var registry: Variant = _service(&"definition_registry") + if construction == null or not construction.has_method(&"get_facilities"): + return + for value: Variant in _completed_facilities_for_draw(): + if not value is Dictionary or int(value.get(&"layer",0))!=layer_mode: + continue + var facility: Dictionary = value + var cells := _cells_from_serialized(facility.get(&"occupied_cells",[])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + var id := StringName(facility.get(&"definition_id",&"")) + var hovered := _hovered_cell is Vector3i and cells.has(_hovered_cell) + var selected := StringName(facility.get(&"instance_id",&""))==selected_entity_id + if not hovered and not selected and rect.size.x<100: + continue + if id in [&"guest_corridor",&"service_corridor",&"staff_door",&"guest_stairs",&"service_stairs"] and not hovered and not selected: + continue + var definition: Variant = registry.call(&"get_facility",id) if registry != null else null + var label := String(definition.display_name) if definition != null else String(id) + var font_size := 12 + var label_width := minf(ThemeDB.fallback_font.get_string_size(label,HORIZONTAL_ALIGNMENT_LEFT,-1,font_size).x+12,rect.size.x) + var at := Vector2(rect.get_center().x-label_width*0.5,rect.position.y-17) + draw_rect(Rect2(at,Vector2(label_width,16)),Color("304b41",0.95)) + draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9")) + + +func _draw_elevators(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"cabins", []): + PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"]) + PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"]) + _draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas) + for badge: Dictionary in _life_observation.get(&"landing_badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + +func _observe_elevators(observation: Dictionary) -> Array[Dictionary]: + var cabins: Array[Dictionary] = [] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _elevator_tracks: Dictionary = observation[&"elevator_tracks"] + var report_value: Dictionary = observation[&"elevators"] + var alive: Dictionary = {} + var p := _cell_dimensions().x/16.0 + for value: Variant in report_value.get(&"shafts", []): + if not value is Dictionary: + continue + var shaft: Dictionary = value + var stops := _cells_from_serialized(shaft.get(&"stops", [])) + if stops.is_empty(): + continue + var shaft_id := StringName(shaft.get(&"shaft_id", &"")) + alive[shaft_id] = true + var passengers: Array = shaft.get(&"boarded", []) + for actor_id: Variant in passengers: + _boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true + var floor_value := float(shaft.get(&"visual_floor", shaft.get(&"current_floor", stops[0].y))) + var visual_floor := _tracked_elevator_floor(shaft_id, floor_value, observation) + if stops[0].z != layer_mode: + continue + var origin := get_cell_rect(stops[0]) + var width := maxi(int(shaft.get(&"width", 2)), 1) + var baseline := origin.end.y-float(visual_floor-stops[0].y)*_cell_dimensions().y-3*p + var cabin := Rect2(Vector2(origin.position.x+4*p,baseline-19*p).round(),Vector2(width*_cell_dimensions().x-8*p,19*p)) + if not cabin.intersects(Rect2(Vector2.ZERO,size)): + continue + var operational := bool(shaft.get(&"operational", true)) + var phase := StringName(shaft.get(&"phase", &"IDLE")) + var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"] + var passengers_drawn: Array[Dictionary] = [] + var seat_columns := maxi(int((cabin.size.x / p - 4.0) / 8.0), 1) + for index: int in range(passengers.size()): + var actor_key := _elevator_actor_key(String(passengers[index])) + var is_staff := actor_key.begins_with("staff/") + var person: Variant = null + var people: Variant = _service(&"staff_service" if is_staff else &"guest_service") + var getter := &"get_staff" if is_staff else &"get_guest" + if people != null and people.has_method(getter): + person = people.call(getter,StringName(actor_key.get_slice("/",1))) + var carrying := is_staff and person != null and not String(person.carried_inventory_id).is_empty() + var state := int(person.state) if person != null else 0 + var x := cabin.position.x + (2.0 + (index % seat_columns + 0.5)*8.0)*p + var feet := cabin.end.y - (2.0 + (int(index / seat_columns) % 2)*9.0)*p + var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5) + passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation)) + cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p, + &"operational": operational, &"doors_open": doors_open, + &"phase": phase, &"passenger_count": passengers.size(), + &"passengers": passengers_drawn, + &"badge_at": Vector2(cabin.position.x, cabin.position.y-14), + &"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)), + &"badge": RoomPresentation.elevator_label(shaft)}) + + for shaft_id: Variant in _elevator_tracks.keys(): + if not alive.has(shaft_id): + _elevator_tracks.erase(shaft_id) + + return cabins + + +func _elevator_actor_key(actor_id: String) -> String: + if actor_id.begins_with("guest:"): + return "guest/" + actor_id.trim_prefix("guest:") + if actor_id.begins_with("staff:"): + return "staff/" + actor_id.trim_prefix("staff:") + return actor_id + + +func _tracked_elevator_floor(shaft_id: StringName, current_floor: float, observation: Dictionary = {}) -> float: + var _elevator_tracks: Dictionary = observation.get(&"elevator_tracks", self._elevator_tracks) + if not _elevator_tracks.has(shaft_id): + _elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time} + var track: Dictionary = _elevator_tracks[shaft_id] + if not is_equal_approx(float(track[&"floor"]),current_floor): + track[&"from"] = float(track[&"floor"]) + track[&"floor"] = current_floor + track[&"at"] = _animation_time + var clock := get_node_or_null("/root/GameClock") + if clock != null and bool(clock.get("paused")): + return current_floor + var t := clampf((_animation_time-float(track[&"at"]))/0.42,0.0,1.0) + return lerpf(float(track[&"from"]),current_floor,t) + + +func _draw_actors(canvas: CanvasItem = null) -> void: + for row: Dictionary in _life_observation.get(&"actor_drawings", []): + _draw_observed_actor(row, canvas) + _draw_actor_bubble(row, canvas) + + +func _observe_actors(observation: Dictionary) -> Array[Dictionary]: + var rows: Array[Dictionary] = [] + var _actor_tracks: Dictionary = observation[&"actor_tracks"] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _queue_hidden: Dictionary = observation[&"queue_hidden"] + var _queue_positions: Dictionary = observation[&"queue_positions"] + var _carried_patients: Dictionary = observation[&"carried_patients"] + var _treated_patients: Dictionary = observation[&"treated_patients"] + var _resting_staff: Dictionary = observation[&"resting_staff"] + var counts: Dictionary = {} + var alive: Dictionary = {} + var service_slots: Dictionary = {} + var guest_service: Variant = _service(&"guest_service") + if guest_service != null and guest_service.has_method(&"get_guests"): + for guest: Variant in guest_service.call(&"get_guests"): + if guest == null: + continue + var key := "guest/"+String(guest.guest_id) + alive[key] = true + if _boarded_actor_ids.has(key) or _queue_hidden.has(key): + _actor_tracks.erase(key) + continue + var position := _tracked_actor_position(key,guest.current_cell,observation) + if guest.current_cell.z==layer_mode: + var receiving := not String(guest.current_service_id).is_empty() + if _queue_positions.has(key): + position = _queue_positions[key] + elif receiving: + position = _service_use_position(guest,position,service_slots,observation) + if not position.is_finite(): + continue + else: + position += _actor_offset(guest.current_cell,counts) + rows.append(_actor_drawing(position,key,false,int(guest.state),receiving,false,_guest_activity(guest),false,observation)) + var staff_service: Variant = _service(&"staff_service") + if staff_service != null and staff_service.has_method(&"get_staff_members"): + for staff: Variant in staff_service.call(&"get_staff_members"): + if staff == null: + continue + var key := "staff/"+String(staff.staff_id) + alive[key] = true + if _boarded_actor_ids.has(key) or _queue_hidden.has(key): + _actor_tracks.erase(key) + continue + var position := _tracked_actor_position(key,staff.current_cell,observation) + if staff.current_cell.z==layer_mode: + if _queue_positions.has(key): + rows.append(_actor_drawing(_queue_positions[key],key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) + continue + if _carried_patients.has(staff.staff_id): + var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id]) + if carrier != null: + position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0) + rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + if _resting_staff.has(staff.staff_id): + position = _resting_staff_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + if _treated_patients.has(staff.staff_id): + position = _rescue_treatment_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + rows.append(_actor_drawing(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts),observation)),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) + for key: Variant in _actor_tracks.keys(): + if not alive.has(key): + _actor_tracks.erase(key) + + return rows + + +func _staff_work_animation(staff: StaffRuntime) -> StringName: + if staff.role_id != &"cleaner": + return &"work" + # A staffed base is not a cleaning site. Animate only a live job at its + # authoritative destination, including after load and interruption. + var service: Variant = _service(&"staff_service") + var bridge: Variant = _service(&"cleaning_bridge") + var task: StaffTask = service.get_task(staff.current_task_id) if service != null else null + if staff.state == StaffRuntime.State.WORKING and task != null and task.task_type == FacilityCleaningBridge.TASK_TYPE and task.state == StaffTask.State.WORKING and bridge != null and bridge.task_can_work(task, staff): + return &"clean" + return &"idle" + + +func _staff_work_position(staff: Variant, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]: + return fallback + var service: Variant = _service(&"facility_service") + var facility: Variant = service.get_instance(staff.assigned_facility_id) if service != null else null + if facility == null: + return fallback + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(staff.assigned_facility_id) if world != null else [] + if not cells.has(staff.current_cell): + return fallback + var data: Dictionary = _room_presentations.get(staff.assigned_facility_id, {}) + var workers: Array = data.get(&"staff", []) + var slot := workers.find(staff.staff_id) + if slot < 0: + return fallback + var rect := _cell_bounds_rect(cells) + var p := _cell_dimensions().x / 16.0 + if rect.size.y < 40*p: + return fallback + var columns := maxi(int(rect.size.x / (16*p)), 1) + if slot >= columns: + return fallback + return Vector2(rect.position.x+(slot+0.5)*rect.size.x/columns, rect.position.y+26*p-_cell_dimensions().y*0.5) + + +func _tracked_actor_position(key: String, cell: Vector3i, observation: Dictionary = {}) -> Vector2: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) + if not _actor_tracks.has(key): + _actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0} + var track: Dictionary = _actor_tracks[key] + var previous: Vector3i = track[&"cell"] + if previous != cell: + track[&"from"] = previous if previous.z==cell.z and absi(previous.x-cell.x)+absi(previous.y-cell.y)==1 else cell + track[&"cell"] = cell + track[&"at"] = _animation_time + if previous.x!=cell.x: + track[&"direction"] = 1.0 if cell.x>previous.x else -1.0 + var clock := get_node_or_null("/root/GameClock") + var t := clampf((_animation_time-float(track[&"at"]))/0.42,0.0,1.0) + # Pausing displays the authoritative cell, never an ambiguous half-step. + if clock != null and bool(clock.get("paused")): + t = 1.0 + return cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(cell),t) + + +func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + # Guest paths stop at an access cell. While a service is active, project the + # occupant into that facility's use position; authoritative paths stay intact. + var id := StringName(guest.target_facility_id) + var world: Variant = _service(&"world_service") + if world==null or id.is_empty(): + return fallback + var cells := _cells_from_serialized(world.call(&"get_entity_cells",id)) + if cells.is_empty() or cells[0].z!=layer_mode: + return fallback + var rect := _cell_bounds_rect(cells) + var data: Dictionary = _room_presentations.get(id, {}) + var slot := (data.get(&"people", []) as Array).find(guest.guest_id) + if slot < 0: + return fallback + var p := _cell_dimensions().x/16.0 + var anchors := RoomPresentation.slots(rect, p, _guest_activity(guest)) + if slot >= anchors.size(): + return Vector2(INF, INF) + var at := anchors[slot] + if _guest_activity(guest) in [&"standard_room", &"premium_room"]: + at.y -= 5*p + return at + Vector2(0, 3*p-_cell_dimensions().y*0.5) + + + +func _guest_activity(guest: Variant) -> StringName: + if String(guest.target_facility_id).is_empty(): + return &"" + var service: Variant = _service(&"facility_service") + var facility: Variant = service.call(&"get_instance",guest.target_facility_id) if service != null else null + return StringName(facility.definition_id) if facility != null else &"" + + +func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false) -> void: + _draw_observed_actor(_actor_drawing(center, key, staff, state, receiving, carrying, activity, riding)) + + +func _draw_observed_actor(row: Dictionary, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var at: Vector2 = row[&"at"] + var p: float = row[&"scale"] + canvas.draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2)) + People.draw(canvas, at, p, row[&"mode"], row[&"frame"], row[&"variant"], row[&"staff"], row[&"flip"]) + + +func _actor_drawing(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false, observation: Dictionary = {}) -> Dictionary: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) + var _queue_positions: Dictionary = observation.get(&"queue_positions", self._queue_positions) + var world_pixel := _cell_dimensions().x / 16.0 + var scale := 0.5 if riding else 0.75 + var p := maxf(floorf(world_pixel * scale), 1.0) if world_pixel >= 2.0 else world_pixel * scale + var track: Dictionary = _actor_tracks.get(key, {}) + var moving: bool = not receiving and not _queue_positions.has(key) and track.get(&"cell", Vector3i.ZERO) != track.get(&"from", Vector3i.ZERO) and _animation_time - float(track.get(&"at", 0.0)) < 0.42 + var clock := get_node_or_null("/root/GameClock") + var paused := clock != null and bool(clock.get("paused")) + moving = moving and not paused and not riding + var variant := int(absi(key.hash())) % 15 + var frame := int(_animation_time * 12.0) + variant + var mode: StringName = &"idle" + if staff and state == StaffRuntime.State.INCAPACITATED: + mode = &"collapsed" + elif receiving and activity in [&"standard_room", &"premium_room"]: + mode = &"standard_room_sleep" if activity == &"standard_room" else &"sleep" + elif moving: + mode = &"carry_walk" if carrying else &"walk" + elif carrying: + mode = &"carry" + elif staff and state == StaffRuntime.State.WORKING and not riding: + mode = activity if activity in [&"clean", &"idle", &"work"] else &"work" + elif receiving and activity in [&"cafeteria", &"tavern"]: + mode = &"eat" + elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING): + mode = &"wait" + var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round() + return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode, + &"frame": frame, &"variant": variant, &"staff": staff, &"state": state, + &"receiving": receiving, &"carrying": carrying, &"activity": activity, + &"riding": riding, &"flip": float(track.get(&"direction", 1.0)) < 0.0} + + +## Identity cores are tied to the unchanged PixelPeople poses, not sprite boxes. +func _life_core(row: Dictionary) -> Rect2: + var phase := int(row[&"frame"]) % 8 + var core := Rect2(6, 1, 6, 12) + if row[&"mode"] in [&"sleep", &"collapsed", &"standard_room_sleep"]: + core = Rect2(2, 11, 13, 5) + if row[&"mode"] == &"standard_room_sleep" and phase in [2, 3, 4]: + core.position.y -= 1 + core.size.y += 1 + elif row[&"mode"] in [&"walk", &"carry_walk"] and phase in [2, 6]: + core.position.y -= 1 + if row[&"flip"]: + core.position.x = 16-core.end.x + return Rect2(row[&"at"]+core.position*row[&"scale"], core.size*row[&"scale"]) + + +func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dictionary: + var track: Dictionary = model[&"actor_tracks"].get(key, {}) + var transit: bool = not model[&"paused"] and track.get(&"from", cell) != cell and _animation_time-float(track.get(&"at", 0.0)) < 0.42 + var rect := get_cell_rect(cell) + if transit: + rect = rect.merge(get_cell_rect(track[&"from"])) + return {&"bounds": rect, &"transit": transit} + + +## Keep non-passengers on a real corridor landing when a cabin crosses their pose. +func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary: + var blocked := false + for cabin: Dictionary in model[&"cabins"]: + if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]): + blocked = true + break + if not blocked: + return region + var world: Variant = _service(&"world_service") + var facilities: Variant = _service(&"facility_service") + var footprint: Array = model[&"shaft_footprint"] + var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z) + origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + var lane: Array[Vector3i] = [] + for origin: Vector3i in origins: + for direction: int in [-1, 1]: + for distance: int in range(1, 9): + var target := origin + Vector3i(direction*distance, 0, 0) + if world == null or footprint.has(target) or not world.is_walkable(target): + break + var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null + if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: + break + if not lane.has(target): + lane.append(target) + lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + if lane.is_empty(): + var anchor := get_cell_rect(cell).position+Vector2(-80, -16) + var nearest := INF + for badge: Dictionary in model[&"landing_badges"]: + var landing: Vector3i = badge[&"cell"] + if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest: + nearest = absf(landing.x-cell.x) + anchor = badge[&"at"] + return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor} + var selected: Vector3i = lane[0] + for target: Vector3i in lane: + if not model[&"queue_positions"].values().has(cell_to_canvas(target)): + selected = target + break + return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)} + + +func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary: + var key: String = row[&"key"] + var cell: Vector3i = person[&"cell"] + var result := _life_track_region(key, cell, model) + var p := _cell_dimensions().x/16.0 + var world: Variant = _service(&"world_service") + var facility_id: StringName = &"" + if model[&"queue_positions"].has(key): + result = {&"bounds": Rect2(model[&"queue_positions"][key]-_cell_dimensions()*0.5, _cell_dimensions()), &"transit": false} + elif not row[&"staff"]: + if not String(person[&"service"]).is_empty(): + var room: Dictionary = model[&"rooms"].get(person[&"target"], {}) + if (room.get(&"people", []) as Array).has(person[&"id"]): + facility_id = person[&"target"] + else: + var id: StringName = person[&"id"] + if model[&"carried_patients"].has(id): + for carrier: Dictionary in model[&"staff"]: + if carrier[&"id"] == model[&"carried_patients"][id]: + result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model) + var rect: Rect2 = result[&"bounds"] + rect = rect.grow(p) + rect.position.y -= 12*p + result[&"bounds"] = rect + if result.has(&"center"): + result[&"center"] -= Vector2(0, 12*p) + elif model[&"resting_staff"].has(id): + facility_id = model[&"resting_staff"][id] + elif model[&"treated_patients"].has(id): + facility_id = model[&"treated_patients"][id] + elif person[&"state"] in ["WORKING", "AVAILABLE"] and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(person[&"facility"]) + var room: Dictionary = model[&"rooms"].get(person[&"facility"], {}) + var slot := (room.get(&"staff", []) as Array).find(id) + var rect := _cell_bounds_rect(cells) + var columns := maxi(int(rect.size.x/(16*p)), 1) + if cells.has(cell) and slot >= 0 and slot < columns and rect.size.y >= 40*p: + result = {&"bounds": Rect2(rect.position+Vector2(slot*rect.size.x/columns, 0), Vector2(rect.size.x/columns, rect.size.y)), &"transit": false} + if not facility_id.is_empty() and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) + if not cells.is_empty(): + result = {&"bounds": _cell_bounds_rect(cells), &"transit": false} + if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false): + result = _life_landing_region(result, cell, model) + result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p) + return result + + +func _life_scaled(row: Dictionary, scale: float) -> Dictionary: + var result := row.duplicate(true) + result[&"scale"] = scale + result[&"at"] = (row[&"center"]+Vector2(-8*scale, _cell_dimensions().y*0.5-3*_cell_dimensions().x/16.0-17*scale)).round() + return result + + +## Forward/backward packing keeps each member's own interval. No relocation. +func _life_pack(group: Array, scale: float) -> Array: + var trial: Array = [] + var right := -INF + for member: Dictionary in group: + var row := _life_scaled(member[&"row"], minf(scale, member[&"row"][&"scale"])) + var core := _life_core(row) + var bounds: Rect2 = member[&"bounds"] + var x := maxf(maxf(core.position.x, bounds.position.x), right) + row[&"at"].x += x-core.position.x + right = x+core.size.x + trial.append(row) + var left := INF + for i: int in range(trial.size()-1, -1, -1): + var row: Dictionary = trial[i] + var core := _life_core(row) + var bounds: Rect2 = group[i][&"bounds"] + var x := minf(core.position.x, minf(bounds.end.x, left)-core.size.x) + row[&"at"].x += x-core.position.x + left = x + if x < bounds.position.x or core.position.y < bounds.position.y or core.end.y > bounds.end.y: + return [] + return trial + + +## Rebuild vertical components at every fitted scale. Ceilings only decrease; +## once none decreases, the feet/cores and therefore the partition are fixed. +func _life_fitted_groups(members: Array) -> Array: + var groups: Array = [] + while true: + for member: Dictionary in members: + member[&"row"].merge(_life_scaled(member[&"row"], member[&"row"][&"scale"]), true) + members.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: + var ay := _life_core(a[&"row"]).position.y + var by := _life_core(b[&"row"]).position.y + return ay < by or (ay == by and a[&"row"][&"key"] < b[&"row"][&"key"])) + groups = [] + var bottom := -INF + for member: Dictionary in members: + var core := _life_core(member[&"row"]) + if groups.is_empty() or core.position.y >= bottom: + groups.append([]) + bottom = core.end.y + else: + bottom = maxf(bottom, core.end.y) + groups.back().append(member) + var reduced := false + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var ceiling := 1.0 + for member: Dictionary in group: + ceiling = maxf(ceiling, member[&"row"][&"scale"]) + if _life_pack(group, ceiling).is_empty() and ceiling > 1: + for member: Dictionary in group: + var scale := minf(member[&"row"][&"scale"], maxf(1, int(ceiling)-1)) + member[&"row"].merge(_life_scaled(member[&"row"], scale), true) + reduced = true + if not reduced: + return groups + return groups + + +func _fit_life_actors(model: Dictionary) -> void: + var people: Dictionary = {} + for person: Dictionary in model[&"guests"]: + people["guest/"+String(person[&"id"])] = person + for person: Dictionary in model[&"staff"]: + people["staff/"+String(person[&"id"])] = person + var members: Array = [] + var stranded: Array = [] + var all_rows: Array = model[&"actor_drawings"].duplicate() + var passenger_bounds: Dictionary = {} + for cabin: Dictionary in model[&"cabins"]: + for row: Dictionary in cabin[&"passengers"]: + all_rows.append(row) + passenger_bounds[row[&"key"]] = (cabin[&"rect"] as Rect2).grow(-_cell_dimensions().x/16.0) + for row: Dictionary in all_rows: + var person: Dictionary = people[row[&"key"]] + row[&"cell"] = person[&"cell"] + var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false} + if region.has(&"center"): + var jitter := Vector2.ZERO + if not model[&"carried_patients"].has(person[&"id"]): + var track: Dictionary = model[&"actor_tracks"][row[&"key"]] + var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1) + jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t) + row[&"center"] = region[&"center"]+jitter + row.merge(_life_scaled(row, row[&"scale"]), true) + region[&"row"] = row + if region.get(&"no_lane", false): + stranded.append(region) + elif not region[&"transit"]: + members.append(region) + var groups := _life_fitted_groups(members) + model[&"actor_aggregations"] = [] + var hidden: Dictionary = {} + for member: Dictionary in stranded: + var row: Dictionary = member[&"row"] + hidden[row[&"key"]] = true + var aggregate: Dictionary = {} + for existing: Dictionary in model[&"actor_aggregations"]: + if existing[&"at"] == member[&"badge_at"]: + aggregate = existing + break + if aggregate.is_empty(): + aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""} + model[&"actor_aggregations"].append(aggregate) + aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + aggregate[&"text"] = "+%d" % aggregate[&"members"].size() + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var base := 1.0 + for member: Dictionary in group: + base = maxf(base, member[&"row"][&"scale"]) + var fitted := _life_pack(group, base) + var retained: Array = group + if fitted.is_empty(): + retained = [] + var identities: Array = [] + var drawn: Array = [] + for member: Dictionary in group: + var row: Dictionary = member[&"row"] + identities.append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + var attempt := retained.duplicate() + attempt.append(member) + var packed := _life_pack(attempt, 1) + if not packed.is_empty(): + retained = attempt + fitted = packed + drawn.append(row[&"key"]) + else: + hidden[row[&"key"]] = true + model[&"actor_aggregations"].append({&"members": identities, &"drawn_keys": drawn, &"at": (group[0][&"bounds"] as Rect2).position, &"text": "+%d" % (group.size()-retained.size())}) + for i: int in fitted.size(): + retained[i][&"row"].merge(fitted[i], true) + model[&"actor_drawings"] = model[&"actor_drawings"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + for cabin: Dictionary in model[&"cabins"]: + cabin[&"passengers"] = cabin[&"passengers"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + + +## P24 overhead bubble for one already observed and fitted actor row. Presentation +## only: reads authoritative Staff/Guest state and RelationshipState at draw time, +## never writes the life observation report or its rows, so the P23 frame report +## stays exactly what was observed. Riding passengers and aggregated (hidden) +## actors have no row here and therefore never keep a stale symbol. +func _draw_actor_bubble(row: Dictionary, canvas: CanvasItem = null) -> void: + if bool(row.get(&"riding", false)): + return + var key := String(row.get(&"key", "")) + var bubble: StringName = OperationFeedbackScript.BUBBLE_NONE + if key.begins_with("staff/"): + var staff_id := StringName(key.trim_prefix("staff/")) + var staff_service: Variant = _service(&"staff_service") + var staff: Variant = staff_service.get_staff(staff_id) if staff_service != null and staff_service.has_method(&"get_staff") else null + bubble = OperationFeedbackScript.resolve_staff_bubble(staff, _staff_relationship(staff_id)) + elif key.begins_with("guest/"): + var guest_id := StringName(key.trim_prefix("guest/")) + var guest_service: Variant = _service(&"guest_service") + var guest: Variant = guest_service.get_guest(guest_id) if guest_service != null and guest_service.has_method(&"get_guest") else null + bubble = OperationFeedbackScript.resolve_guest_bubble(guest, _guest_relationship(guest_id)) + if bubble != OperationFeedbackScript.BUBBLE_NONE: + _draw_unit_bubble(row[&"center"], bubble, canvas, row.get(&"at", Vector2.INF), float(row.get(&"scale", 0.0))) + + +func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName, canvas: CanvasItem = null, actor_at := Vector2.INF, actor_scale := 0.0) -> void: + if canvas == null: + canvas = self + var world_pixel := _cell_dimensions().x / 16.0 + var p := maxf(floorf(world_pixel * 0.75), 1.0) if world_pixel >= 2.0 else world_pixel * 0.75 + var bubble_pos := actor_center + Vector2(-9 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 32 * p) + if actor_scale > 0.0 and actor_at.is_finite(): + # Fitted P23 rows carry their own sprite origin and scale; follow them. + p = actor_scale + bubble_pos = actor_at + Vector2(-1 * p, -15 * p) + var bubble_size := Vector2(18 * p, 13 * p) + var rect := Rect2(bubble_pos.round(), bubble_size.round()) + canvas.draw_rect(rect, Color("ffffff", 0.94)) + canvas.draw_rect(rect, Color("303c40", 0.85), false, 1.0) + var tri_p1 := Vector2(rect.get_center().x - 2.5 * p, rect.end.y) + var tri_p2 := Vector2(rect.get_center().x + 2.5 * p, rect.end.y) + var tri_p3 := Vector2(rect.get_center().x, rect.end.y + 3.5 * p) + canvas.draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color("ffffff", 0.94)) + canvas.draw_line(tri_p1, tri_p3, Color("303c40", 0.85), 1.0) + canvas.draw_line(tri_p2, tri_p3, Color("303c40", 0.85), 1.0) + + var font := ThemeDB.fallback_font + var font_size := clampi(int(9 * p), 9, 14) + match bubble_type: + OperationFeedbackScript.BUBBLE_ZZZ: + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "Zzz", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("2d68c4")) + OperationFeedbackScript.BUBBLE_ANGER: + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "💢", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("d63031")) + OperationFeedbackScript.BUBBLE_HEART: + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "♥", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("e84393")) + + +func _staff_relationship(staff_id: StringName) -> Variant: + var service: Variant = _service(&"relationship_service") + if service == null or not service.has_method(&"get_relationships"): + return null + for rel_val: Variant in service.call(&"get_relationships"): + if rel_val is RelationshipState: + if rel_val.actor_a_id == staff_id or rel_val.actor_b_id == staff_id: + return rel_val + return null + + +func _guest_relationship(guest_id: StringName) -> Variant: + var service: Variant = _service(&"relationship_service") + if service == null or not service.has_method(&"get_relationships"): + return null + for rel_val: Variant in service.call(&"get_relationships"): + if rel_val is RelationshipState: + if rel_val.actor_a_id == guest_id or rel_val.actor_b_id == guest_id: + return rel_val + return null + + +func _draw_excavation_jobs() -> void: + var excavation: Variant = _service(&"excavation_service") + if excavation == null or not excavation.has_method(&"get_jobs"): + return + var font := ThemeDB.fallback_font + for job_value: Variant in excavation.call(&"get_jobs"): + if not job_value is Dictionary: + continue + var job: Dictionary = job_value + var state := StringName(job.get(&"state", &"")) + if state == &"COMPLETED": + continue + var position: Variant = job.get(&"position", null) + if not position is Vector2i: + continue + var cell := Vector3i(position.x, position.y, layer_mode) + var rect := get_cell_rect(cell).grow(-1.0) + if rect.size == Vector2.ZERO: + continue + var paused := state == &"PAUSED" + draw_rect(rect, Color("d69a56", 0.26) if paused else Color("4ca5c7", 0.28)) + draw_rect(rect, Color("efbd72") if paused else Color("79d3e7"), false, 2.0) + if rect.size.x >= 12.0: + var percent := clampi(int(job.get(&"progress", 0)) / 100, 0, 100) + draw_string( + font, + rect.position + Vector2(3.0, rect.size.y - 3.0), + "掘 %d%%" % percent, + HORIZONTAL_ALIGNMENT_LEFT, + -1, + clampi(int(rect.size.x * 0.38), 8, 12), + Color("e5f8fa") + ) + + +func _draw_build_preview() -> void: + if build_definition == null or not _hovered_cell is Vector3i: + return + var preview_cells := _cells_from_serialized(_last_preview_report.get(&"cells", [])) + if preview_cells.is_empty(): + preview_cells = _fallback_build_cells(_hovered_cell) + var valid := bool(_last_preview_report.get(&"can_build", false)) + var fill := Color("64d48c", 0.34) if valid else Color("e16262", 0.38) + var edge := Color("8ff0ab") if valid else Color("ff8a80") + for cell: Vector3i in preview_cells: + if cell.z != layer_mode: + continue + var rect := get_cell_rect(cell).grow(-1.0) + draw_rect(rect, fill) + draw_rect(rect, edge, false, 2.0) + + _draw_build_connection(_last_preview_report.get(&"connection", {})) + +func _draw_excavation_preview() -> void: + if not excavation_mode or not _hovered_cell is Vector3i: + return + var preview_cells := _cells_from_serialized( + _last_excavation_preview_report.get(&"cells", []) + ) + if preview_cells.is_empty(): + preview_cells = [Vector3i(_hovered_cell.x, _hovered_cell.y, layer_mode)] + var valid := bool(_last_excavation_preview_report.get(&"ok", false)) + var fill := Color("52bdd0", 0.35) if valid else Color("e16262", 0.38) + var edge := Color("8ce6ee") if valid else Color("ff8a80") + for cell: Vector3i in preview_cells: + if cell.z != layer_mode: + continue + var rect := get_cell_rect(cell).grow(-1.0) + draw_rect(rect, fill) + draw_rect(rect, edge, false, 2.0) + + +func _draw_hover() -> void: + if not _hovered_cell is Vector3i or build_definition != null or excavation_mode: + return + var rect := get_cell_rect(_hovered_cell).grow(-1.0) + draw_rect(rect, Color("d7ebef", 0.75), false, 1.5) + + +func _draw_map_caption(_map_rect: Rect2) -> void: + var font := ThemeDB.fallback_font + var caption := "世界樹の宿 / お客様のフロア" if layer_mode==LayerMode.FRONT else "世界樹の宿 / 従業員と物流" + draw_rect(Rect2(14,12,244,26),Color("29483d",0.93)) + draw_string(font,Vector2(24,30),caption,HORIZONTAL_ALIGNMENT_LEFT,-1,13,Color("f0e2bd")) + var status := "表示 %d%% · %s · G マス目%s" % [int(zoom_level*100), "裏層:半透明" if layer_mode == LayerMode.FRONT else "表層:輪郭", " ON" if show_grid else ""] + var status_width := font.get_string_size(status,HORIZONTAL_ALIGNMENT_LEFT,-1,12).x + 20.0 + draw_rect(Rect2(size.x-status_width-14,12,status_width,26),Color("29483d",0.93)) + draw_string(font,Vector2(size.x-status_width-4,30),status,HORIZONTAL_ALIGNMENT_LEFT,-1,12,Color("f0e2bd")) + + +func _draw_center_message(message: String) -> void: + var font := ThemeDB.fallback_font + draw_string(font, Vector2(0, size.y * 0.5), message, HORIZONTAL_ALIGNMENT_CENTER, size.x, 16, Color("8ca4af")) + + +func _actor_offset(cell: Vector3i, counts: Dictionary) -> Vector2: + var count := int(counts.get(cell,0)) + counts[cell] = count+1 + var p := _cell_dimensions().x/16.0 + var slots := [0.0,4.0,-4.0,2.0,-2.0,6.0,-6.0] + return Vector2(float(slots[count%slots.size()])*p,-float(count%2)*p) + + +func _facility_color(instance_id: StringName) -> Color: + var facility_service: Variant = _service(&"facility_service") + if facility_service == null or not facility_service.has_method(&"get_instance"): + return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582") + var instance: Variant = facility_service.call(&"get_instance", instance_id) + if instance == null: + return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582") + match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)): + FacilityInstance.State.BROKEN: + return Color("a24b45") + FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF: + return Color("a77d3f") + FacilityInstance.State.CLOSED: + return Color("6b6258") + FacilityInstance.State.DEGRADED: + return Color("89914d") + _: + return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e") + + +func _fallback_build_cells(origin: Vector3i) -> Array[Vector3i]: + var result: Array[Vector3i] = [] + if build_definition == null: + return result + var footprint: Variant = build_definition.get(&"footprint") if build_definition is Object else null + if build_definition is Dictionary: + footprint = build_definition.get(&"footprint", Vector2i.ONE) + if not footprint is Vector2i: + footprint = Vector2i.ONE + var dimensions: Vector2i = footprint + if build_rotation in [90, 270]: + dimensions = Vector2i(footprint.y, footprint.x) + for y_offset in range(dimensions.y): + for x_offset in range(dimensions.x): + result.append(origin + Vector3i(x_offset, y_offset, 0)) + return result + + +func _fallback_cells_are_available(origin: Vector3i) -> bool: + var world: Variant = _service(&"world_service") + if world == null: + return false + for cell: Vector3i in _fallback_build_cells(origin): + if world.has_method(&"is_in_bounds") and not bool(world.call(&"is_in_bounds", cell)): + return false + if world.has_method(&"get_occupant") and not StringName(world.call(&"get_occupant", cell)).is_empty(): + return false + return true + + +func _set_hovered_cell(value: Variant) -> void: + if value == _hovered_cell: + return + _hovered_cell = value + hovered_cell_changed.emit(_hovered_cell) + _update_build_preview() + _update_excavation_preview() + queue_redraw() + + +func _update_build_preview() -> void: + _preview_topology_key = _current_preview_topology() + _last_preview_report = get_build_preview_report() + build_preview_changed.emit(_last_preview_report.duplicate(true)) + + +func _update_excavation_preview() -> void: + _last_excavation_preview_report = ( + get_excavation_preview_report() + if excavation_mode else INVALID_EXCAVATION_PREVIEW.duplicate(true) + ) + excavation_preview_changed.emit(_last_excavation_preview_report.duplicate(true)) + + +func _on_mouse_exited() -> void: + _set_hovered_cell(null) + + +func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void: + _connection_cache_key.clear() + _connection_cache_report.clear() + queue_redraw() + _update_build_preview() + _update_excavation_preview() + + +func _connect_runtime() -> void: + if _runtime == null or not _runtime is Object or not _runtime.has_signal(&"snapshot_published"): + return + var notification: StringName = &"state_updated" if _runtime.has_signal(&"state_updated") else &"snapshot_published" + var callback := Callable(self, "_on_snapshot_published") + if not _runtime.is_connected(notification, callback): + _runtime.connect(notification, callback) + + +func _disconnect_runtime() -> void: + if _runtime == null or not _runtime is Object or not _runtime.has_signal(&"snapshot_published"): + return + var notification: StringName = &"state_updated" if _runtime.has_signal(&"state_updated") else &"snapshot_published" + var callback := Callable(self, "_on_snapshot_published") + if _runtime.is_connected(notification, callback): + _runtime.disconnect(notification, callback) + + +func _service(property_name: StringName) -> Variant: + if _runtime == null or not _runtime is Object: + return null + if _drawing and _draw_cache.has(property_name): + return _draw_cache[property_name] + for property: Dictionary in _runtime.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + var value = _runtime.get(property_name) + if _drawing: + _draw_cache[property_name] = value + return value + return null + + +func _world_bounds() -> Dictionary: + if _drawing and _draw_cache.has(&"bounds"): + return _draw_cache[&"bounds"] + var world: Variant = _service(&"world_service") + if world == null or not world.has_method(&"get_bounds"): + return {} + var result: Variant = world.call(&"get_bounds") + if not result is Dictionary: + return {} + for key: StringName in [&"min_x", &"max_x", &"min_y", &"max_y"]: + if not result.has(key) and not result.has(String(key)): + return {} + var result_value = { + &"min_x": int(result.get(&"min_x", result.get("min_x", 0))), + &"max_x": int(result.get(&"max_x", result.get("max_x", -1))), + &"min_y": int(result.get(&"min_y", result.get("min_y", 0))), + &"max_y": int(result.get(&"max_y", result.get("max_y", -1))), + } + + if _drawing: + _draw_cache[&"bounds"] = result_value + return result_value + + +func _cells_from_serialized(value: Variant) -> Array[Vector3i]: + var result: Array[Vector3i] = [] + if not value is Array: + return result + for raw_cell: Variant in value: + if raw_cell is Vector3i: + result.append(raw_cell) + elif raw_cell is Array and raw_cell.size() == 3: + result.append(Vector3i(int(raw_cell[0]), int(raw_cell[1]), int(raw_cell[2]))) + return result + + +func _serialize_cells(cells: Array[Vector3i]) -> Array[Array]: + var result: Array[Array] = [] + for cell: Vector3i in cells: + result.append([cell.x, cell.y, cell.z]) + return result + + +func _normalized_rotation(value: int) -> int: + return posmod(value, 360) / 90 * 90 + + +func _rescue_treatment_position(patient_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _treated_patients: Dictionary = observation.get(&"treated_patients", self._treated_patients) + var facility_id: StringName = _treated_patients[patient_id] + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] + if cells.is_empty(): return fallback + var data: Dictionary = _room_presentations.get(facility_id, {}) + var slot := (data.get(&"people", []) as Array).find(patient_id) + var p := _cell_dimensions().x/16.0 + var anchors := RoomPresentation.slots(_cell_bounds_rect(cells), p, &"infirmary") + if slot < 0 or slot >= anchors.size(): return Vector2(INF,INF) + return anchors[slot]+Vector2(0,-2*p-_cell_dimensions().y*0.5) + + +func _resting_staff_position(staff_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _resting_staff: Dictionary = observation.get(&"resting_staff", self._resting_staff) + var facility_id: StringName = _resting_staff[staff_id] + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] + if cells.is_empty(): return fallback + var data: Dictionary = _room_presentations.get(facility_id, {}) + var slot := (data.get(&"people", []) as Array).find(staff_id) + var anchors := RoomPresentation.slots(_cell_bounds_rect(cells), _cell_dimensions().x/16.0, &"staff_waiting_room") + if slot < 0 or slot >= anchors.size(): return Vector2(INF,INF) + return anchors[slot]+Vector2(0,-_cell_dimensions().y*0.5) + + +func _can_drop_data(_position: Vector2, data: Variant) -> bool: + if modal_input_blocked: + return false + return data is Dictionary and data.get(&"kind", &"") == &"staff_assignment" and data.get(&"staff_id", &"") is StringName + + +func _drop_data(position: Vector2, data: Variant) -> void: + if _can_drop_data(position, data): + staff_assignment_dropped.emit(data, facility_at_canvas(position)) + + +func _current_preview_topology() -> Array: + var world: Variant = _service(&"world_service") + var path: Variant = _service(&"path_service") + return [world.revision if world != null else -1, + path.graph_revision if path != null else -1, + path.traffic_revision if path != null else -1] + + +## Read-only connection preview. Shares ordinary facility access candidates and +## accessible graph edges, but does not issue live path queries or change routing. +## The displayed path proves connectivity; it is not a cheapest-route promise. +func _build_connection_report(cells: Array[Vector3i], origin: Vector3i) -> Dictionary: + var path: Variant = _service(&"path_service") + if path == null or _runtime == null or cells.is_empty(): + return {&"status": &"UNAVAILABLE", &"reachable": false, &"path": []} + var entrance: Variant = _runtime.call(&"_operations_entrance_cell") + var key: Array = [origin, cells, _current_preview_topology(), entrance] + if key == _connection_cache_key: + return _connection_cache_report.duplicate(true) + var result := {&"status": &"UNAVAILABLE", &"reachable": false, + &"entrance": entrance, &"origin": origin, &"path": [], &"access_points": []} + if entrance is Vector3i: + var future := FacilityInstance.new() + future.origin = origin + future.occupied_cells.assign(cells) + var candidates: Array = _runtime.call(&"_facility_access_candidates", future) + result[&"access_points"] = candidates + var targets: Dictionary = {} + for cell: Vector3i in candidates: targets[cell] = true + var queue: Array[Vector3i] = [entrance] + var parents: Dictionary = {} + parents[entrance] = entrance + var cursor := 0 + var target: Variant = null + while cursor < queue.size(): + var current := queue[cursor] + cursor += 1 + if targets.has(current): + target = current + break + for edge: Dictionary in path.get_neighbors(current): + var next: Vector3i = edge[&"to"] + if parents.has(next) or path.get_edge_report(current, next).is_empty(): + continue + parents[next] = current + queue.append(next) + var route: Array[Vector3i] = [] + if target is Vector3i: + var current: Vector3i = target + while current != entrance: + route.push_front(current) + current = parents[current] + route.push_front(entrance) + result[&"reachable"] = not route.is_empty() + result[&"status"] = &"CONNECTED" if result[&"reachable"] else &"DISCONNECTED" + result[&"path"] = route + _connection_cache_key = key.duplicate(true) + _connection_cache_report = result.duplicate(true) + return result + + +func _draw_build_connection(report: Dictionary) -> void: + if report.is_empty() or not _hovered_cell is Vector3i: return + var status: StringName = report.get(&"status", &"UNAVAILABLE") + var color := Color("8ff0ab") if status == &"CONNECTED" else Color("ffd080") + var target := cell_to_canvas(_hovered_cell) + var route: Array = report.get(&"path", []) + if status == &"CONNECTED": + for i in range(1, route.size()): + var from: Vector3i = route[i-1] + var to: Vector3i = route[i] + if from.z == layer_mode and to.z == layer_mode: + draw_line(cell_to_canvas(from), cell_to_canvas(to), color, 2.0) + if not route.is_empty() and (route.back() as Vector3i).z == layer_mode: + draw_line(cell_to_canvas(route.back()), target, color, 2.0) + elif report.get(&"entrance") is Vector3i: + # Dashed straight guide is a warning, never a traversable-path claim. + draw_dashed_line(cell_to_canvas(report[&"entrance"]), target, color, 2.0, 8.0) + draw_line(target-Vector2(6,6),target+Vector2(6,6),color,2.0) + draw_line(target+Vector2(-6,6),target+Vector2(6,-6),color,2.0) + var message := "入口への経路接続済" if status == &"CONNECTED" else "入口への経路未接続" if status == &"DISCONNECTED" else "入口・経路を確認できません" + draw_string(ThemeDB.fallback_font, target+Vector2(8,-12), message, + HORIZONTAL_ALIGNMENT_LEFT, -1, 13, color) + +func set_demolition_mode(enabled: bool) -> void: + demolition_mode = enabled + if enabled: + clear_build_definition() + set_excavation_mode(false) + queue_redraw() diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json new file mode 100644 index 0000000..7c9e5d7 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json @@ -0,0 +1,473 @@ +{ + "schema_version": 1, + "suite_policies": { + "tests/s4/test_original_relationship_model.gd": { + "source_sha256": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/s4/test_original_relationship_runtime.gd": { + "source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_operations_notification.gd": { + "source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": ["single"], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_departure.gd": { + "source_sha256": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": ["single"], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_anri_recruitment.gd": { + "source_sha256": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_event_log_ui.gd": { + "source_sha256": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_amelia_donations.gd": { + "source_sha256": "1bd622a3d88374f01e3d8885a84a816572ed296928f6ce7e3f8134ab15f60793", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_fine_cleaning_effect.gd": { + "source_sha256": "7c7df0bac1c072ed4423140e8afe431952ec2a47f76df7dbc24cab62283cae06", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_normal_auto_purchase_chain.gd": { + "source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_normal_supply_chain.gd": { + "source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_approved_free_unique_arrears_ui.gd": { + "source_sha256": "ff684e2f227896c1b5df6026f130d45f4289fc6cae40989be51ddc95b2cf135b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_business_management.gd": { + "source_sha256": "2c76697f9a911e9dded63ebbbc2b82ee9e616777c50a637eebd91dc4932f85a5", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_chapel_gameplay.gd": { + "source_sha256": "e1996d6bd6caf46bae7becf1de2514e91a6168a686e83563b00803ac71109735", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_cleaning_gameplay.gd": { + "source_sha256": "38593cd17829611dd34561ec46a0d746492a6cffebc35a659d49fe69de4234ac", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_dashboard_coalescing.gd": { + "source_sha256": "2f4bd8e9d43911067a7e82864454f6779ae92acc48e0b8d4932e5d963e0430ec", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_defense_management.gd": { + "source_sha256": "e7debf5746e1ad5976b1afb1d2471c1c2317bcc406ed2716a2368bd4e06e0235", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_dimension_migration_ui.gd": { + "source_sha256": "0f0cec88ea8bf5fccdcd5ade02fab70b7dc71135132048cd10db17ad9d964c0d", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_elevator_management.gd": { + "source_sha256": "dff2d3f2a1cea84224aee8f70c4ad53d731df173bc7d16d7ec012c0d16c62e11", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_first_operating_day.gd": { + "source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_guest_decision_log.gd": { + "source_sha256": "8532defab87c075ad3196b44af030cdab785abed365e25d5a56d2d42f115cf71", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_guest_observation.gd": { + "source_sha256": "8f5af3d6ef367512b34d0cf35fd8c1d2848d020419dddfa234120206dbe4f6e4", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_layer_skin.gd": { + "source_sha256": "aab6f0561e222f88681169a82b47631320495df4c359bcf79d2fb228c60ad83b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "front-owner", + "back-owner" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_main_audio_feedback.gd": { + "source_sha256": "9bc9e13ef3498941b6e8566c0b7666c42e294b63e18ec0da2cfa1cda3e4617bd", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_main_dashboard.gd": { + "source_sha256": "fa9ee0c4b9aee00a2be6d05a9dab7c0c3e8b0beb2b944257de1dc308bfc3cc20", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_original_build_pointer.gd": { + "source_sha256": "a04a79c5e766659d10c4564d1f11c6554a27cb8af71431295e10bc4967012913", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_original_facility_controls.gd": { + "source_sha256": "294b39808d3454f96555b981c8f822f17e1248afa803f7c0da74c35799ae55a1", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_rank_pointer.gd": { + "source_sha256": "2c51510f80fb57068c6c06aa56566936685e679720b0fd853d8f814566e730f2", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_autosave.gd": { + "source_sha256": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_lifecycle.gd": { + "source_sha256": "ed68b120f422e1b37b8cf0654b6d6605458fe01c7e5d84decc3ba6f4e9366c47", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "transition", + "finish" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_management.gd": { + "source_sha256": "7e37dc47583325a564ef48de31596adeeccfb7151ac5fce952efc6873c8ab704", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "restored-transition", + "finish" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_rescue.gd": { + "source_sha256": "928179383bda38adf9211065b644a37bed3f8128be42b7e7a0fba9a505a40b9e", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_rest.gd": { + "source_sha256": "712f2c30ec89da20835e54f2e83b22a358cc6fc05555147ce635ec2592ef75b5", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_stair_transport_details.gd": { + "source_sha256": "3abd3655eecbefe1b7cd49e1b16c42f8161c86f20380c298ec6367b42d31555f", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_supply_auto_purchase_ui.gd": { + "source_sha256": "ad3788ae4b3f17918be67cf06c4162635da1ecba1ee0a0465d10acc019f3e3ae", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_supply_management.gd": { + "source_sha256": "ee96be8b31eb71e84ab813e427a28bcfb76de4a057bd964584a6a497382ea716", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_ui_audio_feedback.gd": { + "source_sha256": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": true + }, + "tests/ui/test_ui_presentation_audio_contract.gd": { + "source_sha256": "9769b89b61630ddf2ef13a927f329ddaa30e04da77987e7a868d0490244d6a25", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "before-remount", + "final" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_upkeep_closed_ui.gd": { + "source_sha256": "915db12e539d98bfcb2cfc3712693ca7fca3ff459aaacba419faff04ac712868", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_upkeep_inspector.gd": { + "source_sha256": "99895a24bcddc4c2e01d8960a2fd7eba05be237a64700d78879c68c1a0660f34", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_world_tree_rank.gd": { + "source_sha256": "8e108e3ae63d1ee3971c7b236f50a673c407835f094de0d38a736c38261767aa", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_runtime_upkeep.gd": { + "source_sha256": "eae48823d58d78c419d29728e33a5559fbe5131b49e33d04ca4e6fa3a721c29b", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/transport/test_runtime_transport_comparison.gd": { + "source_sha256": "ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/transport/test_transport_comparison.gd": { + "source_sha256": "7e80e9ef80500278c56f0d1ada5c911f43e3c0a9bc370045fdbebd33c2f02fd5", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_mimi_purchase_quote.gd": { + "source_sha256": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false, + "standalone_ownership": { + "Main_instantiated": false, + "configure_audio_called": false, + "UIAudio_owner_registered": false, + "actual_parent": "SceneTree.root", + "owned_supply_panel": "configure(runtime) then add_child; queue_free + await process_frame + is_instance_valid rejection", + "autoload_Runtime_Economy_lifetime": "native strict diagnostics and whole profile capture remain mandatory; leak outcome unmeasured", + "normal_human_viewport_or_OS": false + }, + "source_only_ownership_review": true + }, + "tests/transport/test_runtime_transport.gd": { + "source_sha256": "ec6f8fa8e8f2a76a6f6214b4ee84861aba1bb13845a342c5126905b01f428a15", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_actor_selection.gd": { + "source_sha256": "f37a0f4805adce4b538fccde1404c1a417f20639fddf36947f076d88409ba0a3", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "before-remount", + "final" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_pending_service_pricing_report.gd": { + "source_sha256": "50095c41fe76bac7bd9346ce97651d40610fa69b48c3b24f7acc5b371578e753", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_p08_normal_placement_ops.gd": { + "source_sha256": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + } + }, + "outside_routes": { + "tests/supply/test_normal_auto_purchase_chain.gd": { + "env": "THE_TREE_SUPPLY_EVIDENCE_DIR", + "filename": "normal-play-report.json", + "stdout_prefix": "NORMAL_AUTO_PURCHASE_REPORT ", + "stdout_projection": "subset", + "report_owner_source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256" + }, + "tests/supply/test_normal_supply_chain.gd": { + "env": "THE_TREE_SUPPLY_EVIDENCE_DIR", + "filename": "normal-play-report.json", + "stdout_prefix": "NORMAL_SUPPLY_REPORT ", + "stdout_projection": "subset", + "report_owner_source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d" + }, + "tests/ui/test_first_operating_day.gd": { + "env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR", + "filename": "operating-report.json", + "stdout_prefix": "FIRST_OPERATING_DAY_REPORT ", + "stdout_projection": "full", + "report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226" + }, + "tests/ui/test_selection_autosave.gd": { + "env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR", + "filename": "operating-report.json", + "stdout_prefix": "FIRST_OPERATING_DAY_REPORT ", + "stdout_projection": "full", + "inherited_source": "tests/ui/test_first_operating_day.gd", + "report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226" + } + }, + "fixed_generated_report_routes": { + "tests/mvp/test_runtime_upkeep.gd": [ + "artifacts/upkeep-2026-09-29/preclose-snapshot.txt", + "artifacts/upkeep-2026-09-29/closed-snapshot.txt" + ], + "tests/transport/test_runtime_transport_comparison.gd": [ + "artifacts/transport/runtime-comparison.json", + "artifacts/transport/runtime-comparison-1-trace.json", + "artifacts/transport/runtime-comparison-2-trace.json" + ], + "tests/transport/test_transport_comparison.gd": [ + "artifacts/transport/path-comparison.json", + "artifacts/transport/comparison-demand.json" + ] + }, + "scope": "observed source/proof/IO contracts; no new gameplay assertions" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json new file mode 100644 index 0000000..2dae67b --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json @@ -0,0 +1,22 @@ +{ + "scope": "Reuse actual local9test command only; no whole-unit/full-roster acceptance. JSON append-position correction does not alter these checker/test/shared-roster code inputs.", + "actual_run": "1cde6bdddd3a4d88818d1c38103aedb8", + "actual_role": "implementer", + "actual_service": "codex", + "actual_model": "gpt-6.1-sol", + "raw_provider_stdout_sha256": "97bc112da31cb41f938373fc623481d4e10555f2d328ef2dad7e9a326cbc46c1", + "actual_command_event": { + "id": "item_16", + "type": "command_execution", + "command": "/bin/zsh -lc 'python3 docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py'", + "aggregated_output": "test_1_invented_replacement (__main__.CheckerTests) ... ok\ntest_2_duplicate_padding (__main__.CheckerTests) ... ok\ntest_3_unknown_extra (__main__.CheckerTests) ... ok\ntest_4_required_results (__main__.CheckerTests) ... ok\ntest_5_source_hash (__main__.CheckerTests) ... ok\ntest_6_missing_or_malformed_rows (__main__.CheckerTests) ... ok\ntest_7_env_mismatch_and_positive_head (__main__.CheckerTests) ... ERROR: CANDIDATE_COMMIT mismatch with real git HEAD 90f4d81f674f669c5ea6e8e537648b693ab5af5e\nERROR: TARGET_HEAD mismatch with real git HEAD 90f4d81f674f669c5ea6e8e537648b693ab5af5e\nERROR: TARGET_HEAD mismatch with real git HEAD 90f4d81f674f669c5ea6e8e537648b693ab5af5e\nok\ntest_8_git_unavailable_env_only (__main__.CheckerTests) ... ok\ntest_positive_roster (__main__.CheckerTests) ... ok\n\n----------------------------------------------------------------------\nRan 9 tests in 0.373s\n\nOK\n", + "exit_code": 0, + "status": "completed" + }, + "dependency_file_sha256": { + "tools/check_p24_operations_evidence.py": "3dc04b66ab07e2193595aff4ad63baf1ada732cee02c8b4d67298b933bd21487", + "tools/run_all_tests.py": "1d56dc16ab34f5a8f77f3ed7e833fb6d9fe73aa37cf297a0597e41bfa9c9e4b4", + "docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py": "e528dc2939d3561ac1d890d46caf29632316f4ed4aeb8f5efdaed59d130b9234" + }, + "no_dependency_file_change_events_after_actual_test_pass": true +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_REAUDITED_STATIC_PHASE_SOURCE_READY.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_REAUDITED_STATIC_PHASE_SOURCE_READY.json new file mode 100644 index 0000000..bd18cfe --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_REAUDITED_STATIC_PHASE_SOURCE_READY.json @@ -0,0 +1,100 @@ +{ + "actual_code_run": "dad03b83bd41486d8f5e5297e636a055", + "actual_model": "gpt-6.1-sol", + "scope": "Root re-audited actual code-phase local command exits and SHA/JSON-move guards; original P24 unit all8 still unaccepted.", + "raw_provider_stdout_sha256": "9d04e2d1faf03c8b971ddae613a39c144a80fc5de040b449d9d5ad5f011425c9", + "actual_local_command_events": [ + { + "id": "item_9", + "type": "command_execution", + "command": "/bin/zsh -lc 'PYTHONPYCACHEPREFIX=\"$TMPDIR/p24-phase-pyc\" python3 -m py_compile tools/check_p24_operations_evidence.py tools/run_all_tests.py docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py'", + "aggregated_output": "", + "exit_code": 0, + "status": "completed" + }, + { + "id": "item_10", + "type": "command_execution", + "command": "/bin/zsh -lc 'python3 docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py --write-evidence'", + "aggregated_output": "{\n \"status\": \"SOURCE-READY\",\n \"runtime_status\": \"NOTRUN\",\n \"files\": {\n \"scenes/main/main.gd\": {\n \"required_count\": 279,\n \"reconciled_symbol_count\": 279,\n \"missing\": 0,\n \"duplicates\": 0,\n \"unexpected\": 0,\n \"body_issues\": 0\n },\n \"scenes/main/world_preview.gd\": {\n \"required_count\": 179,\n \"reconciled_symbol_count\": 180,\n \"missing\": 0,\n \"duplicates\": 0,\n \"unexpected\": 0,\n \"body_issues\": 0\n },\n \"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json\": {\n \"missing\": 0,\n \"duplicates\": 0,\n \"equals_main_plus_p24_only_additions_in_order\": true\n },\n \"tools/regression/suite-policy.json\": {\n \"missing\": 0,\n \"duplicates\": 0,\n \"equals_main_plus_p24_only_additions_in_order\": true\n }\n },\n \"schema_checks_ok\": true,\n \"root_composition_dependencies\": 4,\n \"failures\": []\n}\n", + "exit_code": 0, + "status": "completed" + }, + { + "id": "item_11", + "type": "command_execution", + "command": "/bin/zsh -lc 'git diff --check'", + "aggregated_output": "", + "exit_code": 0, + "status": "completed" + }, + { + "id": "item_13", + "type": "command_execution", + "command": "/bin/zsh -lc \"python3 -c 'import pathlib,json,hashlib; guard=json.loads(\\\"{\\\\\\\"AGENTS.md\\\\\\\":\\\\\\\"8d8dc40211390275f858f322743309a0663c4f2d859780a8b8a08cea1450e8b3\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/NEXT_CLOSURE.md\\\\\\\":\\\\\\\"9301fa028052174ae9fa5acf006b50fd6c3f21fcbc6e04fc1a398efc7d9be1cb\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md\\\\\\\":\\\\\\\"940996894b2ea28cf46b992bd5c2719d4ef952452d0585396eae6664a63e6f53\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md\\\\\\\":\\\\\\\"003724ef4a597298016a59f6198dec2ca3c94115c88afb7dd6cbd7478e0fe1ae\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md\\\\\\\":\\\\\\\"1e16de00c419467fdfa87b1bf28c004f579576a97cbb04fa5ac06070697532d5\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md\\\\\\\":\\\\\\\"1eeb4ac0d14347700a9d6e599ce61e36b3324f5fcc4385266aaf16a1c8ab80bb\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json\\\\\\\":\\\\\\\"c68f35352233d9adb91b1db203cc0e9d47db3f91ec103b9d37a9eeec578f9ba2\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json\\\\\\\":\\\\\\\"1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt\\\\\\\":\\\\\\\"f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt\\\\\\\":\\\\\\\"8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json\\\\\\\":\\\\\\\"fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json\\\\\\\":\\\\\\\"ded162f334d6d7a7422ee76d03e26d1544658a06242bc2e2f3f5c6913890d524\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md\\\\\\\":\\\\\\\"96f0620efdee9b073fcad279de243111a4d5266335d29a93acb19a5dc1b453b3\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/collector_protocol.json\\\\\\\":\\\\\\\"bfe94a37304c54ac615bcbd6f938e79933730298fb1c00c6e7ce1922fda3aec2\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/owner-gui-direction-r04.txt\\\\\\\":\\\\\\\"2936e33085ce4eae5f556401a55c781b8ac18208ef48f76cacda5faa97e26585\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py\\\\\\\":\\\\\\\"909c8c283aca5ef6ad14485bea405e248280a1a63cb6f2fd3458b32a8632cee3\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py\\\\\\\":\\\\\\\"e528dc2939d3561ac1d890d46caf29632316f4ed4aeb8f5efdaed59d130b9234\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json\\\\\\\":\\\\\\\"5a7c01fc2d6d37046413fc1f81e89005da11616bd0b97c94e6ea1fe0b7668298\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json\\\\\\\":\\\\\\\"ada4a9d2cf612e7cb0b0528f4b4c09b61fb45d47411ed3e78726138d4d233c48\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json\\\\\\\":\\\\\\\"08ae7860410859e504bf74a9484d1003f3f8d5f0e6e057a6268a0742af590301\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json\\\\\\\":\\\\\\\"6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt\\\\\\\":\\\\\\\"92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt\\\\\\\":\\\\\\\"4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2\\\\\\\",\\\\\\\"docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json\\\\\\\":\\\\\\\"7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f\\\\\\\",\\\\\\\"scenes/main/main.gd\\\\\\\":\\\\\\\"f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24\\\\\\\",\\\\\\\"scenes/main/world_preview.gd\\\\\\\":\\\\\\\"8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa\\\\\\\",\\\\\\\"tools/check_p24_operations_evidence.py\\\\\\\":\\\\\\\"3dc04b66ab07e2193595aff4ad63baf1ada732cee02c8b4d67298b933bd21487\\\\\\\",\\\\\\\"tools/run_all_tests.py\\\\\\\":\\\\\\\"1d56dc16ab34f5a8f77f3ed7e833fb6d9fe73aa37cf297a0597e41bfa9c9e4b4\\\\\\\"}\\\"); bad=[p for p,h in guard.items() if hashlib.sha256(pathlib.Path(p).read_bytes()).hexdigest()\"'!=h]; assert not bad,bad; p=pathlib.Path('\"'\\\\''docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/REPAIR4_CODE_PHASE_STATUS.md'\\\\''); old=p.read_text(); p.write_text(\\\"# Repair4 JSON order correction — static phase ready\\\\n\\\\nTask P24-02; commit \"'`9641ed52def2ff8e5b0222449a4a83a1191e1d37`. Same repair4; no new task, permission change or commit. Root prelaunch proof trusted; no ps, external search, engine, OS, full or final checker executed. Root performs normal submission separately. This is static readiness only, never acceptance.'\"\\\\n\\\\nOnly the P24 operations marker row and suite policy key/object were moved from index 2 to the end of their respective containers. Raw entry bodies retained verbatim; existing main entries and their order retained. Both JSON files parse. Before/after structural comparison passed: all values deep-equal, marker rows remain 180, suite policy key set unchanged (48). Only the specified entry position differs.\\\\n\\\\nJSON SHA256 before → after:\\\\n\\\\n- completion_marker_contracts.json: \"'`1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1` → `1063e578eefe6da1b38c24f4c004669bcac67a21884636fb1b9c7e96e4ecf388`'\"\\\\n- suite-policy.json: \"'`fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9` → `adc2fbac2463bfdb047bede0182c66083996b75d2bd542a3b8640672d7ca2400`'\"\\\\n\\\\n\"'`PYTHONPYCACHEPREFIX='\"\\\\\\\"\"'$TMPDIR/p24-phase-pyc'\"\\\\\\\" python3 -m py_compile\"'` on tools/check_p24_operations_evidence.py, tools/run_all_tests.py, repair4/test_checker_roster_head.py and repair4/check_repair4_preservation.py: exit 0. Cache prefix applied to that command only; no system settings or permissions changed.'\"\\\\n\\\\n\"'`check_repair4_preservation.py --write-evidence`: exit 0, SOURCE-READY, runtime NOTRUN, schema checks true, failures empty. Both JSON equals_main_plus_p24_only_additions_in_order=true. main.gd remains 279/279; world_preview.gd remains 179 required/180 reconciled with the one allowed bubble adapter; zero missing, duplicate, unexpected or body issues. `git diff --check`: exit 0.'\"\\\\n\\\\n9 tests reused without rerun: ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json records actual run 1cde6bdddd3a4d88818d1c38103aedb8, completed exit 0, 9 tests OK. All three dependency SHA256 values match the unchanged files exactly. Earlier 179 result remains historical and is not relabelled.\\\\n\\\\nProtected SHA256 guard: recorded before any edit, then recomputed after authorized writes; every file below is byte-identical. This includes all task Root instruction/reference/proof files, both GDScripts, all checker/test Python and shared roster. The identical before/after SHA256 is listed for each:\\\\n\\\\n- \"'`AGENTS.md`: `8d8dc40211390275f858f322743309a0663c4f2d859780a8b8a08cea1450e8b3`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/NEXT_CLOSURE.md`: `9301fa028052174ae9fa5acf006b50fd6c3f21fcbc6e04fc1a398efc7d9be1cb`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md`: `940996894b2ea28cf46b992bd5c2719d4ef952452d0585396eae6664a63e6f53`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md`: `003724ef4a597298016a59f6198dec2ca3c94115c88afb7dd6cbd7478e0fe1ae`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md`: `1e16de00c419467fdfa87b1bf28c004f579576a97cbb04fa5ac06070697532d5`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md`: `1eeb4ac0d14347700a9d6e599ce61e36b3324f5fcc4385266aaf16a1c8ab80bb`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json`: `c68f35352233d9adb91b1db203cc0e9d47db3f91ec103b9d37a9eeec578f9ba2`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`: `1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt`: `f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt`: `8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json`: `fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json`: `ded162f334d6d7a7422ee76d03e26d1544658a06242bc2e2f3f5c6913890d524`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md`: `96f0620efdee9b073fcad279de243111a4d5266335d29a93acb19a5dc1b453b3`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/collector_protocol.json`: `bfe94a37304c54ac615bcbd6f938e79933730298fb1c00c6e7ce1922fda3aec2`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/owner-gui-direction-r04.txt`: `2936e33085ce4eae5f556401a55c781b8ac18208ef48f76cacda5faa97e26585`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py`: `909c8c283aca5ef6ad14485bea405e248280a1a63cb6f2fd3458b32a8632cee3`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py`: `e528dc2939d3561ac1d890d46caf29632316f4ed4aeb8f5efdaed59d130b9234`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json`: `5a7c01fc2d6d37046413fc1f81e89005da11616bd0b97c94e6ea1fe0b7668298`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json`: `ada4a9d2cf612e7cb0b0528f4b4c09b61fb45d47411ed3e78726138d4d233c48`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json`: `08ae7860410859e504bf74a9484d1003f3f8d5f0e6e057a6268a0742af590301`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`: `6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt`: `92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt`: `4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2`'\"\\\\n- \"'`docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json`: `7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f`'\"\\\\n- \"'`scenes/main/main.gd`: `f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24`'\"\\\\n- \"'`scenes/main/world_preview.gd`: `8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa`'\"\\\\n- \"'`tools/check_p24_operations_evidence.py`: `3dc04b66ab07e2193595aff4ad63baf1ada732cee02c8b4d67298b933bd21487`'\"\\\\n- \"'`tools/run_all_tests.py`: `1d56dc16ab34f5a8f77f3ed7e833fb6d9fe73aa37cf297a0597e41bfa9c9e4b4`'\"\\\\n\\\\nPending: whole8, Root S/M/T/Q composition, native, current full, whole-unit independent review and Windows. No acceptance index created or edited. Earlier failed preparation and quality-check output is retained below as historical evidence; the authorized corrective checks above supersede only the static readiness result.\\\\n\\\\n---\\\\n\\\\n## Historical prior phase status (retained verbatim)\\\\n\\\\n\\\"+old); print('\\\\''Protected before/after SHA guard PASS: '\\\\''+str(len(guard))+'\\\\'' files; status updated'\\\\'')'\"", + "aggregated_output": "Protected before/after SHA guard PASS: 28 files; status updated\n", + "exit_code": 0, + "status": "completed" + }, + { + "id": "item_15", + "type": "command_execution", + "command": "/bin/zsh -lc 'git diff --check'", + "aggregated_output": "", + "exit_code": 0, + "status": "completed" + } + ], + "source_ready_result_sha256": "94388558fcb52b98abc27fb8f0ea88dfb395167f69db4e452b1e40ccbf6a6771", + "source_ready_pin_checks": [ + { + "path": "scenes/main/main.gd", + "sha256": "92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a", + "blob": "fff37f082d750bb91c71f2aa54d168c9e2cb8fe7", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "scenes/main/world_preview.gd", + "sha256": "4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2", + "blob": "fa32fe0344d20565b6d542da1256a94e8dee1845", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "sha256": "6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c", + "blob": "2eecb803f5eb6774ff38cd353de1f02847aeb0f9", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "tools/regression/suite-policy.json", + "sha256": "7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f", + "blob": "ad194ae425fb8123fa191ed3635f404c098f692d", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + } + ], + "phase_guards": { + "result": { + "task_id": "P24-02", + "commit": "9641ed52def2ff8e5b0222449a4a83a1191e1d37", + "status": "submitted", + "summary": "Static phase ready: moved only the two P24 JSON entries to append positions. Structural equality, compilation, preservation checker and diff checks passed. Reused 9 passing tests after all three input hashes matched. All 28 protected files remain byte-identical. Evidence/status updated; no commit. Acceptance, whole8, Root S/M/T/Q, native, current full, whole-unit review and Windows remain pending.", + "acceptance_ids": [], + "findings": [] + }, + "artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-02-operations/runs/dad03b83bd41486d8f5e5297e636a055", + "scope_violations": [], + "protected_files_unchanged": true, + "marker_only_expected_new_entry_move": true, + "policy_only_expected_new_key_move": true, + "repairs_unchanged": 4, + "normal_automatic_submit": false, + "ended": 1792213459.353031 + }, + "local9_tests_reused_from_actual_pass": "ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json", + "new_native_or_full_runs": 0, + "original_completed_units": 14 +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md new file mode 100644 index 0000000..6cb7521 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md @@ -0,0 +1,31 @@ +# Verified task-local zsh temporary transport + +No policy/permission/source change. Previous JSON phase2cf378d ended before editing because a large zsh here-document tried /tmp, not existing task runtime. Root reproduced failure then success with the exact same saved sandbox, using TMPPREFIX pointed to existing allocated task tmp. + +For EVERY shell command using a here-document, start it with: + + TMPPREFIX=/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-02-operations/.runtime/P24-02/tmp/p24-zsh; export TMPPREFIX; + +Then run the original command. Or use python -c instead of a here-document. Each shell invocation is independent, so prefix every long-heredoc command. For py_compile also prefix PYTHONPYCACHEPREFIX="$TMPDIR/p24-phase-pyc" as authorized by actual Opus. These paths are already writable under unchanged task sandbox. Do not search user folders, request permissions or stop the implementation solely because a transport preparation failed. Root pre-launch duties are already complete. Actual quality-check failure still must be recorded, never waived. Preserve the earlier failure and continue only this same recorded JSON-order correction. + +{ + "same_saved_sandbox": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p24-02-operations/runs/2cf378d728f64bbca470f5603d3299dc/sandbox.sb", + "sandbox_sha256": "8955b7a2a3601db1dd5c0cc3422dbf490b0463123eafab87d45d78a30d51cb07", + "no_policy_or_permission_change": true, + "runtime_tmp": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-02-operations/.runtime/P24-02/tmp", + "source_changes": false, + "probe_results": [ + { + "name": "original", + "exit_code": 1, + "stdout": "", + "stderr": "zsh:1: can't create temp file for here document: operation not permitted\n" + }, + { + "name": "task-local-zsh-temp", + "exit_code": 0, + "stdout": "P24_TEMP_TRANSPORT_OK\n", + "stderr": "" + } + ] +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/REPAIR4_CODE_PHASE_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/REPAIR4_CODE_PHASE_STATUS.md new file mode 100644 index 0000000..b838719 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/REPAIR4_CODE_PHASE_STATUS.md @@ -0,0 +1,189 @@ +# Repair4 JSON order correction — static phase ready + +Task P24-02; commit `9641ed52def2ff8e5b0222449a4a83a1191e1d37`. Same repair4; no new task, permission change or commit. Root prelaunch proof trusted; no ps, external search, engine, OS, full or final checker executed. Root performs normal submission separately. This is static readiness only, never acceptance. + +Only the P24 operations marker row and suite policy key/object were moved from index 2 to the end of their respective containers. Raw entry bodies retained verbatim; existing main entries and their order retained. Both JSON files parse. Before/after structural comparison passed: all values deep-equal, marker rows remain 180, suite policy key set unchanged (48). Only the specified entry position differs. + +JSON SHA256 before → after: + +- completion_marker_contracts.json: `1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1` → `1063e578eefe6da1b38c24f4c004669bcac67a21884636fb1b9c7e96e4ecf388` +- suite-policy.json: `fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9` → `adc2fbac2463bfdb047bede0182c66083996b75d2bd542a3b8640672d7ca2400` + +`PYTHONPYCACHEPREFIX="$TMPDIR/p24-phase-pyc" python3 -m py_compile` on tools/check_p24_operations_evidence.py, tools/run_all_tests.py, repair4/test_checker_roster_head.py and repair4/check_repair4_preservation.py: exit 0. Cache prefix applied to that command only; no system settings or permissions changed. + +`check_repair4_preservation.py --write-evidence`: exit 0, SOURCE-READY, runtime NOTRUN, schema checks true, failures empty. Both JSON equals_main_plus_p24_only_additions_in_order=true. main.gd remains 279/279; world_preview.gd remains 179 required/180 reconciled with the one allowed bubble adapter; zero missing, duplicate, unexpected or body issues. `git diff --check`: exit 0. + +9 tests reused without rerun: ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json records actual run 1cde6bdddd3a4d88818d1c38103aedb8, completed exit 0, 9 tests OK. All three dependency SHA256 values match the unchanged files exactly. Earlier 179 result remains historical and is not relabelled. + +Protected SHA256 guard: recorded before any edit, then recomputed after authorized writes; every file below is byte-identical. This includes all task Root instruction/reference/proof files, both GDScripts, all checker/test Python and shared roster. The identical before/after SHA256 is listed for each: + +- `AGENTS.md`: `8d8dc40211390275f858f322743309a0663c4f2d859780a8b8a08cea1450e8b3` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/NEXT_CLOSURE.md`: `9301fa028052174ae9fa5acf006b50fd6c3f21fcbc6e04fc1a398efc7d9be1cb` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_CODE_ONLY_HANDOFF.md`: `940996894b2ea28cf46b992bd5c2719d4ef952452d0585396eae6664a63e6f53` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_GUI_INSTRUCTION.md`: `003724ef4a597298016a59f6198dec2ca3c94115c88afb7dd6cbd7478e0fe1ae` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_JSON_ORDER_CORRECTION.md`: `1e16de00c419467fdfa87b1bf28c004f579576a97cbb04fa5ac06070697532d5` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/OWNER_REPAIR4_SUPPLEMENT_CHECKER.md`: `1eeb4ac0d14347700a9d6e599ce61e36b3324f5fcc4385266aaf16a1c8ab80bb` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/VERIFIED_PRELAUNCH.json`: `c68f35352233d9adb91b1db203cc0e9d47db3f91ec103b9d37a9eeec578f9ba2` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`: `1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/main.gd.source.txt`: `f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/scenes/main/world_preview.gd.source.txt`: `8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_CODE_PHASE_PRELAUNCH_PROOF/original-GUI-copy/tools/regression/suite-policy.json`: `fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_LOCAL_VALIDATION_R02_REUSE_RECEIPT.json`: `ded162f334d6d7a7422ee76d03e26d1544658a06242bc2e2f3f5c6913890d524` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/ROOT_ZSH_TEMP_TRANSPORT_FIX.md`: `96f0620efdee9b073fcad279de243111a4d5266335d29a93acb19a5dc1b453b3` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/collector_protocol.json`: `bfe94a37304c54ac615bcbd6f938e79933730298fb1c00c6e7ce1922fda3aec2` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/owner-gui-direction-r04.txt`: `2936e33085ce4eae5f556401a55c781b8ac18208ef48f76cacda5faa97e26585` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py`: `909c8c283aca5ef6ad14485bea405e248280a1a63cb6f2fd3458b32a8632cee3` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py`: `e528dc2939d3561ac1d890d46caf29632316f4ed4aeb8f5efdaed59d130b9234` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json`: `5a7c01fc2d6d37046413fc1f81e89005da11616bd0b97c94e6ea1fe0b7668298` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json`: `ada4a9d2cf612e7cb0b0528f4b4c09b61fb45d47411ed3e78726138d4d233c48` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json`: `08ae7860410859e504bf74a9484d1003f3f8d5f0e6e057a6268a0742af590301` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`: `6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt`: `92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt`: `4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2` +- `docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json`: `7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f` +- `scenes/main/main.gd`: `f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24` +- `scenes/main/world_preview.gd`: `8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa` +- `tools/check_p24_operations_evidence.py`: `3dc04b66ab07e2193595aff4ad63baf1ada732cee02c8b4d67298b933bd21487` +- `tools/run_all_tests.py`: `1d56dc16ab34f5a8f77f3ed7e833fb6d9fe73aa37cf297a0597e41bfa9c9e4b4` + +Pending: whole8, Root S/M/T/Q composition, native, current full, whole-unit independent review and Windows. No acceptance index created or edited. Earlier failed preparation and quality-check output is retained below as historical evidence; the authorized corrective checks above supersede only the static readiness result. + +--- + +## Historical prior phase status (retained verbatim) + +# Repair4 code-only phase — blocked, no submit + +Task P24-02, actual HEAD `9641ed52def2ff8e5b0222449a4a83a1191e1d37`. +The trusted Root bridge's VERIFIED_PRELAUNCH.json was read first. Its lease, +original-GUI copies, no-competing-job check and quota record are the launch +authority. No bridge duties were repeated. No acceptance is claimed. + +## Code changes + +The evidence checker delegates its required roster to the actual +`tools/run_all_tests.py:roster(root)` through importlib, without executing main. +It compares exact suite sets and rejects malformed/missing/unknown/duplicate +rows, missing paths, nonzero or non-integer failures (including bool), missing +or non-True passed, and missing/malformed/mismatched source hashes. Optional +counts cannot replace rows and must agree exactly when present. +HEAD comes from git in the repository root; both environment variables are +cross-checks only. Git failure or either mismatch returns an empty value. +The other condition, OS/PID, artifact, profile and pending-template guards +were left unchanged. Isolated stdlib tests cover S3's eight negative groups +and positive roster/HEAD controls; all temporary Git writes are confined to +the disposable test repository. + +The preservation script now emits the requested report on the authorized +no-argument invocation, including failures. Its METRIC_IDS regex previously +stopped at the type annotation's closing bracket in `Array[StringName]`; +it now reads the assigned array. The real pinned source contains affection. +No preservation rule or protected file was changed to make a check pass. + +## Actual local command results + +1. Exact authorized `python3 -m py_compile` for the four specified Python + files: **exit 1**. Python 3.9 attempted to create its configured cache + directory outside the workspace and raised: + `PermissionError: [Errno 1] Operation not permitted: '/Users/korizatou114/Library/Caches/com.apple.python/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-02-operations'`. + This was not rerun with an environment override or relabelled as passed. +2. Exact authorized importlib import command: **exit 0**. +3. Exact authorized `test_checker_roster_head.py`: **exit 0**, 9 tests OK. + Expected mismatch diagnostics appeared for CANDIDATE_COMMIT/TARGET_HEAD. +4. Exact authorized `check_repair4_preservation.py`: initially **exit 1** + with two JSON preservation failures and the regex false negative. + After fixing the regex, rerun **exit 1**, SOURCE-NOT-READY, runtime NOTRUN, + schema checks true. Remaining failure output: + + - `docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json: reconciled JSON != main snapshot + P24-only additions (value/order)` + - `tools/regression/suite-policy.json: reconciled JSON != main snapshot + P24-only additions (value/order)` + + Both GDScript checks have zero missing, duplicate, unexpected or lost-body + items. JSON entry presence/duplicate checks also pass. The protected JSON + files insert the P24 operations row/policy at position 2; the strict + three-way expectation appends P24-only additions after main's entries. + The contracts have 180 rows, first difference at index 2. Minimal concrete + Root correction if retaining this strict order contract: relocate only + `tests/ui/test_operations_notification.gd` to the end of `rows` and to the + end of `suite_policies`, preserving its values and every main entry/order. + These protected edits require a subsequent authorized Root phase. They + were not performed here; the strict check and failure report are preserved. +5. Exact authorized `shasum -a 256` on the four protected files: **exit 0**; + all four match the pre-launch proof: + + | File | SHA256 | + | --- | --- | + | scenes/main/main.gd | f71f8c15493d6b51002ee4afb41cc349efe77e860944c1f79a5b24f928ecbd24 | + | scenes/main/world_preview.gd | 8c2daa9e95d56e6e40bb2071b206693069cb7e4731b4d660d59d41c34cb43dfa | + | tools/regression/suite-policy.json | fa395a4d2a98bec607dfa1a030122efe62e9412fccf8dfba2e2db5579561a7b9 | + | docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json | 1653c4beac791ec5aa0a6691275ecf5c798ffd689482747c739d7eb2ea5f87d1 | + +Git status already contained four protected GUI modifications, Root +instructions/reference/proof files and the unfinished repair4 script before +this phase. Those were preserved. This phase's writes are confined to the +five authorized paths: checker, preservation script, its result, stdlib test, +and this status. The dirty preexisting worktree is not claimed to have only +these five paths relative to HEAD. No commit, branch, push or submission was +performed. Failed steps 1 and 4 prohibit normal submit; task stays repair_ready. + +## All eight conditions remain unmet + +| Frozen condition | Status | Remaining dependencies | +| --- | --- | --- | +| placement_and_invalid_drop | NOTRUN | Genuine composition; Root Godot/local tests and actual valid/invalid placement, model/ledger/selection preservation and click evidence. | +| modal_and_pause_resume | NOTRUN | Composed runtime; actual modal focus/input isolation, pending command/save identity, pause freeze and exactly-once resume evidence. | +| unit_bubbles | NOTRUN | Static schema now passes, but composed runtime must prove actual state/render binding, switching and removal. | +| event_log | NOTRUN | Actual composed production event sequence, bounded log, Japanese values/time/reasons and pause/load deduplication evidence. | +| important_warning_cutin | NOTRUN | Actual production invasion/duel/defense triggers and upstream/catalog/assets applicability; dismiss/stop/resume runtime evidence. Injected fixtures cannot establish production triggers. | +| click_alternatives | NOTRUN | Composed navigation/assignment runtime evidence; accepted scales/bounds applicability. P23 held-gesture manual checks remain deferred, not waived. | +| japanese_input_and_scale | NOTRUN | Actual OS Japanese composition/confirmation/cancel, persisted save label and invalid feedback, representative scales/focus/clipping; macOS development evidence and separate same-candidate Windows acceptance. | +| final_validation_review | NOTRUN | All prior conditions; Root composition, static/import and affected Godot tests, exact-final full roster, real-index verify-only, independent Claude runtime review, current-main integration and final validation. | + +SOURCE-READY describes only the narrow checker code/isolated test work, not +these eight condition groups or the failed preservation gate. + +## Unresolved Root S/M/T/Q prerequisite (S5) + +The old-base submitted S differs from combined current-main T/Q outside the +four reconciled files. A real externally bound submitted SHA (S), main context +SHA (M), tested tree (T), tested commit (Q), complete blob bindings and clean +genuine composed checkout are still required before heavy final evidence. +The current P24 checker has no such Git-provenance contract. HEAD checking +does not establish composition. No environment or receipt relabelling is a +substitute. + +Root must reuse/adapt the accepted P23 composition functions from current-main +`tools/check_p23_life_observation_evidence.py`, absent entirely on this worker +base. Explicit missing helper/API dependencies are `strict_json`, +`marker_join`, `structural_equal`, `verify_marker_join`, `git_bytes`, +`blob_oid`, `git_blob_stream`, `verify_composition`, `tested_checkout`, +`root_context`, `input_map`, and `input_provenance`, plus the verified-view +registry `_VERIFIED_VIEWS` and externally bound role/blob context. P23's +marker join is P23-specific and must receive a reviewed P24 adaptation, not +be imported unchanged as proof. Root must use current-main +`tools/run_all_tests.py:source_refs` and `roster` on that genuine composed +checkout for the complete source/settings/QA map and required suite set. +The worker's shared roster delegation is implemented; composition is NOTRUN. +No missing helper was stubbed or imported into this old-base worker. + +The static report also names these concrete composition resources: +`scenes/main/world_preview.gd`, `scripts/ui/event_log_dialog.gd`, +`scripts/ui/manual_save_slots_dialog.gd`, and `scripts/ui/room_presentation.gd` +(changed-on-main). Root must resolve their complete current-main context; +this phase did not copy or edit them. + +## Historical evidence and GUI stop (S6) + +The GUI phase ended on the actual Antigravity individual quota, Error ID +`b866fa14-cff8-4fc1-ac4f-fba529d47498-136`; queued followup failed at the same +quota (ending 143). No replacement GUI worker or engine was launched. + +The prior 179 passes, legacy 60 evidence, event-log evidence and every earlier +pass are stale/inapplicable as acceptance or full regression for this changed +P24 source. P23 full179 remains valid only for its original accepted +d2581af/M5041b88/treea700519 source and closed P23-03. Original evidence and +closure bytes remain unchanged. No old result was transferred to this source. +No Godot, native/OS collector, full roster, run_all_tests main, full checker +against an index, acceptance fixture/index, or independent review was run. +All mandatory runtime, OS, review, integration and Windows gates remain +NOTRUN/pending as listed above. Nothing here counts as acceptance. diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/SOURCE_READY_PRESERVATION_RESULT.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/SOURCE_READY_PRESERVATION_RESULT.json new file mode 100644 index 0000000..82ed81b --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/SOURCE_READY_PRESERVATION_RESULT.json @@ -0,0 +1,1621 @@ +{ + "pins": [ + { + "path": "scenes/main/main.gd", + "sha256": "92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a", + "blob": "fff37f082d750bb91c71f2aa54d168c9e2cb8fe7", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "scenes/main/world_preview.gd", + "sha256": "4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2", + "blob": "fa32fe0344d20565b6d542da1256a94e8dee1845", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "sha256": "6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c", + "blob": "2eecb803f5eb6774ff38cd353de1f02847aeb0f9", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + }, + { + "path": "tools/regression/suite-policy.json", + "sha256": "7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f", + "blob": "ad194ae425fb8123fa191ed3635f404c098f692d", + "pin_ok": true, + "snapshot_equals_main_commit_blob": true + } + ], + "files": { + "scenes/main/main.gd": { + "kind": "gdscript", + "main_symbols": [ + "main.gd::const::BusinessManagementPanelScript", + "main.gd::const::DefaultVisualTheme", + "main.gd::const::DefenseManagementPanelScript", + "main.gd::const::ELEVATOR_PHASE_NAMES", + "main.gd::const::EVENT_CHOICE_NAMES", + "main.gd::const::EXCAVATION_REASON_NAMES", + "main.gd::const::EventLogDialogScript", + "main.gd::const::FACILITY_REASON_NAMES", + "main.gd::const::FACILITY_STATE_NAMES", + "main.gd::const::LayerSkin", + "main.gd::const::MANUAL_SAVE_PATH", + "main.gd::const::MVP_FACILITY_IDS", + "main.gd::const::ManualSaveSlotsDialogScript", + "main.gd::const::OpeningGuideScript", + "main.gd::const::OperatingLedgerPanelScript", + "main.gd::const::PREVIEW_REASON_NAMES", + "main.gd::const::PairedBGMScript", + "main.gd::const::RESOURCE_TITLES", + "main.gd::const::RankDialogScript", + "main.gd::const::STAFF_ROLE_NAMES", + "main.gd::const::STAFF_SHIFT_NAMES", + "main.gd::const::STAFF_SKILL_NAMES", + "main.gd::const::StaffManagementPanelScript", + "main.gd::const::SupplyManagementPanelScript", + "main.gd::const::UIAudioFeedbackScript", + "main.gd::const::UIAudioOwnerShutdownScript", + "main.gd::const::UIAudioSettingsDialogScript", + "main.gd::const::WorldPreviewScript", + "main.gd::func::_activate_management_mode", + "main.gd::func::_advance_ui_sequence_from_key", + "main.gd::func::_apply_button_icon", + "main.gd::func::_apply_button_style", + "main.gd::func::_apply_loaded_interface", + "main.gd::func::_build_button_text", + "main.gd::func::_build_interface", + "main.gd::func::_button", + "main.gd::func::_call_array", + "main.gd::func::_cancel_demolition", + "main.gd::func::_capture_interface_selection", + "main.gd::func::_capture_manual_thumbnail_png", + "main.gd::func::_clear_active_tool", + "main.gd::func::_clear_build_selection", + "main.gd::func::_command_failure_message", + "main.gd::func::_confirm_demolition", + "main.gd::func::_confirm_dimension_migration", + "main.gd::func::_connect_signals", + "main.gd::func::_construction_facility", + "main.gd::func::_create_bottom_bar", + "main.gd::func::_create_navigation_rail", + "main.gd::func::_create_side_panel", + "main.gd::func::_create_top_bar", + "main.gd::func::_current_audio_foreground", + "main.gd::func::_definition_value", + "main.gd::func::_elevator_floor_name", + "main.gd::func::_elevator_wait_text", + "main.gd::func::_ensure_defense_manager", + "main.gd::func::_ensure_guest_observer", + "main.gd::func::_ensure_manual_save_slots", + "main.gd::func::_event_choice_button_text", + "main.gd::func::_excavation_command_failure_message", + "main.gd::func::_excavation_cost_and_time", + "main.gd::func::_excavation_failure_message", + "main.gd::func::_excavation_gate_message", + "main.gd::func::_excavation_idempotency_key", + "main.gd::func::_exit_tree", + "main.gd::func::_facility_definition", + "main.gd::func::_facility_reasons", + "main.gd::func::_facility_upkeep_text", + "main.gd::func::_flush_dashboard_refresh", + "main.gd::func::_focus_initial_view", + "main.gd::func::_hire_failure_message", + "main.gd::func::_hire_staff_candidate", + "main.gd::func::_hud_value_label", + "main.gd::func::_icon_rect", + "main.gd::func::_load_manual", + "main.gd::func::_load_manual_at_path", + "main.gd::func::_load_selected_manual_slot", + "main.gd::func::_move_selected_detail_first", + "main.gd::func::_on_audio_world_selection", + "main.gd::func::_on_build_button_toggled", + "main.gd::func::_on_build_preview_changed", + "main.gd::func::_on_checkpoint_presentation_requested", + "main.gd::func::_on_command_completed", + "main.gd::func::_on_construction_selected", + "main.gd::func::_on_defense_notice_input", + "main.gd::func::_on_elevator_operation_pressed", + "main.gd::func::_on_excavation_preview_changed", + "main.gd::func::_on_facility_selected", + "main.gd::func::_on_legacy_stair_selected", + "main.gd::func::_on_map_cell_clicked", + "main.gd::func::_on_opening_action", + "main.gd::func::_on_phase_changed", + "main.gd::func::_on_related_facility_selected", + "main.gd::func::_on_resource_changed", + "main.gd::func::_on_save_operation_completed", + "main.gd::func::_on_snapshot_published", + "main.gd::func::_on_staff_management_status", + "main.gd::func::_on_time_changed", + "main.gd::func::_on_window_close_requested", + "main.gd::func::_operations_dashboard_text", + "main.gd::func::_panel_style", + "main.gd::func::_pause_fresh_opening", + "main.gd::func::_phase_name", + "main.gd::func::_populate_build_palette", + "main.gd::func::_populate_staff_candidates", + "main.gd::func::_prepare_owned_audio_close", + "main.gd::func::_preview_demolition", + "main.gd::func::_preview_dimension_migration", + "main.gd::func::_preview_failure_message", + "main.gd::func::_queue_dashboard_refresh", + "main.gd::func::_ready", + "main.gd::func::_refresh_dashboard", + "main.gd::func::_refresh_foreground_event", + "main.gd::func::_refresh_selected_construction", + "main.gd::func::_refresh_selected_elevator", + "main.gd::func::_refresh_selected_entity", + "main.gd::func::_refresh_selected_excavation", + "main.gd::func::_refresh_selected_stairs", + "main.gd::func::_refresh_staff_candidates", + "main.gd::func::_report_audio_close_drained", + "main.gd::func::_resource_title", + "main.gd::func::_restore_actor_interface_selection", + "main.gd::func::_restore_defense_interface_selection", + "main.gd::func::_restore_interface_selection", + "main.gd::func::_restore_product_interface_selection", + "main.gd::func::_result_message", + "main.gd::func::_rotate_build_preview", + "main.gd::func::_save_manual", + "main.gd::func::_save_manual_at_path", + "main.gd::func::_save_new_manual_slot", + "main.gd::func::_section_heading", + "main.gd::func::_select_build_definition", + "main.gd::func::_select_excavation_job", + "main.gd::func::_selected_elevator_report", + "main.gd::func::_service", + "main.gd::func::_set_build_palette_open", + "main.gd::func::_set_business_panel_open", + "main.gd::func::_set_demolition_mode", + "main.gd::func::_set_excavation_mode", + "main.gd::func::_set_layer", + "main.gd::func::_set_ledger_panel_open", + "main.gd::func::_set_management_panel_open", + "main.gd::func::_set_pan_mode", + "main.gd::func::_set_speed", + "main.gd::func::_set_staff_candidate_panel_open", + "main.gd::func::_set_staff_management_panel_open", + "main.gd::func::_set_status", + "main.gd::func::_set_supply_panel_open", + "main.gd::func::_setup_ui_audio", + "main.gd::func::_show_audio_settings", + "main.gd::func::_show_defense_manager", + "main.gd::func::_show_demolition_result", + "main.gd::func::_show_guest_observer", + "main.gd::func::_show_manual_load_slots", + "main.gd::func::_staff_candidate_summaries", + "main.gd::func::_staff_candidate_text", + "main.gd::func::_start_excavation", + "main.gd::func::_submit_foreground_event_choice", + "main.gd::func::_submit_ui_command", + "main.gd::func::_synchronize_elevator_pending", + "main.gd::func::_synchronize_ui_command_state", + "main.gd::func::_themed_panel_style", + "main.gd::func::_toggle_build_palette", + "main.gd::func::_toggle_demolition_mode", + "main.gd::func::_toggle_excavation_mode", + "main.gd::func::_toggle_management_panel", + "main.gd::func::_toggle_pan_mode", + "main.gd::func::_toggle_pause", + "main.gd::func::_toggle_staff_candidate_panel", + "main.gd::func::_toggle_staff_management_panel", + "main.gd::func::_unhandled_key_input", + "main.gd::func::_update_pause_and_speed", + "main.gd::func::_valid_interface_selection_version", + "main.gd::func::_visual_color", + "main.gd::signal::ui_audio_window_close_drained", + "main.gd::var::_audio_feedback", + "main.gd::var::_audio_settings_dialog", + "main.gd::var::_build_palette_open", + "main.gd::var::_build_rotation", + "main.gd::var::_build_sequence", + "main.gd::var::_business_panel_open", + "main.gd::var::_dashboard_refresh_pending", + "main.gd::var::_demolition_mode", + "main.gd::var::_demolition_target", + "main.gd::var::_dimension_migration_digest", + "main.gd::var::_elevator_sequence", + "main.gd::var::_excavation_mode", + "main.gd::var::_excavation_sequence", + "main.gd::var::_hire_sequence", + "main.gd::var::_interface_layer", + "main.gd::var::_last_presented_close_minute", + "main.gd::var::_ledger_panel_open", + "main.gd::var::_management_panel_open", + "main.gd::var::_operations_start_report", + "main.gd::var::_paired_bgm", + "main.gd::var::_pending_build_commands", + "main.gd::var::_pending_demolition_commands", + "main.gd::var::_pending_elevator_commands", + "main.gd::var::_pending_excavation_commands", + "main.gd::var::_pending_hire_commands", + "main.gd::var::_pending_related_commands", + "main.gd::var::_runtime", + "main.gd::var::_selected_definition", + "main.gd::var::_selected_definition_id", + "main.gd::var::_selected_entity_id", + "main.gd::var::_selected_entity_kind", + "main.gd::var::_staff_candidate_panel_open", + "main.gd::var::_staff_management_panel_open", + "main.gd::var::_supply_panel_open", + "main.gd::var::_window_close_in_progress", + "main.gd::var::alert_count_label", + "main.gd::var::alert_icon_node", + "main.gd::var::audio_settings_path", + "main.gd::var::back_button", + "main.gd::var::build_buttons", + "main.gd::var::build_status_label", + "main.gd::var::business_management_panel", + "main.gd::var::dashboard_summary_label", + "main.gd::var::defense_manager", + "main.gd::var::demolition_button", + "main.gd::var::demolition_dialog", + "main.gd::var::dimension_migration_dialog", + "main.gd::var::elevator_operation_button", + "main.gd::var::elevator_operation_hint", + "main.gd::var::event_log_dialog", + "main.gd::var::excavation_button", + "main.gd::var::foreground_event_choices", + "main.gd::var::foreground_event_label", + "main.gd::var::foreground_event_panel", + "main.gd::var::front_button", + "main.gd::var::guest_count_label", + "main.gd::var::guest_observer", + "main.gd::var::lodging_count_label", + "main.gd::var::management_panel", + "main.gd::var::management_toggle_button", + "main.gd::var::manual_save_path", + "main.gd::var::manual_save_slots_dialog", + "main.gd::var::navigation_buttons", + "main.gd::var::opening_guide", + "main.gd::var::operating_ledger_panel", + "main.gd::var::operations_controls", + "main.gd::var::palette_scroll", + "main.gd::var::palette_toggle_button", + "main.gd::var::pan_button", + "main.gd::var::pause_button", + "main.gd::var::phase_label", + "main.gd::var::rank_button", + "main.gd::var::rank_dialog", + "main.gd::var::related_facility_picker", + "main.gd::var::resource_icon_nodes", + "main.gd::var::resource_labels", + "main.gd::var::selected_detail_label", + "main.gd::var::selected_elevator_controls", + "main.gd::var::selected_entity_icon", + "main.gd::var::selected_heading_label", + "main.gd::var::speed_buttons", + "main.gd::var::staff_candidate_buttons", + "main.gd::var::staff_candidate_panel", + "main.gd::var::staff_candidate_summary_label", + "main.gd::var::staff_candidate_toggle_button", + "main.gd::var::staff_management_panel", + "main.gd::var::staff_management_toggle_button", + "main.gd::var::supply_management_panel", + "main.gd::var::time_label", + "main.gd::var::visual_theme", + "main.gd::var::weather_label", + "main.gd::var::world_preview" + ], + "p24_side_symbols": [ + "main.gd::const::BusinessManagementPanelScript", + "main.gd::const::DefaultVisualTheme", + "main.gd::const::DefenseManagementPanelScript", + "main.gd::const::ELEVATOR_PHASE_NAMES", + "main.gd::const::EVENT_CHOICE_NAMES", + "main.gd::const::EXCAVATION_REASON_NAMES", + "main.gd::const::EventLogDialogScript", + "main.gd::const::FACILITY_REASON_NAMES", + "main.gd::const::FACILITY_STATE_NAMES", + "main.gd::const::LayerSkin", + "main.gd::const::MANUAL_SAVE_PATH", + "main.gd::const::MVP_FACILITY_IDS", + "main.gd::const::ManualSaveSlotsDialogScript", + "main.gd::const::OpeningGuideScript", + "main.gd::const::OperatingLedgerPanelScript", + "main.gd::const::OperationFeedbackScript", + "main.gd::const::OperationsCameraControlsScript", + "main.gd::const::PREVIEW_REASON_NAMES", + "main.gd::const::PairedBGMScript", + "main.gd::const::RankDialogScript", + "main.gd::const::STAFF_ROLE_NAMES", + "main.gd::const::STAFF_SHIFT_NAMES", + "main.gd::const::STAFF_SKILL_NAMES", + "main.gd::const::StaffManagementPanelScript", + "main.gd::const::SupplyManagementPanelScript", + "main.gd::const::UIAudioFeedbackScript", + "main.gd::const::UIAudioOwnerShutdownScript", + "main.gd::const::UIAudioSettingsDialogScript", + "main.gd::const::WorldPreviewScript", + "main.gd::func::_activate_management_mode", + "main.gd::func::_advance_ui_sequence_from_key", + "main.gd::func::_apply_button_icon", + "main.gd::func::_apply_button_style", + "main.gd::func::_apply_loaded_interface", + "main.gd::func::_bind_domain_event_stream", + "main.gd::func::_build_button_text", + "main.gd::func::_build_interface", + "main.gd::func::_button", + "main.gd::func::_call_array", + "main.gd::func::_capture_interface_selection", + "main.gd::func::_capture_manual_thumbnail_png", + "main.gd::func::_clear_active_tool", + "main.gd::func::_clear_build_selection", + "main.gd::func::_command_failure_message", + "main.gd::func::_confirm_dimension_migration", + "main.gd::func::_connect_signals", + "main.gd::func::_construction_facility", + "main.gd::func::_create_bottom_bar", + "main.gd::func::_create_navigation_rail", + "main.gd::func::_create_side_panel", + "main.gd::func::_create_top_bar", + "main.gd::func::_current_audio_foreground", + "main.gd::func::_definition_value", + "main.gd::func::_elevator_floor_name", + "main.gd::func::_elevator_wait_text", + "main.gd::func::_ensure_defense_manager", + "main.gd::func::_ensure_guest_observer", + "main.gd::func::_ensure_important_warning_cutin", + "main.gd::func::_ensure_manual_save_slots", + "main.gd::func::_event_choice_button_text", + "main.gd::func::_excavation_command_failure_message", + "main.gd::func::_excavation_cost_and_time", + "main.gd::func::_excavation_failure_message", + "main.gd::func::_excavation_gate_message", + "main.gd::func::_excavation_idempotency_key", + "main.gd::func::_exit_tree", + "main.gd::func::_facility_definition", + "main.gd::func::_facility_reasons", + "main.gd::func::_facility_upkeep_text", + "main.gd::func::_flush_dashboard_refresh", + "main.gd::func::_focus_initial_view", + "main.gd::func::_hire_failure_message", + "main.gd::func::_hire_staff_candidate", + "main.gd::func::_hud_value_label", + "main.gd::func::_icon_rect", + "main.gd::func::_load_manual", + "main.gd::func::_load_manual_at_path", + "main.gd::func::_load_selected_manual_slot", + "main.gd::func::_move_selected_detail_first", + "main.gd::func::_on_audio_world_selection", + "main.gd::func::_on_build_button_toggled", + "main.gd::func::_on_build_preview_changed", + "main.gd::func::_on_checkpoint_presentation_requested", + "main.gd::func::_on_command_completed", + "main.gd::func::_on_construction_selected", + "main.gd::func::_on_defense_notice_input", + "main.gd::func::_on_domain_event_published", + "main.gd::func::_on_elevator_operation_pressed", + "main.gd::func::_on_excavation_preview_changed", + "main.gd::func::_on_facility_selected", + "main.gd::func::_on_legacy_stair_selected", + "main.gd::func::_on_map_cell_clicked", + "main.gd::func::_on_opening_action", + "main.gd::func::_on_phase_changed", + "main.gd::func::_on_related_facility_selected", + "main.gd::func::_on_resource_changed", + "main.gd::func::_on_save_operation_completed", + "main.gd::func::_on_snapshot_published", + "main.gd::func::_on_staff_management_status", + "main.gd::func::_on_time_changed", + "main.gd::func::_on_window_close_requested", + "main.gd::func::_operations_dashboard_text", + "main.gd::func::_panel_style", + "main.gd::func::_pause_fresh_opening", + "main.gd::func::_phase_name", + "main.gd::func::_populate_build_palette", + "main.gd::func::_populate_staff_candidates", + "main.gd::func::_prepare_owned_audio_close", + "main.gd::func::_preview_dimension_migration", + "main.gd::func::_preview_failure_message", + "main.gd::func::_queue_dashboard_refresh", + "main.gd::func::_ready", + "main.gd::func::_refresh_dashboard", + "main.gd::func::_refresh_foreground_event", + "main.gd::func::_refresh_selected_construction", + "main.gd::func::_refresh_selected_elevator", + "main.gd::func::_refresh_selected_entity", + "main.gd::func::_refresh_selected_excavation", + "main.gd::func::_refresh_selected_stairs", + "main.gd::func::_refresh_staff_candidates", + "main.gd::func::_report_audio_close_drained", + "main.gd::func::_restore_actor_interface_selection", + "main.gd::func::_restore_defense_interface_selection", + "main.gd::func::_restore_interface_selection", + "main.gd::func::_restore_product_interface_selection", + "main.gd::func::_result_message", + "main.gd::func::_rotate_build_preview", + "main.gd::func::_save_manual", + "main.gd::func::_save_manual_at_path", + "main.gd::func::_save_new_manual_slot", + "main.gd::func::_section_heading", + "main.gd::func::_select_build_definition", + "main.gd::func::_select_excavation_job", + "main.gd::func::_selected_elevator_report", + "main.gd::func::_service", + "main.gd::func::_set_build_palette_open", + "main.gd::func::_set_business_panel_open", + "main.gd::func::_set_excavation_mode", + "main.gd::func::_set_layer", + "main.gd::func::_set_ledger_panel_open", + "main.gd::func::_set_management_panel_open", + "main.gd::func::_set_pan_mode", + "main.gd::func::_set_speed", + "main.gd::func::_set_staff_candidate_panel_open", + "main.gd::func::_set_staff_management_panel_open", + "main.gd::func::_set_status", + "main.gd::func::_set_supply_panel_open", + "main.gd::func::_setup_ui_audio", + "main.gd::func::_show_audio_settings", + "main.gd::func::_show_defense_manager", + "main.gd::func::_show_guest_observer", + "main.gd::func::_show_manual_load_slots", + "main.gd::func::_staff_candidate_summaries", + "main.gd::func::_staff_candidate_text", + "main.gd::func::_start_excavation", + "main.gd::func::_submit_foreground_event_choice", + "main.gd::func::_submit_ui_command", + "main.gd::func::_synchronize_elevator_pending", + "main.gd::func::_synchronize_ui_command_state", + "main.gd::func::_themed_panel_style", + "main.gd::func::_toggle_build_palette", + "main.gd::func::_toggle_excavation_mode", + "main.gd::func::_toggle_management_panel", + "main.gd::func::_toggle_pan_mode", + "main.gd::func::_toggle_pause", + "main.gd::func::_toggle_staff_candidate_panel", + "main.gd::func::_toggle_staff_management_panel", + "main.gd::func::_unbind_domain_event_stream", + "main.gd::func::_unhandled_key_input", + "main.gd::func::_update_modal_input_guard", + "main.gd::func::_update_pause_and_speed", + "main.gd::func::_valid_interface_selection_version", + "main.gd::func::_visual_color", + "main.gd::func::assign_staff_by_click", + "main.gd::func::show_warning_cutin", + "main.gd::signal::ui_audio_window_close_drained", + "main.gd::var::_audio_feedback", + "main.gd::var::_audio_settings_dialog", + "main.gd::var::_build_palette_open", + "main.gd::var::_build_rotation", + "main.gd::var::_build_sequence", + "main.gd::var::_business_panel_open", + "main.gd::var::_dashboard_refresh_pending", + "main.gd::var::_dimension_migration_digest", + "main.gd::var::_elevator_sequence", + "main.gd::var::_excavation_mode", + "main.gd::var::_excavation_sequence", + "main.gd::var::_hire_sequence", + "main.gd::var::_interface_layer", + "main.gd::var::_last_presented_close_minute", + "main.gd::var::_ledger_panel_open", + "main.gd::var::_management_panel_open", + "main.gd::var::_operations_start_report", + "main.gd::var::_paired_bgm", + "main.gd::var::_pending_build_commands", + "main.gd::var::_pending_elevator_commands", + "main.gd::var::_pending_excavation_commands", + "main.gd::var::_pending_hire_commands", + "main.gd::var::_pending_related_commands", + "main.gd::var::_runtime", + "main.gd::var::_selected_definition", + "main.gd::var::_selected_definition_id", + "main.gd::var::_selected_entity_id", + "main.gd::var::_selected_entity_kind", + "main.gd::var::_staff_candidate_panel_open", + "main.gd::var::_staff_management_panel_open", + "main.gd::var::_subscribed_event_stream", + "main.gd::var::_supply_panel_open", + "main.gd::var::_window_close_in_progress", + "main.gd::var::alert_count_label", + "main.gd::var::alert_icon_node", + "main.gd::var::audio_settings_path", + "main.gd::var::back_button", + "main.gd::var::build_buttons", + "main.gd::var::build_status_label", + "main.gd::var::business_management_panel", + "main.gd::var::dashboard_summary_label", + "main.gd::var::defense_manager", + "main.gd::var::dimension_migration_dialog", + "main.gd::var::elevator_operation_button", + "main.gd::var::elevator_operation_hint", + "main.gd::var::event_log_dialog", + "main.gd::var::excavation_button", + "main.gd::var::foreground_event_choices", + "main.gd::var::foreground_event_label", + "main.gd::var::foreground_event_panel", + "main.gd::var::front_button", + "main.gd::var::guest_count_label", + "main.gd::var::guest_observer", + "main.gd::var::important_warning_cutin", + "main.gd::var::lodging_count_label", + "main.gd::var::management_panel", + "main.gd::var::management_toggle_button", + "main.gd::var::manual_save_path", + "main.gd::var::manual_save_slots_dialog", + "main.gd::var::navigation_buttons", + "main.gd::var::opening_guide", + "main.gd::var::operating_ledger_panel", + "main.gd::var::operations_camera_controls", + "main.gd::var::operations_controls", + "main.gd::var::palette_scroll", + "main.gd::var::palette_toggle_button", + "main.gd::var::pan_button", + "main.gd::var::pause_button", + "main.gd::var::phase_label", + "main.gd::var::rank_button", + "main.gd::var::rank_dialog", + "main.gd::var::related_facility_picker", + "main.gd::var::resource_icon_nodes", + "main.gd::var::resource_labels", + "main.gd::var::selected_detail_label", + "main.gd::var::selected_elevator_controls", + "main.gd::var::selected_entity_icon", + "main.gd::var::selected_heading_label", + "main.gd::var::speed_buttons", + "main.gd::var::staff_candidate_buttons", + "main.gd::var::staff_candidate_panel", + "main.gd::var::staff_candidate_summary_label", + "main.gd::var::staff_candidate_toggle_button", + "main.gd::var::staff_management_panel", + "main.gd::var::staff_management_toggle_button", + "main.gd::var::supply_management_panel", + "main.gd::var::time_label", + "main.gd::var::visual_theme", + "main.gd::var::weather_label", + "main.gd::var::world_preview" + ], + "p24_added_symbols": [ + "main.gd::const::OperationFeedbackScript", + "main.gd::const::OperationsCameraControlsScript", + "main.gd::func::_bind_domain_event_stream", + "main.gd::func::_ensure_important_warning_cutin", + "main.gd::func::_on_domain_event_published", + "main.gd::func::_unbind_domain_event_stream", + "main.gd::func::_update_modal_input_guard", + "main.gd::func::assign_staff_by_click", + "main.gd::func::show_warning_cutin", + "main.gd::var::_subscribed_event_stream", + "main.gd::var::important_warning_cutin", + "main.gd::var::operations_camera_controls" + ], + "main_removed_vs_base_untouched_by_p24": [], + "required_count": 279, + "reconciled_symbol_count": 279, + "missing": [], + "duplicates": [], + "unexpected": [], + "repair4_added_with_reason": {}, + "body_issues": [], + "p24_lines_reexpressed_with_reason": [], + "conflict_markers": false + }, + "scenes/main/world_preview.gd": { + "kind": "gdscript", + "main_symbols": [ + "world_preview.gd::class_name::WorldPreview", + "world_preview.gd::const::INVALID_EXCAVATION_PREVIEW", + "world_preview.gd::const::INVALID_PREVIEW", + "world_preview.gd::const::InactiveLayerRooms", + "world_preview.gd::const::LAYER_LENS_SHADER", + "world_preview.gd::const::LAYER_TRANSITION_SECONDS", + "world_preview.gd::const::MAP_PADDING", + "world_preview.gd::const::People", + "world_preview.gd::const::PixelArt", + "world_preview.gd::const::RoomPresentation", + "world_preview.gd::enum::LayerMode", + "world_preview.gd::func::_actor_drawing", + "world_preview.gd::func::_actor_offset", + "world_preview.gd::func::_add_queue_member", + "world_preview.gd::func::_build_connection_report", + "world_preview.gd::func::_build_life_observation", + "world_preview.gd::func::_can_drop_data", + "world_preview.gd::func::_cell_bounds_rect", + "world_preview.gd::func::_cell_dimensions", + "world_preview.gd::func::_cells_from_serialized", + "world_preview.gd::func::_clamp_pan", + "world_preview.gd::func::_completed_facilities_for_draw", + "world_preview.gd::func::_connect_runtime", + "world_preview.gd::func::_construction_jobs_for_draw", + "world_preview.gd::func::_current_preview_topology", + "world_preview.gd::func::_disconnect_runtime", + "world_preview.gd::func::_draw", + "world_preview.gd::func::_draw_actors", + "world_preview.gd::func::_draw_background", + "world_preview.gd::func::_draw_build_connection", + "world_preview.gd::func::_draw_build_preview", + "world_preview.gd::func::_draw_center_message", + "world_preview.gd::func::_draw_congestion_overlay", + "world_preview.gd::func::_draw_construction_labels", + "world_preview.gd::func::_draw_contents", + "world_preview.gd::func::_draw_elevators", + "world_preview.gd::func::_draw_excavation_jobs", + "world_preview.gd::func::_draw_excavation_preview", + "world_preview.gd::func::_draw_facility_labels", + "world_preview.gd::func::_draw_hatch", + "world_preview.gd::func::_draw_hover", + "world_preview.gd::func::_draw_leaf_cluster", + "world_preview.gd::func::_draw_legacy_stairs", + "world_preview.gd::func::_draw_life_badges", + "world_preview.gd::func::_draw_life_overlay", + "world_preview.gd::func::_draw_map_caption", + "world_preview.gd::func::_draw_observed_actor", + "world_preview.gd::func::_draw_other_layer", + "world_preview.gd::func::_draw_pixel_actor", + "world_preview.gd::func::_draw_readable_badge", + "world_preview.gd::func::_draw_world_cells", + "world_preview.gd::func::_draw_world_entity_icons", + "world_preview.gd::func::_drop_data", + "world_preview.gd::func::_elevator_actor_key", + "world_preview.gd::func::_end_pan_drag", + "world_preview.gd::func::_facility_color", + "world_preview.gd::func::_facility_visual_level", + "world_preview.gd::func::_fallback_build_cells", + "world_preview.gd::func::_fallback_cells_are_available", + "world_preview.gd::func::_fit_life_actors", + "world_preview.gd::func::_guest_activity", + "world_preview.gd::func::_gui_input", + "world_preview.gd::func::_legacy_stair_rect", + "world_preview.gd::func::_life_actor_region", + "world_preview.gd::func::_life_core", + "world_preview.gd::func::_life_fitted_groups", + "world_preview.gd::func::_life_landing_region", + "world_preview.gd::func::_life_pack", + "world_preview.gd::func::_life_scaled", + "world_preview.gd::func::_life_track_region", + "world_preview.gd::func::_normalized_rotation", + "world_preview.gd::func::_notification", + "world_preview.gd::func::_observe_actors", + "world_preview.gd::func::_observe_elevators", + "world_preview.gd::func::_observe_queues", + "world_preview.gd::func::_observe_rooms", + "world_preview.gd::func::_on_mouse_exited", + "world_preview.gd::func::_on_snapshot_published", + "world_preview.gd::func::_other_layer_rooms", + "world_preview.gd::func::_prepare_queues", + "world_preview.gd::func::_prepare_room_presentations", + "world_preview.gd::func::_process", + "world_preview.gd::func::_readable_badge_rect", + "world_preview.gd::func::_ready", + "world_preview.gd::func::_rescue_treatment_position", + "world_preview.gd::func::_resolve_observation_labels", + "world_preview.gd::func::_resting_staff_position", + "world_preview.gd::func::_serialize_cells", + "world_preview.gd::func::_service", + "world_preview.gd::func::_service_use_position", + "world_preview.gd::func::_set_hovered_cell", + "world_preview.gd::func::_staff_work_animation", + "world_preview.gd::func::_staff_work_position", + "world_preview.gd::func::_start_layer_transition", + "world_preview.gd::func::_tracked_actor_position", + "world_preview.gd::func::_tracked_elevator_floor", + "world_preview.gd::func::_traffic_segments", + "world_preview.gd::func::_unhandled_key_input", + "world_preview.gd::func::_update_build_preview", + "world_preview.gd::func::_update_excavation_preview", + "world_preview.gd::func::_world_bounds", + "world_preview.gd::func::canvas_to_cell", + "world_preview.gd::func::cell_to_canvas", + "world_preview.gd::func::clear_build_definition", + "world_preview.gd::func::clear_hovered_cell", + "world_preview.gd::func::facility_at_canvas", + "world_preview.gd::func::focus_operating_area", + "world_preview.gd::func::get_build_preview_report", + "world_preview.gd::func::get_cell_rect", + "world_preview.gd::func::get_excavation_preview_report", + "world_preview.gd::func::get_hovered_cell", + "world_preview.gd::func::get_legacy_stair_records", + "world_preview.gd::func::get_life_observation_report", + "world_preview.gd::func::get_map_rect", + "world_preview.gd::func::legacy_stair_at_canvas", + "world_preview.gd::func::reset_view", + "world_preview.gd::func::set_build_definition", + "world_preview.gd::func::set_build_rotation", + "world_preview.gd::func::set_demolition_mode", + "world_preview.gd::func::set_excavation_mode", + "world_preview.gd::func::set_pan_mode", + "world_preview.gd::func::set_runtime", + "world_preview.gd::func::set_visual_theme", + "world_preview.gd::func::set_zoom", + "world_preview.gd::signal::build_preview_changed", + "world_preview.gd::signal::cell_clicked", + "world_preview.gd::signal::construction_selected", + "world_preview.gd::signal::excavation_preview_changed", + "world_preview.gd::signal::facility_selected", + "world_preview.gd::signal::hovered_cell_changed", + "world_preview.gd::signal::legacy_stair_selected", + "world_preview.gd::signal::staff_assignment_dropped", + "world_preview.gd::var::_actor_tracks", + "world_preview.gd::var::_animation_frame", + "world_preview.gd::var::_animation_time", + "world_preview.gd::var::_boarded_actor_ids", + "world_preview.gd::var::_carried_patients", + "world_preview.gd::var::_connection_cache_key", + "world_preview.gd::var::_connection_cache_report", + "world_preview.gd::var::_drag_button", + "world_preview.gd::var::_dragging", + "world_preview.gd::var::_draw_cache", + "world_preview.gd::var::_drawing", + "world_preview.gd::var::_elevator_tracks", + "world_preview.gd::var::_hovered_cell", + "world_preview.gd::var::_inactive_layer_art", + "world_preview.gd::var::_landing_badges", + "world_preview.gd::var::_last_excavation_preview_report", + "world_preview.gd::var::_last_preview_report", + "world_preview.gd::var::_layer_lens", + "world_preview.gd::var::_layer_transition_elapsed", + "world_preview.gd::var::_life_observation", + "world_preview.gd::var::_life_observation_serial", + "world_preview.gd::var::_life_overlay", + "world_preview.gd::var::_preview_topology_key", + "world_preview.gd::var::_queue_hidden", + "world_preview.gd::var::_queue_positions", + "world_preview.gd::var::_resting_staff", + "world_preview.gd::var::_room_presentations", + "world_preview.gd::var::_runtime", + "world_preview.gd::var::_transport_report", + "world_preview.gd::var::_treated_patients", + "world_preview.gd::var::build_definition", + "world_preview.gd::var::build_rotation", + "world_preview.gd::var::demolition_mode", + "world_preview.gd::var::excavation_mode", + "world_preview.gd::var::layer_mode", + "world_preview.gd::var::pan_mode", + "world_preview.gd::var::pan_offset", + "world_preview.gd::var::selected_entity_id", + "world_preview.gd::var::show_grid", + "world_preview.gd::var::visual_theme", + "world_preview.gd::var::zoom_level" + ], + "p24_side_symbols": [ + "world_preview.gd::class_name::WorldPreview", + "world_preview.gd::const::INVALID_EXCAVATION_PREVIEW", + "world_preview.gd::const::INVALID_PREVIEW", + "world_preview.gd::const::InactiveLayerRooms", + "world_preview.gd::const::LAYER_LENS_SHADER", + "world_preview.gd::const::LAYER_TRANSITION_SECONDS", + "world_preview.gd::const::MAP_PADDING", + "world_preview.gd::const::OperationFeedbackScript", + "world_preview.gd::const::People", + "world_preview.gd::const::PixelArt", + "world_preview.gd::const::RoomPresentation", + "world_preview.gd::enum::LayerMode", + "world_preview.gd::func::_actor_offset", + "world_preview.gd::func::_add_queue_member", + "world_preview.gd::func::_build_connection_report", + "world_preview.gd::func::_can_drop_data", + "world_preview.gd::func::_cell_bounds_rect", + "world_preview.gd::func::_cell_dimensions", + "world_preview.gd::func::_cells_from_serialized", + "world_preview.gd::func::_clamp_pan", + "world_preview.gd::func::_completed_facilities_for_draw", + "world_preview.gd::func::_connect_runtime", + "world_preview.gd::func::_construction_jobs_for_draw", + "world_preview.gd::func::_current_preview_topology", + "world_preview.gd::func::_disconnect_runtime", + "world_preview.gd::func::_draw", + "world_preview.gd::func::_draw_actors", + "world_preview.gd::func::_draw_background", + "world_preview.gd::func::_draw_build_connection", + "world_preview.gd::func::_draw_build_preview", + "world_preview.gd::func::_draw_center_message", + "world_preview.gd::func::_draw_congestion_overlay", + "world_preview.gd::func::_draw_construction_labels", + "world_preview.gd::func::_draw_contents", + "world_preview.gd::func::_draw_elevators", + "world_preview.gd::func::_draw_excavation_jobs", + "world_preview.gd::func::_draw_excavation_preview", + "world_preview.gd::func::_draw_facility_labels", + "world_preview.gd::func::_draw_hatch", + "world_preview.gd::func::_draw_hover", + "world_preview.gd::func::_draw_leaf_cluster", + "world_preview.gd::func::_draw_legacy_stairs", + "world_preview.gd::func::_draw_map_caption", + "world_preview.gd::func::_draw_other_layer", + "world_preview.gd::func::_draw_pixel_actor", + "world_preview.gd::func::_draw_readable_badge", + "world_preview.gd::func::_draw_unit_bubble", + "world_preview.gd::func::_draw_world_cells", + "world_preview.gd::func::_draw_world_entity_icons", + "world_preview.gd::func::_drop_data", + "world_preview.gd::func::_elevator_actor_key", + "world_preview.gd::func::_end_pan_drag", + "world_preview.gd::func::_facility_color", + "world_preview.gd::func::_facility_visual_level", + "world_preview.gd::func::_fallback_build_cells", + "world_preview.gd::func::_fallback_cells_are_available", + "world_preview.gd::func::_guest_activity", + "world_preview.gd::func::_guest_relationship", + "world_preview.gd::func::_gui_input", + "world_preview.gd::func::_legacy_stair_rect", + "world_preview.gd::func::_normalized_rotation", + "world_preview.gd::func::_notification", + "world_preview.gd::func::_on_mouse_exited", + "world_preview.gd::func::_on_snapshot_published", + "world_preview.gd::func::_other_layer_rooms", + "world_preview.gd::func::_prepare_queues", + "world_preview.gd::func::_prepare_room_presentations", + "world_preview.gd::func::_process", + "world_preview.gd::func::_ready", + "world_preview.gd::func::_rescue_treatment_position", + "world_preview.gd::func::_resting_staff_position", + "world_preview.gd::func::_serialize_cells", + "world_preview.gd::func::_service", + "world_preview.gd::func::_service_use_position", + "world_preview.gd::func::_set_hovered_cell", + "world_preview.gd::func::_staff_relationship", + "world_preview.gd::func::_staff_work_animation", + "world_preview.gd::func::_staff_work_position", + "world_preview.gd::func::_start_layer_transition", + "world_preview.gd::func::_tracked_actor_position", + "world_preview.gd::func::_tracked_elevator_floor", + "world_preview.gd::func::_unhandled_key_input", + "world_preview.gd::func::_update_build_preview", + "world_preview.gd::func::_update_excavation_preview", + "world_preview.gd::func::_world_bounds", + "world_preview.gd::func::canvas_to_cell", + "world_preview.gd::func::cell_to_canvas", + "world_preview.gd::func::clear_build_definition", + "world_preview.gd::func::clear_hovered_cell", + "world_preview.gd::func::facility_at_canvas", + "world_preview.gd::func::focus_operating_area", + "world_preview.gd::func::get_build_preview_report", + "world_preview.gd::func::get_cell_rect", + "world_preview.gd::func::get_excavation_preview_report", + "world_preview.gd::func::get_hovered_cell", + "world_preview.gd::func::get_legacy_stair_records", + "world_preview.gd::func::get_map_rect", + "world_preview.gd::func::legacy_stair_at_canvas", + "world_preview.gd::func::reset_view", + "world_preview.gd::func::set_build_definition", + "world_preview.gd::func::set_build_rotation", + "world_preview.gd::func::set_excavation_mode", + "world_preview.gd::func::set_pan_mode", + "world_preview.gd::func::set_runtime", + "world_preview.gd::func::set_visual_theme", + "world_preview.gd::func::set_zoom", + "world_preview.gd::signal::build_preview_changed", + "world_preview.gd::signal::cell_clicked", + "world_preview.gd::signal::construction_selected", + "world_preview.gd::signal::excavation_preview_changed", + "world_preview.gd::signal::facility_selected", + "world_preview.gd::signal::hovered_cell_changed", + "world_preview.gd::signal::legacy_stair_selected", + "world_preview.gd::signal::staff_assignment_clicked", + "world_preview.gd::signal::staff_assignment_dropped", + "world_preview.gd::var::_actor_tracks", + "world_preview.gd::var::_animation_frame", + "world_preview.gd::var::_animation_time", + "world_preview.gd::var::_boarded_actor_ids", + "world_preview.gd::var::_carried_patients", + "world_preview.gd::var::_connection_cache_key", + "world_preview.gd::var::_connection_cache_report", + "world_preview.gd::var::_drag_button", + "world_preview.gd::var::_dragging", + "world_preview.gd::var::_draw_cache", + "world_preview.gd::var::_drawing", + "world_preview.gd::var::_elevator_tracks", + "world_preview.gd::var::_hovered_cell", + "world_preview.gd::var::_inactive_layer_art", + "world_preview.gd::var::_landing_badges", + "world_preview.gd::var::_last_excavation_preview_report", + "world_preview.gd::var::_last_preview_report", + "world_preview.gd::var::_layer_lens", + "world_preview.gd::var::_layer_transition_elapsed", + "world_preview.gd::var::_preview_topology_key", + "world_preview.gd::var::_queue_hidden", + "world_preview.gd::var::_queue_positions", + "world_preview.gd::var::_resting_staff", + "world_preview.gd::var::_room_presentations", + "world_preview.gd::var::_runtime", + "world_preview.gd::var::_transport_report", + "world_preview.gd::var::_treated_patients", + "world_preview.gd::var::build_definition", + "world_preview.gd::var::build_rotation", + "world_preview.gd::var::excavation_mode", + "world_preview.gd::var::layer_mode", + "world_preview.gd::var::modal_input_blocked", + "world_preview.gd::var::pan_mode", + "world_preview.gd::var::pan_offset", + "world_preview.gd::var::selected_entity_id", + "world_preview.gd::var::show_grid", + "world_preview.gd::var::visual_theme", + "world_preview.gd::var::zoom_level" + ], + "p24_added_symbols": [ + "world_preview.gd::const::OperationFeedbackScript", + "world_preview.gd::func::_draw_unit_bubble", + "world_preview.gd::func::_guest_relationship", + "world_preview.gd::func::_staff_relationship", + "world_preview.gd::signal::staff_assignment_clicked", + "world_preview.gd::var::modal_input_blocked" + ], + "main_removed_vs_base_untouched_by_p24": [], + "required_count": 179, + "reconciled_symbol_count": 180, + "missing": [], + "duplicates": [], + "unexpected": [], + "repair4_added_with_reason": { + "world_preview.gd::func::_draw_actor_bubble": "P24 bubble binding re-expressed on main's P23 observation pipeline: bubbles are drawn per fitted actor row on the LifeOverlay canvas without mutating the observation report." + }, + "body_issues": [], + "p24_lines_reexpressed_with_reason": [ + { + "symbol": "world_preview.gd::func::_draw_actors", + "lines": [ + "_draw_pixel_actor(staff_draw_pos,key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))", + "_draw_unit_bubble(position, guest_bubble)", + "_draw_unit_bubble(staff_draw_pos, staff_bubble)", + "if guest_bubble != OperationFeedbackScript.BUBBLE_NONE:", + "if staff_bubble != OperationFeedbackScript.BUBBLE_NONE:", + "var guest_bubble := OperationFeedbackScript.resolve_guest_bubble(guest, rel_guest)", + "var rel_guest: Variant = _guest_relationship(guest.guest_id)", + "var rel_staff: Variant = _staff_relationship(staff.staff_id)", + "var staff_bubble := OperationFeedbackScript.resolve_staff_bubble(staff, rel_staff)", + "var staff_draw_pos: Vector2 = _queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts)))" + ], + "reason": "main replaced immediate _draw_pixel_actor with observed rows; P24's inline bubble calls are re-bound through _draw_actor_bubble(row, canvas) in main's _draw_actors(canvas)." + }, + { + "symbol": "world_preview.gd::func::_draw_unit_bubble", + "lines": [ + "draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color(\"ffffff\", 0.94))", + "draw_line(tri_p1, tri_p3, Color(\"303c40\", 0.85), 1.0)", + "draw_line(tri_p2, tri_p3, Color(\"303c40\", 0.85), 1.0)", + "draw_rect(rect, Color(\"303c40\", 0.85), false, 1.0)", + "draw_rect(rect, Color(\"ffffff\", 0.94))", + "draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), \"Zzz\", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color(\"2d68c4\"))", + "draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), \"♥\", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color(\"e84393\"))", + "draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), \"💢\", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color(\"d63031\"))", + "func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName) -> void:" + ], + "reason": "same bubble geometry/glyphs, but draw calls go to the LifeOverlay canvas (canvas.draw_*) and follow the fitted row origin/scale." + } + ], + "conflict_markers": false + }, + "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json": { + "kind": "json", + "main_entry_keys": [ + "schema_version", + "scope", + "rows", + "rows[tests/s4/test_original_relationship_model.gd]", + "rows[tests/s4/test_original_relationship_runtime.gd]", + "rows[tests/ui/test_selection_departure.gd]", + "rows[tests/business/test_business_closure.gd]", + "rows[tests/business/test_business_operations.gd]", + "rows[tests/business/test_required_kitchen.gd]", + "rows[tests/environment/test_cleaning_access.gd]", + "rows[tests/environment/test_facility_cleanliness.gd]", + "rows[tests/environment/test_runtime_cleanliness.gd]", + "rows[tests/environment/test_runtime_weather.gd]", + "rows[tests/environment/test_weather_recovery.gd]", + "rows[tests/environment/test_weather_timeline.gd]", + "rows[tests/mvp/test_amelia_donations.gd]", + "rows[tests/mvp/test_anri_floor_pricing.gd]", + "rows[tests/mvp/test_approved_free_unique_arrears.gd]", + "rows[tests/mvp/test_chapel_boundaries.gd]", + "rows[tests/mvp/test_chapel_service.gd]", + "rows[tests/mvp/test_fine_cleaning_effect.gd]", + "rows[tests/mvp/test_mvp_content_catalog.gd]", + "rows[tests/mvp/test_operations_coordinator.gd]", + "rows[tests/mvp/test_original_midnight_settlement.gd]", + "rows[tests/mvp/test_original_room_zones.gd]", + "rows[tests/mvp/test_pending_service_pricing_report.gd]", + "rows[tests/mvp/test_premium_room.gd]", + "rows[tests/mvp/test_premium_room_guest_flow.gd]", + "rows[tests/mvp/test_progression_service.gd]", + "rows[tests/mvp/test_runtime_content_progression.gd]", + "rows[tests/mvp/test_runtime_mana_depletion.gd]", + "rows[tests/mvp/test_runtime_operations_acceptance.gd]", + "rows[tests/mvp/test_runtime_operations_integration.gd]", + "rows[tests/mvp/test_runtime_upkeep.gd]", + "rows[tests/mvp/test_staff_unique_contract_report.gd]", + "rows[tests/mvp/test_unique_character_validation_policy.gd]", + "rows[tests/mvp/test_upkeep_construction_boundaries.gd]", + "rows[tests/mvp/test_upkeep_legacy_midnight.gd]", + "rows[tests/mvp/test_upkeep_loss_streak.gd]", + "rows[tests/performance/test_digest_performance.gd]", + "rows[tests/performance/test_facility_reachability.gd]", + "rows[tests/performance/test_lazy_digest.gd]", + "rows[tests/performance/test_mvp_manifest_cache.gd]", + "rows[tests/performance/test_path_membership.gd]", + "rows[tests/performance/test_perf_mvp_001.gd]", + "rows[tests/performance/test_snapshot_ownership.gd]", + "rows[tests/performance/test_system_optimization.gd]", + "rows[tests/performance/test_world_snapshot_cache.gd]", + "rows[tests/run_tests.gd]", + "rows[tests/s0/test_command_snapshot_key_order.gd]", + "rows[tests/s0/test_command_transaction.gd]", + "rows[tests/s0/test_definition_registry.gd]", + "rows[tests/s0/test_determinism.gd]", + "rows[tests/s0/test_domain_events.gd]", + "rows[tests/s0/test_runtime_integration.gd]", + "rows[tests/s0/test_stable_id_generator.gd]", + "rows[tests/s1/test_construction_service.gd]", + "rows[tests/s1/test_dimension_batch_save.gd]", + "rows[tests/s1/test_excavation_service.gd]", + "rows[tests/s1/test_facility_infrastructure_projector.gd]", + "rows[tests/s1/test_facility_resize_preview.gd]", + "rows[tests/s1/test_original_facility_dimensions.gd]", + "rows[tests/s1/test_path_congestion.gd]", + "rows[tests/s1/test_path_service.gd]", + "rows[tests/s1/test_path_snapshot_transit_guard.gd]", + "rows[tests/s1/test_route_subslot_path_adapter.gd]", + "rows[tests/s1/test_runtime_infrastructure_integration.gd]", + "rows[tests/s1/test_runtime_world_construction.gd]", + "rows[tests/s1/test_spatial_subslot_service.gd]", + "rows[tests/s1/test_weighted_resize_runtime.gd]", + "rows[tests/s1/test_world_service.gd]", + "rows[tests/s2/test_economy_service.gd]", + "rows[tests/s2/test_facility_pricing.gd]", + "rows[tests/s2/test_facility_service.gd]", + "rows[tests/s2/test_facility_upkeep_accrual.gd]", + "rows[tests/s2/test_facility_upkeep_settlement.gd]", + "rows[tests/s2/test_inventory_logistics.gd]", + "rows[tests/s2/test_kitchen_production.gd]", + "rows[tests/s2/test_ledger_backed_resources.gd]", + "rows[tests/s2/test_mana_network_service.gd]", + "rows[tests/s2/test_mana_power_projection.gd]", + "rows[tests/s2/test_mana_reserve_balance.gd]", + "rows[tests/s2/test_original_facility_parameters.gd]", + "rows[tests/s2/test_runtime_operations_cycle.gd]", + "rows[tests/s2/test_service_start_slots.gd]", + "rows[tests/s3/test_guest_observation_restore_order.gd]", + "rows[tests/s3/test_guest_service.gd]", + "rows[tests/s3/test_runtime_actor_cycle.gd]", + "rows[tests/s3/test_runtime_actor_hardening.gd]", + "rows[tests/s3/test_runtime_path_traffic.gd]", + "rows[tests/s3/test_runtime_staff_assignment.gd]", + "rows[tests/s3/test_staff_payroll_operations.gd]", + "rows[tests/s3/test_staff_service.gd]", + "rows[tests/s4/test_placement_risk_preview_service.gd]", + "rows[tests/s4/test_relationship_event_service.gd]", + "rows[tests/s4/test_runtime_relationship_events.gd]", + "rows[tests/s5/test_authority_service.gd]", + "rows[tests/s5/test_defense_service.gd]", + "rows[tests/s5/test_runtime_authority_cycle.gd]", + "rows[tests/s5/test_runtime_defense_autonomy.gd]", + "rows[tests/s5/test_runtime_defense_cycle.gd]", + "rows[tests/s5/test_runtime_event_defense.gd]", + "rows[tests/s6/test_performance_budget.gd]", + "rows[tests/s6/test_runtime_save_lifecycle.gd]", + "rows[tests/s6/test_runtime_snapshot_serialization.gd]", + "rows[tests/s6/test_save_operations.gd]", + "rows[tests/s6/test_save_preflight.gd]", + "rows[tests/s6/test_save_recovery_preservation.gd]", + "rows[tests/staff/test_dormitory_dimension_migration.gd]", + "rows[tests/staff/test_dormitory_shift_cycle.gd]", + "rows[tests/staff/test_rescue_elevator.gd]", + "rows[tests/staff/test_shift_rush.gd]", + "rows[tests/staff/test_staff_commute.gd]", + "rows[tests/staff/test_staff_dormitory.gd]", + "rows[tests/staff/test_staff_rescue.gd]", + "rows[tests/staff/test_staff_rest.gd]", + "rows[tests/staff/test_task_work_clock.gd]", + "rows[tests/supply/test_mimi_purchase_quote.gd]", + "rows[tests/supply/test_normal_auto_purchase_chain.gd]", + "rows[tests/supply/test_normal_supply_chain.gd]", + "rows[tests/supply/test_supply_auto_purchase.gd]", + "rows[tests/supply/test_supply_auto_purchase_boundaries.gd]", + "rows[tests/supply/test_supply_auto_purchase_destruction.gd]", + "rows[tests/supply/test_supply_logistics_bridge.gd]", + "rows[tests/supply/test_supply_management_service.gd]", + "rows[tests/supply/test_supply_save_migration.gd]", + "rows[tests/transport/test_elevator_service.gd]", + "rows[tests/transport/test_runtime_stairs.gd]", + "rows[tests/transport/test_runtime_transport.gd]", + "rows[tests/transport/test_runtime_transport_comparison.gd]", + "rows[tests/transport/test_transport_boundaries.gd]", + "rows[tests/transport/test_transport_comparison.gd]", + "rows[tests/transport/test_transport_density_migration.gd]", + "rows[tests/transport/test_transport_save.gd]", + "rows[tests/ui/test_actor_selection.gd]", + "rows[tests/ui/test_anri_recruitment.gd]", + "rows[tests/ui/test_approved_free_unique_arrears_ui.gd]", + "rows[tests/ui/test_business_management.gd]", + "rows[tests/ui/test_chapel_gameplay.gd]", + "rows[tests/ui/test_cleaning_gameplay.gd]", + "rows[tests/ui/test_dashboard_coalescing.gd]", + "rows[tests/ui/test_defense_management.gd]", + "rows[tests/ui/test_dimension_migration_ui.gd]", + "rows[tests/ui/test_elevator_management.gd]", + "rows[tests/ui/test_first_operating_day.gd]", + "rows[tests/ui/test_guest_decision_log.gd]", + "rows[tests/ui/test_guest_observation.gd]", + "rows[tests/ui/test_layer_presentation.gd]", + "rows[tests/ui/test_layer_skin.gd]", + "rows[tests/ui/test_main_audio_feedback.gd]", + "rows[tests/ui/test_main_dashboard.gd]", + "rows[tests/ui/test_opening_assignment.gd]", + "rows[tests/ui/test_original_build_pointer.gd]", + "rows[tests/ui/test_original_facility_controls.gd]", + "rows[tests/ui/test_pixel_tower.gd]", + "rows[tests/ui/test_rank_pointer.gd]", + "rows[tests/ui/test_room_presentation.gd]", + "rows[tests/ui/test_selection_autosave.gd]", + "rows[tests/ui/test_selection_lifecycle.gd]", + "rows[tests/ui/test_staff_management.gd]", + "rows[tests/ui/test_staff_rescue.gd]", + "rows[tests/ui/test_staff_rest.gd]", + "rows[tests/ui/test_stair_transport_details.gd]", + "rows[tests/ui/test_supply_auto_purchase_ui.gd]", + "rows[tests/ui/test_supply_management.gd]", + "rows[tests/ui/test_ui_audio_feedback.gd]", + "rows[tests/ui/test_ui_presentation_audio_contract.gd]", + "rows[tests/ui/test_upkeep_closed_ui.gd]", + "rows[tests/ui/test_upkeep_inspector.gd]", + "rows[tests/ui/test_world_tree_rank.gd]", + "rows[tests/world/test_elevator_topology.gd]", + "rows[tests/world/test_legacy_stairs.gd]", + "rows[tests/world/test_path_transit_density.gd]", + "rows[tests/world/test_path_vertical_policy.gd]", + "rows[tests/world/test_stair_infrastructure.gd]", + "rows[tests/ui/test_event_log_ui.gd]", + "rows[tests/ui/test_paired_bgm.gd]", + "rows[tests/mvp/test_tomari_floor_trouble.gd]", + "rows[tests/s1/test_p08_combined_migration.gd]", + "rows[tests/ui/test_p08_normal_placement_ops.gd]", + "rows[tests/transport/test_runtime_build_preview.gd]", + "rows[tests/ui/test_p23_life_observation.gd]", + "unknown_roster_suite_policy", + "roster_count_is_not_hardcoded", + "original155_authority_sha256", + "current164_review_sha256", + "audio3_manifest_sha256", + "production_changes", + "engine_runs", + "mimi_purchase_v2_candidate_binding", + "mimi_purchase_v2_candidate_binding/existing4_GD_source_changes", + "mimi_purchase_v2_candidate_binding/old169_suite_source_SHA_rewritten", + "mimi_purchase_v2_candidate_binding/source_schema_scope", + "mimi_purchase_v2_candidate_binding/actual_new_marker", + "mimi_purchase_v2_candidate_binding/actual_check_count", + "mimi_purchase_v2_candidate_binding/actual_new_UID3", + "mimi_purchase_v2_candidate_binding/adoption", + "mimi_purchase_v2_candidate_binding/source_r06_seal" + ], + "p24_side_entry_keys": [ + "schema_version", + "scope", + "rows", + "rows[tests/ui/test_operations_notification.gd]", + "rows[tests/ui/test_selection_departure.gd]", + "rows[tests/business/test_business_closure.gd]", + "rows[tests/business/test_business_operations.gd]", + "rows[tests/business/test_required_kitchen.gd]", + "rows[tests/environment/test_cleaning_access.gd]", + "rows[tests/environment/test_facility_cleanliness.gd]", + "rows[tests/environment/test_runtime_cleanliness.gd]", + "rows[tests/environment/test_runtime_weather.gd]", + "rows[tests/environment/test_weather_recovery.gd]", + "rows[tests/environment/test_weather_timeline.gd]", + "rows[tests/mvp/test_amelia_donations.gd]", + "rows[tests/mvp/test_anri_floor_pricing.gd]", + "rows[tests/mvp/test_approved_free_unique_arrears.gd]", + "rows[tests/mvp/test_chapel_boundaries.gd]", + "rows[tests/mvp/test_chapel_service.gd]", + "rows[tests/mvp/test_fine_cleaning_effect.gd]", + "rows[tests/mvp/test_mvp_content_catalog.gd]", + "rows[tests/mvp/test_operations_coordinator.gd]", + "rows[tests/mvp/test_original_midnight_settlement.gd]", + "rows[tests/mvp/test_original_room_zones.gd]", + "rows[tests/mvp/test_pending_service_pricing_report.gd]", + "rows[tests/mvp/test_premium_room.gd]", + "rows[tests/mvp/test_premium_room_guest_flow.gd]", + "rows[tests/mvp/test_progression_service.gd]", + "rows[tests/mvp/test_runtime_content_progression.gd]", + "rows[tests/mvp/test_runtime_mana_depletion.gd]", + "rows[tests/mvp/test_runtime_operations_acceptance.gd]", + "rows[tests/mvp/test_runtime_operations_integration.gd]", + "rows[tests/mvp/test_runtime_upkeep.gd]", + "rows[tests/mvp/test_staff_unique_contract_report.gd]", + "rows[tests/mvp/test_unique_character_validation_policy.gd]", + "rows[tests/mvp/test_upkeep_construction_boundaries.gd]", + "rows[tests/mvp/test_upkeep_legacy_midnight.gd]", + "rows[tests/mvp/test_upkeep_loss_streak.gd]", + "rows[tests/performance/test_digest_performance.gd]", + "rows[tests/performance/test_facility_reachability.gd]", + "rows[tests/performance/test_lazy_digest.gd]", + "rows[tests/performance/test_mvp_manifest_cache.gd]", + "rows[tests/performance/test_path_membership.gd]", + "rows[tests/performance/test_perf_mvp_001.gd]", + "rows[tests/performance/test_snapshot_ownership.gd]", + "rows[tests/performance/test_system_optimization.gd]", + "rows[tests/performance/test_world_snapshot_cache.gd]", + "rows[tests/run_tests.gd]", + "rows[tests/s0/test_command_snapshot_key_order.gd]", + "rows[tests/s0/test_command_transaction.gd]", + "rows[tests/s0/test_definition_registry.gd]", + "rows[tests/s0/test_determinism.gd]", + "rows[tests/s0/test_domain_events.gd]", + "rows[tests/s0/test_runtime_integration.gd]", + "rows[tests/s0/test_stable_id_generator.gd]", + "rows[tests/s1/test_construction_service.gd]", + "rows[tests/s1/test_dimension_batch_save.gd]", + "rows[tests/s1/test_excavation_service.gd]", + "rows[tests/s1/test_facility_infrastructure_projector.gd]", + "rows[tests/s1/test_facility_resize_preview.gd]", + "rows[tests/s1/test_original_facility_dimensions.gd]", + "rows[tests/s1/test_path_congestion.gd]", + "rows[tests/s1/test_path_service.gd]", + "rows[tests/s1/test_path_snapshot_transit_guard.gd]", + "rows[tests/s1/test_route_subslot_path_adapter.gd]", + "rows[tests/s1/test_runtime_infrastructure_integration.gd]", + "rows[tests/s1/test_runtime_world_construction.gd]", + "rows[tests/s1/test_spatial_subslot_service.gd]", + "rows[tests/s1/test_weighted_resize_runtime.gd]", + "rows[tests/s1/test_world_service.gd]", + "rows[tests/s2/test_economy_service.gd]", + "rows[tests/s2/test_facility_pricing.gd]", + "rows[tests/s2/test_facility_service.gd]", + "rows[tests/s2/test_facility_upkeep_accrual.gd]", + "rows[tests/s2/test_facility_upkeep_settlement.gd]", + "rows[tests/s2/test_inventory_logistics.gd]", + "rows[tests/s2/test_kitchen_production.gd]", + "rows[tests/s2/test_ledger_backed_resources.gd]", + "rows[tests/s2/test_mana_network_service.gd]", + "rows[tests/s2/test_mana_power_projection.gd]", + "rows[tests/s2/test_mana_reserve_balance.gd]", + "rows[tests/s2/test_original_facility_parameters.gd]", + "rows[tests/s2/test_runtime_operations_cycle.gd]", + "rows[tests/s2/test_service_start_slots.gd]", + "rows[tests/s3/test_guest_observation_restore_order.gd]", + "rows[tests/s3/test_guest_service.gd]", + "rows[tests/s3/test_runtime_actor_cycle.gd]", + "rows[tests/s3/test_runtime_actor_hardening.gd]", + "rows[tests/s3/test_runtime_path_traffic.gd]", + "rows[tests/s3/test_runtime_staff_assignment.gd]", + "rows[tests/s3/test_staff_payroll_operations.gd]", + "rows[tests/s3/test_staff_service.gd]", + "rows[tests/s4/test_placement_risk_preview_service.gd]", + "rows[tests/s4/test_relationship_event_service.gd]", + "rows[tests/s4/test_runtime_relationship_events.gd]", + "rows[tests/s5/test_authority_service.gd]", + "rows[tests/s5/test_defense_service.gd]", + "rows[tests/s5/test_runtime_authority_cycle.gd]", + "rows[tests/s5/test_runtime_defense_autonomy.gd]", + "rows[tests/s5/test_runtime_defense_cycle.gd]", + "rows[tests/s5/test_runtime_event_defense.gd]", + "rows[tests/s6/test_performance_budget.gd]", + "rows[tests/s6/test_runtime_save_lifecycle.gd]", + "rows[tests/s6/test_runtime_snapshot_serialization.gd]", + "rows[tests/s6/test_save_operations.gd]", + "rows[tests/s6/test_save_preflight.gd]", + "rows[tests/s6/test_save_recovery_preservation.gd]", + "rows[tests/staff/test_dormitory_dimension_migration.gd]", + "rows[tests/staff/test_dormitory_shift_cycle.gd]", + "rows[tests/staff/test_rescue_elevator.gd]", + "rows[tests/staff/test_shift_rush.gd]", + "rows[tests/staff/test_staff_commute.gd]", + "rows[tests/staff/test_staff_dormitory.gd]", + "rows[tests/staff/test_staff_rescue.gd]", + "rows[tests/staff/test_staff_rest.gd]", + "rows[tests/staff/test_task_work_clock.gd]", + "rows[tests/supply/test_mimi_purchase_quote.gd]", + "rows[tests/supply/test_normal_auto_purchase_chain.gd]", + "rows[tests/supply/test_normal_supply_chain.gd]", + "rows[tests/supply/test_supply_auto_purchase.gd]", + "rows[tests/supply/test_supply_auto_purchase_boundaries.gd]", + "rows[tests/supply/test_supply_auto_purchase_destruction.gd]", + "rows[tests/supply/test_supply_logistics_bridge.gd]", + "rows[tests/supply/test_supply_management_service.gd]", + "rows[tests/supply/test_supply_save_migration.gd]", + "rows[tests/transport/test_elevator_service.gd]", + "rows[tests/transport/test_runtime_stairs.gd]", + "rows[tests/transport/test_runtime_transport.gd]", + "rows[tests/transport/test_runtime_transport_comparison.gd]", + "rows[tests/transport/test_transport_boundaries.gd]", + "rows[tests/transport/test_transport_comparison.gd]", + "rows[tests/transport/test_transport_density_migration.gd]", + "rows[tests/transport/test_transport_save.gd]", + "rows[tests/ui/test_actor_selection.gd]", + "rows[tests/ui/test_anri_recruitment.gd]", + "rows[tests/ui/test_approved_free_unique_arrears_ui.gd]", + "rows[tests/ui/test_business_management.gd]", + "rows[tests/ui/test_chapel_gameplay.gd]", + "rows[tests/ui/test_cleaning_gameplay.gd]", + "rows[tests/ui/test_dashboard_coalescing.gd]", + "rows[tests/ui/test_defense_management.gd]", + "rows[tests/ui/test_dimension_migration_ui.gd]", + "rows[tests/ui/test_elevator_management.gd]", + "rows[tests/ui/test_first_operating_day.gd]", + "rows[tests/ui/test_guest_decision_log.gd]", + "rows[tests/ui/test_guest_observation.gd]", + "rows[tests/ui/test_layer_presentation.gd]", + "rows[tests/ui/test_layer_skin.gd]", + "rows[tests/ui/test_main_audio_feedback.gd]", + "rows[tests/ui/test_main_dashboard.gd]", + "rows[tests/ui/test_opening_assignment.gd]", + "rows[tests/ui/test_original_build_pointer.gd]", + "rows[tests/ui/test_original_facility_controls.gd]", + "rows[tests/ui/test_pixel_tower.gd]", + "rows[tests/ui/test_rank_pointer.gd]", + "rows[tests/ui/test_room_presentation.gd]", + "rows[tests/ui/test_selection_autosave.gd]", + "rows[tests/ui/test_selection_lifecycle.gd]", + "rows[tests/ui/test_staff_management.gd]", + "rows[tests/ui/test_staff_rescue.gd]", + "rows[tests/ui/test_staff_rest.gd]", + "rows[tests/ui/test_stair_transport_details.gd]", + "rows[tests/ui/test_supply_auto_purchase_ui.gd]", + "rows[tests/ui/test_supply_management.gd]", + "rows[tests/ui/test_ui_audio_feedback.gd]", + "rows[tests/ui/test_ui_presentation_audio_contract.gd]", + "rows[tests/ui/test_upkeep_closed_ui.gd]", + "rows[tests/ui/test_upkeep_inspector.gd]", + "rows[tests/ui/test_world_tree_rank.gd]", + "rows[tests/world/test_elevator_topology.gd]", + "rows[tests/world/test_legacy_stairs.gd]", + "rows[tests/world/test_path_transit_density.gd]", + "rows[tests/world/test_path_vertical_policy.gd]", + "rows[tests/world/test_stair_infrastructure.gd]", + "rows[tests/ui/test_event_log_ui.gd]", + "rows[tests/ui/test_paired_bgm.gd]", + "rows[tests/mvp/test_tomari_floor_trouble.gd]", + "unknown_roster_suite_policy", + "roster_count_is_not_hardcoded", + "original155_authority_sha256", + "current164_review_sha256", + "audio3_manifest_sha256", + "production_changes", + "engine_runs", + "mimi_purchase_v2_candidate_binding", + "mimi_purchase_v2_candidate_binding/existing4_GD_source_changes", + "mimi_purchase_v2_candidate_binding/old169_suite_source_SHA_rewritten", + "mimi_purchase_v2_candidate_binding/source_schema_scope", + "mimi_purchase_v2_candidate_binding/actual_new_marker", + "mimi_purchase_v2_candidate_binding/actual_check_count", + "mimi_purchase_v2_candidate_binding/actual_new_UID3", + "mimi_purchase_v2_candidate_binding/adoption", + "mimi_purchase_v2_candidate_binding/source_r06_seal" + ], + "p24_added_entry_keys": [ + "rows[tests/ui/test_operations_notification.gd]" + ], + "missing": [], + "duplicates": [], + "equals_main_plus_p24_only_additions_in_order": true, + "main_entries_value_identical": true + }, + "tools/regression/suite-policy.json": { + "kind": "json", + "main_entry_keys": [ + "schema_version", + "suite_policies", + "suite_policies/tests/s4/test_original_relationship_model.gd", + "suite_policies/tests/s4/test_original_relationship_runtime.gd", + "suite_policies/tests/ui/test_selection_departure.gd", + "suite_policies/tests/ui/test_anri_recruitment.gd", + "suite_policies/tests/ui/test_event_log_ui.gd", + "suite_policies/tests/mvp/test_amelia_donations.gd", + "suite_policies/tests/mvp/test_fine_cleaning_effect.gd", + "suite_policies/tests/supply/test_normal_auto_purchase_chain.gd", + "suite_policies/tests/supply/test_normal_supply_chain.gd", + "suite_policies/tests/ui/test_approved_free_unique_arrears_ui.gd", + "suite_policies/tests/ui/test_business_management.gd", + "suite_policies/tests/ui/test_chapel_gameplay.gd", + "suite_policies/tests/ui/test_cleaning_gameplay.gd", + "suite_policies/tests/ui/test_dashboard_coalescing.gd", + "suite_policies/tests/ui/test_defense_management.gd", + "suite_policies/tests/ui/test_dimension_migration_ui.gd", + "suite_policies/tests/ui/test_elevator_management.gd", + "suite_policies/tests/ui/test_first_operating_day.gd", + "suite_policies/tests/ui/test_guest_decision_log.gd", + "suite_policies/tests/ui/test_guest_observation.gd", + "suite_policies/tests/ui/test_layer_skin.gd", + "suite_policies/tests/ui/test_main_audio_feedback.gd", + "suite_policies/tests/ui/test_main_dashboard.gd", + "suite_policies/tests/ui/test_original_build_pointer.gd", + "suite_policies/tests/ui/test_original_facility_controls.gd", + "suite_policies/tests/ui/test_rank_pointer.gd", + "suite_policies/tests/ui/test_selection_autosave.gd", + "suite_policies/tests/ui/test_selection_lifecycle.gd", + "suite_policies/tests/ui/test_staff_management.gd", + "suite_policies/tests/ui/test_staff_rescue.gd", + "suite_policies/tests/ui/test_staff_rest.gd", + "suite_policies/tests/ui/test_stair_transport_details.gd", + "suite_policies/tests/ui/test_supply_auto_purchase_ui.gd", + "suite_policies/tests/ui/test_supply_management.gd", + "suite_policies/tests/ui/test_ui_audio_feedback.gd", + "suite_policies/tests/ui/test_ui_presentation_audio_contract.gd", + "suite_policies/tests/ui/test_upkeep_closed_ui.gd", + "suite_policies/tests/ui/test_upkeep_inspector.gd", + "suite_policies/tests/ui/test_world_tree_rank.gd", + "suite_policies/tests/mvp/test_runtime_upkeep.gd", + "suite_policies/tests/transport/test_runtime_transport_comparison.gd", + "suite_policies/tests/transport/test_transport_comparison.gd", + "suite_policies/tests/supply/test_mimi_purchase_quote.gd", + "suite_policies/tests/transport/test_runtime_transport.gd", + "suite_policies/tests/ui/test_actor_selection.gd", + "suite_policies/tests/mvp/test_pending_service_pricing_report.gd", + "suite_policies/tests/ui/test_p08_normal_placement_ops.gd", + "outside_routes", + "outside_routes/tests/supply/test_normal_auto_purchase_chain.gd", + "outside_routes/tests/supply/test_normal_supply_chain.gd", + "outside_routes/tests/ui/test_first_operating_day.gd", + "outside_routes/tests/ui/test_selection_autosave.gd", + "fixed_generated_report_routes", + "fixed_generated_report_routes/tests/mvp/test_runtime_upkeep.gd", + "fixed_generated_report_routes/tests/transport/test_runtime_transport_comparison.gd", + "fixed_generated_report_routes/tests/transport/test_transport_comparison.gd", + "scope" + ], + "p24_side_entry_keys": [ + "schema_version", + "suite_policies", + "suite_policies/tests/ui/test_operations_notification.gd", + "suite_policies/tests/ui/test_selection_departure.gd", + "suite_policies/tests/ui/test_anri_recruitment.gd", + "suite_policies/tests/ui/test_event_log_ui.gd", + "suite_policies/tests/mvp/test_amelia_donations.gd", + "suite_policies/tests/mvp/test_fine_cleaning_effect.gd", + "suite_policies/tests/supply/test_normal_auto_purchase_chain.gd", + "suite_policies/tests/supply/test_normal_supply_chain.gd", + "suite_policies/tests/ui/test_approved_free_unique_arrears_ui.gd", + "suite_policies/tests/ui/test_business_management.gd", + "suite_policies/tests/ui/test_chapel_gameplay.gd", + "suite_policies/tests/ui/test_cleaning_gameplay.gd", + "suite_policies/tests/ui/test_dashboard_coalescing.gd", + "suite_policies/tests/ui/test_defense_management.gd", + "suite_policies/tests/ui/test_dimension_migration_ui.gd", + "suite_policies/tests/ui/test_elevator_management.gd", + "suite_policies/tests/ui/test_first_operating_day.gd", + "suite_policies/tests/ui/test_guest_decision_log.gd", + "suite_policies/tests/ui/test_guest_observation.gd", + "suite_policies/tests/ui/test_layer_skin.gd", + "suite_policies/tests/ui/test_main_audio_feedback.gd", + "suite_policies/tests/ui/test_main_dashboard.gd", + "suite_policies/tests/ui/test_original_build_pointer.gd", + "suite_policies/tests/ui/test_original_facility_controls.gd", + "suite_policies/tests/ui/test_rank_pointer.gd", + "suite_policies/tests/ui/test_selection_autosave.gd", + "suite_policies/tests/ui/test_selection_lifecycle.gd", + "suite_policies/tests/ui/test_staff_management.gd", + "suite_policies/tests/ui/test_staff_rescue.gd", + "suite_policies/tests/ui/test_staff_rest.gd", + "suite_policies/tests/ui/test_stair_transport_details.gd", + "suite_policies/tests/ui/test_supply_auto_purchase_ui.gd", + "suite_policies/tests/ui/test_supply_management.gd", + "suite_policies/tests/ui/test_ui_audio_feedback.gd", + "suite_policies/tests/ui/test_ui_presentation_audio_contract.gd", + "suite_policies/tests/ui/test_upkeep_closed_ui.gd", + "suite_policies/tests/ui/test_upkeep_inspector.gd", + "suite_policies/tests/ui/test_world_tree_rank.gd", + "suite_policies/tests/mvp/test_runtime_upkeep.gd", + "suite_policies/tests/transport/test_runtime_transport_comparison.gd", + "suite_policies/tests/transport/test_transport_comparison.gd", + "suite_policies/tests/supply/test_mimi_purchase_quote.gd", + "suite_policies/tests/transport/test_runtime_transport.gd", + "suite_policies/tests/ui/test_actor_selection.gd", + "suite_policies/tests/mvp/test_pending_service_pricing_report.gd", + "outside_routes", + "outside_routes/tests/supply/test_normal_auto_purchase_chain.gd", + "outside_routes/tests/supply/test_normal_supply_chain.gd", + "outside_routes/tests/ui/test_first_operating_day.gd", + "outside_routes/tests/ui/test_selection_autosave.gd", + "fixed_generated_report_routes", + "fixed_generated_report_routes/tests/mvp/test_runtime_upkeep.gd", + "fixed_generated_report_routes/tests/transport/test_runtime_transport_comparison.gd", + "fixed_generated_report_routes/tests/transport/test_transport_comparison.gd", + "scope" + ], + "p24_added_entry_keys": [ + "suite_policies/tests/ui/test_operations_notification.gd" + ], + "missing": [], + "duplicates": [], + "equals_main_plus_p24_only_additions_in_order": true, + "main_entries_value_identical": true + } + }, + "bubble_relation_schema": { + "checks": [ + { + "check": "StaffRuntime.State members ['BREAKING', 'FLEEING', 'INCAPACITATED', 'SLEEPING', 'STRIKE'] exist on main", + "ok": true + }, + { + "check": "GuestRuntime.State members ['ABANDONING', 'FLEEING', 'INCAPACITATED', 'INTERRUPTED', 'QUEUING'] exist on main", + "ok": true + }, + { + "check": "RelationshipState.RouteLock members ['EXCLUSIVE_LOCKED', 'EXCLUSIVE_PENDING'] exist on main", + "ok": true + }, + { + "check": "RelationshipState fields ['metrics', 'route_lock', 'shield_current'] exist on main", + "ok": true + }, + { + "check": "relationship metric 'affection' is a main METRIC_ID", + "ok": true + }, + { + "check": "GuestRuntime.patience_remaining exists on main", + "ok": true + }, + { + "check": "world_preview relation lookup fields ['actor_a_id', 'actor_b_id'] exist on main", + "ok": true + }, + { + "check": "main relationship_service.get_relationships() exists", + "ok": true + }, + { + "check": "main staff_service.get_staff() exists", + "ok": true + }, + { + "check": "main guest_service.get_guest() exists", + "ok": true + }, + { + "check": "worker scripts/relationship/relationship_state.gd identical to main (schema not diverged)", + "ok": true + }, + { + "check": "worker scripts/staff/staff_runtime.gd identical to main (schema not diverged)", + "ok": true + }, + { + "check": "worker scripts/guest/guest_runtime.gd identical to main (schema not diverged)", + "ok": true + } + ], + "relationship_state_vars_main": [ + "action_counts", + "actor_a_id", + "actor_b_id", + "attack_day", + "crossed_thresholds", + "daily_attack_count", + "daily_metric_deltas", + "flags", + "last_event_at", + "metric_delta_day", + "metrics", + "relationship_id", + "route_id", + "route_lock", + "shield_current", + "shield_max" + ] + }, + "root_composition_dependencies": { + "note": "Reconciled files keep main snapshot text; these symbols/resources resolve only on the composed current-main tree. Not stubbed, not copied (outside allowed_paths).", + "items": [ + { + "file": "scenes/main/main.gd", + "resource": "scenes/main/world_preview.gd", + "kind": "changed-on-main" + }, + { + "file": "scenes/main/main.gd", + "resource": "scripts/ui/event_log_dialog.gd", + "kind": "changed-on-main" + }, + { + "file": "scenes/main/main.gd", + "resource": "scripts/ui/manual_save_slots_dialog.gd", + "kind": "changed-on-main" + }, + { + "file": "scenes/main/world_preview.gd", + "resource": "scripts/ui/room_presentation.gd", + "kind": "changed-on-main" + } + ] + }, + "status": "SOURCE-READY", + "runtime_status": "NOTRUN", + "scope": "Static source preservation only. Not Godot/import/native/OS/full-roster proof and not acceptance. Root owns current-main composition and all execution.", + "failures": [] +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py new file mode 100644 index 0000000..403c72f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python3 +"""P24-02 repair4 static overlap/preservation check (SOURCE-READY evidence only). + +This is NOT runtime, import, native, OS or acceptance proof. It only proves, +statically and mechanically, that the four reconciled files contain: + + * every scope-qualified GDScript symbol (class_name / class / func / signal / + const / enum / class-body var) of the pinned current-main snapshot, and + * every symbol the P24 worker diff (base 1a5abc6 -> 9641ed5) added, + +each exactly once in its own scope (so a legitimate ``_init`` or ``_draw`` in a +different inner class is never reported as a duplicate), that main's body lines +and P24's added body lines survived, that both JSON files equal the exact +3-way expectation "main snapshot + P24-only additions" (values and order), and +that UI actor bubbles only read fields that exist in the current relation / +Staff / Guest schema on main. + +Usage (static, no Godot): + python3 docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/check_repair4_preservation.py \ + [--write-evidence] +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import subprocess +import sys +from collections import Counter +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[5] +REF_DIR = HERE.parent / "root-current-main-reference-20261017" +MANIFEST = REF_DIR / "REFERENCE_MANIFEST.json" +EVIDENCE = HERE / "SOURCE_READY_PRESERVATION_RESULT.json" + +BASE = "1a5abc65163097d4b36a060d68f58c92093eb4e9" +P24 = "9641ed52def2ff8e5b0222449a4a83a1191e1d37" +MAIN = "51e8657bfbe24856186cc6530358c5459cf909b6" + +# Explicit, reviewed repair4 deviations. Anything not listed here fails. +REPAIR4_ADDED_SYMBOLS = { + "scenes/main/world_preview.gd": { + "world_preview.gd::func::_draw_actor_bubble": + "P24 bubble binding re-expressed on main's P23 observation pipeline: " + "bubbles are drawn per fitted actor row on the LifeOverlay canvas " + "without mutating the observation report.", + }, +} +# P24-added body lines intentionally re-expressed (not dropped) in repair4. +REPAIR4_REEXPRESSED_P24_LINES = { + ("scenes/main/world_preview.gd", "world_preview.gd::func::_draw_actors"): + "main replaced immediate _draw_pixel_actor with observed rows; P24's inline " + "bubble calls are re-bound through _draw_actor_bubble(row, canvas) in " + "main's _draw_actors(canvas).", + ("scenes/main/world_preview.gd", "world_preview.gd::func::_draw_unit_bubble"): + "same bubble geometry/glyphs, but draw calls go to the LifeOverlay canvas " + "(canvas.draw_*) and follow the fitted row origin/scale.", +} + +failures: list[str] = [] + + +def fail(msg: str) -> None: + failures.append(msg) + + +def git_show(rev: str, path: str) -> str | None: + try: + return subprocess.run(["git", "show", f"{rev}:{path}"], cwd=ROOT, check=True, + capture_output=True).stdout.decode("utf-8") + except subprocess.CalledProcessError: + return None + + +def git_blob(data: bytes) -> str: + return hashlib.sha1(b"blob %d\0" % len(data) + data).hexdigest() + + +# --------------------------------------------------------------------------- GDScript +DECL = [ + ("class_name", re.compile(r"^class_name\s+(\w+)")), + ("class", re.compile(r"^class\s+(\w+)\b")), + ("func", re.compile(r"^(?:static\s+)?func\s+(\w+)\s*\(")), + ("signal", re.compile(r"^signal\s+(\w+)")), + ("const", re.compile(r"^const\s+(\w+)")), + ("enum", re.compile(r"^enum\s+(\w+)")), + ("var", re.compile(r"^(?:@\w+(?:\([^)]*\))?\s+)*(?:static\s+)?var\s+(\w+)")), +] + + +def indent_of(line: str) -> int: + n = 0 + for ch in line: + if ch == "\t": + n += 1 + elif ch == " ": + n += 0.25 + else: + break + return int(n) + + +def gd_symbols(text: str, fname: str) -> list[dict]: + """Scope-qualified declarations with their body line ranges.""" + lines = text.split("\n") + stack: list[tuple[int, str]] = [] # (class indent, qualified scope) + out: list[dict] = [] + in_triple = False + for i, raw in enumerate(lines): + stripped = raw.strip() + if raw.count('"""') % 2 == 1: + in_triple = not in_triple + continue + if in_triple or not stripped or stripped.startswith("#"): + continue + ind = indent_of(raw) + while stack and ind <= stack[-1][0]: + stack.pop() + scope = stack[-1][1] if stack else fname + body_indent = stack[-1][0] + 1 if stack else 0 + if ind != body_indent: + continue + for kind, rx in DECL: + m = rx.match(stripped) + if m: + out.append({"id": f"{scope}::{kind}::{m[1]}", "line": i, "indent": ind, "kind": kind}) + if kind == "class": + stack.append((ind, f"{scope}.{m[1]}")) + break + # body = following lines until next line at indent <= decl indent (non-blank) + for sym in out: + start, ind = sym["line"], sym["indent"] + end = start + 1 + while end < len(lines): + s = lines[end] + if s.strip() and not s.strip().startswith("#") and indent_of(s) <= ind: + break + end += 1 + sym["body"] = [ln.strip() for ln in lines[start:end] if ln.strip()] + return out + + +def by_id(syms: list[dict]) -> dict[str, dict]: + return {s["id"]: s for s in syms} + + +def check_gd(path: str, main_text: str, report: dict) -> None: + fname = Path(path).name + base_t, p24_t = git_show(BASE, path), git_show(P24, path) + recon_t = (ROOT / path).read_text(encoding="utf-8") + if base_t is None or p24_t is None: + fail(f"{path}: pinned base/P24 revision unavailable") + return + base, p24, main, recon = (gd_symbols(t, fname) for t in (base_t, p24_t, main_text, recon_t)) + B, P, M, R = map(by_id, (base, p24, main, recon)) + counts = Counter(s["id"] for s in recon) + dups = sorted(k for k, c in counts.items() if c > 1) + p24_added = sorted(set(P) - set(B)) + required = sorted(set(M) | set(p24_added)) + missing = sorted(k for k in required if k not in R) + allowed_new = REPAIR4_ADDED_SYMBOLS.get(path, {}) + unexpected = sorted(k for k in R if k not in required and k not in allowed_new) + main_removed = sorted(set(B) - set(M)) + lost_p24_on_removed = [k for k in main_removed if k in P and P[k]["body"] != B[k]["body"]] + for k in dups: + fail(f"{path}: duplicate definition {k}") + for k in missing: + fail(f"{path}: missing symbol {k}") + for k in unexpected: + fail(f"{path}: unexpected symbol {k}") + for k in lost_p24_on_removed: + fail(f"{path}: P24 modified {k} but main removed it") + # Body preservation (set semantics on stripped lines). + body_issues = [] + for k, m in M.items(): + if k not in R: + continue + b_lines = set(B[k]["body"]) if k in B else set() + p_lines = set(P[k]["body"]) if k in P else b_lines + p24_removed = b_lines - p_lines + need = set(m["body"]) - p24_removed + lost = sorted(need - set(R[k]["body"])) + if lost: + body_issues.append({"symbol": k, "side": "main", "lost_lines": lost}) + fail(f"{path}: main body lines lost in {k}: {len(lost)}") + reexpressed = [] + for k, p in P.items(): + b_lines = set(B[k]["body"]) if k in B else set() + added = set(p["body"]) - b_lines + if not added or k not in R: + continue + lost = sorted(added - set(R[k]["body"])) + if not lost: + continue + reason = REPAIR4_REEXPRESSED_P24_LINES.get((path, k)) + if reason: + reexpressed.append({"symbol": k, "lines": lost, "reason": reason}) + else: + body_issues.append({"symbol": k, "side": "p24", "lost_lines": lost}) + fail(f"{path}: P24 added body lines lost in {k}: {len(lost)}") + report[path] = { + "kind": "gdscript", + "main_symbols": sorted(M), + "p24_side_symbols": sorted(P), + "p24_added_symbols": p24_added, + "main_removed_vs_base_untouched_by_p24": [k for k in main_removed if k not in lost_p24_on_removed], + "required_count": len(required), + "reconciled_symbol_count": len(recon), + "missing": missing, + "duplicates": dups, + "unexpected": unexpected, + "repair4_added_with_reason": {k: v for k, v in allowed_new.items() if k in R}, + "body_issues": body_issues, + "p24_lines_reexpressed_with_reason": reexpressed, + "conflict_markers": bool(re.search(r"^(<<<<<<<|>>>>>>>|\|\|\|\|\|\|\|) ", recon_t, re.M)), + } + if report[path]["conflict_markers"]: + fail(f"{path}: conflict markers present") + + +# --------------------------------------------------------------------------- JSON +def load_json_strict(text: str, label: str): + def hook(pairs): + keys = [k for k, _ in pairs] + d = Counter(keys) + for k, c in d.items(): + if c > 1: + fail(f"{label}: duplicate JSON key {k!r}") + return dict(pairs) + return json.loads(text, object_pairs_hook=hook) + + +def row_key(row): + return row.get("test") if isinstance(row, dict) else None + + +def expect3(base, main, p24, path="$"): + """3-way expectation: main value, plus P24-only changes where main kept base.""" + if isinstance(main, dict) and isinstance(p24, dict) and isinstance(base, dict): + out = {} + for k in main: + if k in p24 and k in base: + out[k] = expect3(base[k], main[k], p24[k], f"{path}.{k}") + elif k in p24 and k not in base and p24[k] != main[k]: + fail(f"{path}.{k}: added by both main and P24 with different values") + out[k] = main[k] + else: + out[k] = main[k] + for k in p24: + if k not in base and k not in main: + out[k] = p24[k] + elif k in base and k not in main and p24[k] != base[k]: + fail(f"{path}.{k}: P24 changed a key main removed") + return out + if (isinstance(main, list) and isinstance(p24, list) and isinstance(base, list) + and all(row_key(r) for r in main + p24 + base)): + bk = {row_key(r): r for r in base} + pk = {row_key(r): r for r in p24} + out = [] + for r in main: + k = row_key(r) + out.append(expect3(bk[k], r, pk[k], f"{path}[{k}]") if k in bk and k in pk else r) + main_keys = {row_key(r) for r in main} + for r in p24: + k = row_key(r) + if k not in bk and k not in main_keys: + out.append(r) + return out + if p24 != base and main == base: + return p24 + if p24 != base and main != base and p24 != main: + fail(f"{path}: both main and P24 changed scalar value") + return main + + +def ordered_equal(a, b): + if isinstance(a, dict) and isinstance(b, dict): + return list(a) == list(b) and all(ordered_equal(a[k], b[k]) for k in a) + if isinstance(a, list) and isinstance(b, list): + return len(a) == len(b) and all(ordered_equal(x, y) for x, y in zip(a, b)) + return type(a) is type(b) and a == b + + +def json_entry_keys(obj) -> list[str]: + keys = [] + for k, v in obj.items(): + keys.append(k) + if isinstance(v, dict): + keys += [f"{k}/{s}" for s in v] + elif isinstance(v, list) and v and all(row_key(r) for r in v): + keys += [f"{k}[{row_key(r)}]" for r in v] + return keys + + +def check_json(path: str, main_text: str, report: dict) -> None: + base_t, p24_t = git_show(BASE, path), git_show(P24, path) + recon_t = (ROOT / path).read_text(encoding="utf-8") + if base_t is None or p24_t is None: + fail(f"{path}: pinned base/P24 revision unavailable") + return + base, main, p24 = (load_json_strict(t, f"{path}@{r}") for t, r in + ((base_t, "base"), (main_text, "main"), (p24_t, "p24"))) + recon = load_json_strict(recon_t, f"{path}@reconciled") + expected = expect3(base, main, p24) + ok = ordered_equal(recon, expected) + if not ok: + fail(f"{path}: reconciled JSON != main snapshot + P24-only additions (value/order)") + mk, pk, bk, rk = map(json_entry_keys, (main, p24, base, recon)) + rc = Counter(rk) + p24_added = [k for k in pk if k not in bk] + missing = [k for k in list(dict.fromkeys(mk + p24_added)) if k not in rc] + dups = sorted(k for k, c in rc.items() if c > 1) + for k in missing: + fail(f"{path}: missing JSON entry {k}") + for k in dups: + fail(f"{path}: duplicate JSON entry {k}") + report[path] = { + "kind": "json", + "main_entry_keys": mk, + "p24_side_entry_keys": pk, + "p24_added_entry_keys": p24_added, + "missing": missing, + "duplicates": dups, + "equals_main_plus_p24_only_additions_in_order": ok, + "main_entries_value_identical": all( + ordered_equal(recon.get(k), main[k]) for k in main if not isinstance(main[k], (dict, list))), + } + + +# --------------------------------------------------------------------------- schema +def enum_members(text: str, enum: str) -> set[str]: + m = re.search(r"enum\s+" + enum + r"\s*\{(.*?)\}", text, re.S) + return set(re.findall(r"\b([A-Z_][A-Z0-9_]*)\b", m[1])) if m else set() + + +def top_vars(text: str) -> set[str]: + return set(re.findall(r"^var\s+(\w+)", text, re.M)) + + +def check_bubble_schema(report: dict) -> None: + fb = (ROOT / "scripts/ui/operation_feedback.gd").read_text(encoding="utf-8") + wp = (ROOT / "scenes/main/world_preview.gd").read_text(encoding="utf-8") + srcs = { + "relationship_state": "scripts/relationship/relationship_state.gd", + "staff_runtime": "scripts/staff/staff_runtime.gd", + "guest_runtime": "scripts/guest/guest_runtime.gd", + "relationship_service": "scripts/relationship/relationship_service.gd", + "staff_service": "scripts/staff/staff_service.gd", + "guest_service": "scripts/guest/guest_service.gd", + } + main_src = {k: git_show(MAIN, p) for k, p in srcs.items()} + if any(v is None for v in main_src.values()): + fail("schema: main revision source unavailable") + return + rel_vars = top_vars(main_src["relationship_state"]) + metric_ids = set(re.findall(r'&"(\w+)"', re.search(r"const METRIC_IDS[^=]*=\s*\[(.*?)\]", main_src["relationship_state"], re.S)[1])) + checks = [] + + def need(label, cond): + checks.append({"check": label, "ok": bool(cond)}) + if not cond: + fail(f"schema: {label}") + + staff_used = set(re.findall(r"StaffRuntime\.State\.(\w+)", fb)) + guest_used = set(re.findall(r"GuestRuntime\.State\.(\w+)", fb)) + lock_used = set(re.findall(r"RelationshipState\.RouteLock\.(\w+)", fb)) + rel_fields = set(re.findall(r"relationship\.(\w+)", fb)) - {"get"} + rel_fields |= set(re.findall(r'relationship\.get\(&"(\w+)"', fb)) + need(f"StaffRuntime.State members {sorted(staff_used)} exist on main", + staff_used and staff_used <= enum_members(main_src["staff_runtime"], "State")) + need(f"GuestRuntime.State members {sorted(guest_used)} exist on main", + guest_used and guest_used <= enum_members(main_src["guest_runtime"], "State")) + need(f"RelationshipState.RouteLock members {sorted(lock_used)} exist on main", + lock_used and lock_used <= enum_members(main_src["relationship_state"], "RouteLock")) + need(f"RelationshipState fields {sorted(rel_fields)} exist on main", rel_fields and rel_fields <= rel_vars) + need("relationship metric 'affection' is a main METRIC_ID", "affection" in metric_ids) + need("GuestRuntime.patience_remaining exists on main", "patience_remaining" in top_vars(main_src["guest_runtime"])) + wp_rel = set(re.findall(r"rel_val\.(\w+)", wp)) + need(f"world_preview relation lookup fields {sorted(wp_rel)} exist on main", wp_rel and wp_rel <= rel_vars) + for svc, meth in (("relationship_service", "get_relationships"), ("staff_service", "get_staff"), + ("guest_service", "get_guest")): + need(f"main {svc}.{meth}() exists", re.search(r"^func\s+" + meth + r"\s*\(", main_src[svc], re.M)) + for k in ("relationship_state", "staff_runtime", "guest_runtime"): + wt = (ROOT / srcs[k]).read_text(encoding="utf-8") + need(f"worker {srcs[k]} identical to main (schema not diverged)", wt == main_src[k]) + report["bubble_relation_schema"] = {"checks": checks, "relationship_state_vars_main": sorted(rel_vars)} + + +# --------------------------------------------------------------------------- deps +def root_composition_dependencies(report: dict) -> None: + ls_main = subprocess.run(["git", "ls-tree", "-r", "--name-only", MAIN], cwd=ROOT, + capture_output=True, text=True).stdout.split("\n") + main_files = set(filter(None, ls_main)) + main_classes: dict[str, str] = {} + grep = subprocess.run(["git", "grep", "-h", "-E", r"^class_name\s+\w+", MAIN, "--", "*.gd"], cwd=ROOT, + capture_output=True, text=True).stdout + for m in re.finditer(r"class_name\s+(\w+)", grep): + main_classes[m[1]] = "main" + worker_classes = set() + for p in ROOT.rglob("*.gd"): + if ".godot" in p.parts: + continue + for m in re.finditer(r"^class_name\s+(\w+)", p.read_text(encoding="utf-8", errors="ignore"), re.M): + worker_classes.add(m[1]) + deps = [] + for path in ("scenes/main/main.gd", "scenes/main/world_preview.gd"): + text = (ROOT / path).read_text(encoding="utf-8") + for res in sorted(set(re.findall(r'(?:pre)?load\("res://([^"]+)"\)', text))): + in_worker = (ROOT / res).exists() + in_main = res in main_files + changed = None + if in_worker and in_main: + mt = git_show(MAIN, res) + changed = mt is not None and mt != (ROOT / res).read_text(encoding="utf-8", errors="ignore") + if (in_main and not in_worker) or changed: + deps.append({"file": path, "resource": res, "kind": "main-only" if not in_worker else "changed-on-main"}) + words = set(re.findall(r"\b([A-Z]\w+)\b", text)) + for cls in sorted(words & (set(main_classes) - worker_classes)): + deps.append({"file": path, "class_name": cls, "kind": "main-only class_name"}) + report["root_composition_dependencies"] = { + "note": "Reconciled files keep main snapshot text; these symbols/resources resolve only on the " + "composed current-main tree. Not stubbed, not copied (outside allowed_paths).", + "items": deps, + } + + +# --------------------------------------------------------------------------- main +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--write-evidence", action="store_true") + args = ap.parse_args() + manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) + if (manifest["submitted"], manifest["main_reference"], manifest["registered_base_unchanged"]) != (P24, MAIN, BASE): + fail("manifest revisions differ from pinned P24/main/base") + report: dict = {"pins": []} + files: dict = {} + for entry in manifest["all4_current_main_files"]: + data = (ROOT / entry["reference_path"]).read_bytes() + sha, blob = hashlib.sha256(data).hexdigest(), git_blob(data) + ok = sha == entry["sha256"] and blob == entry["main_blob"] + live = git_show(MAIN, entry["path"]) + same_as_main_commit = live is not None and live.encode("utf-8") == data + report["pins"].append({"path": entry["path"], "sha256": sha, "blob": blob, "pin_ok": ok, + "snapshot_equals_main_commit_blob": same_as_main_commit}) + if not ok: + fail(f"pin mismatch: {entry['path']}") + files[entry["path"]] = data.decode("utf-8") + results: dict = {} + for path, text in files.items(): + (check_gd if path.endswith(".gd") else check_json)(path, text, results) + report["files"] = results + check_bubble_schema(report) + root_composition_dependencies(report) + report["status"] = "SOURCE-READY" if not failures else "SOURCE-NOT-READY" + report["runtime_status"] = "NOTRUN" + report["scope"] = ("Static source preservation only. Not Godot/import/native/OS/full-roster proof and " + "not acceptance. Root owns current-main composition and all execution.") + report["failures"] = failures + EVIDENCE.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + summary = {p: {k: (len(v) if isinstance(v, list) else v) for k, v in r.items() + if k in ("missing", "duplicates", "unexpected", "required_count", "reconciled_symbol_count", + "equals_main_plus_p24_only_additions_in_order", "body_issues")} + for p, r in results.items()} + print(json.dumps({"status": report["status"], "runtime_status": "NOTRUN", "files": summary, + "schema_checks_ok": all(c["ok"] for c in report.get("bubble_relation_schema", {}).get("checks", [])), + "root_composition_dependencies": len(report["root_composition_dependencies"]["items"]), + "failures": failures}, ensure_ascii=False, indent=2)) + return 0 if not failures else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py new file mode 100644 index 0000000..4beab0f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/repair4/test_checker_roster_head.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""Isolated checker tests; no acceptance index, engine, or full test execution.""" +import copy +import importlib.util +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[6] +spec = importlib.util.spec_from_file_location('checker', ROOT / 'tools/check_p24_operations_evidence.py') +checker = importlib.util.module_from_spec(spec) +spec.loader.exec_module(checker) + + +class CheckerTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + (self.root / 'tools').mkdir() + shutil.copyfile(ROOT / 'tools/run_all_tests.py', self.root / 'tools/run_all_tests.py') + (self.root / 'tests').mkdir() + for name in ('run_tests.gd', 'test_alpha.gd', 'test_beta.gd'): + (self.root / 'tests' / name).write_text('# isolated fixture ' + name) + self.rows = [dict(test=p, failures=0, passed=True, + source_sha256=checker.compute_sha256(self.root / p)) + for p in checker.get_required_suite_roster(self.root)] + + def validate(self, rows, **counts): + data = dict(test_suite='tests/ui/test_operations_notification.gd', failures=0, checks=1, + full_roster=dict(results=rows, **counts)) + previous = Path.cwd() + try: + os.chdir(self.root) + return checker._validate_condition_artifact_content( + 'final_validation_review', data, 'json', self.root, {}, {}) + finally: + os.chdir(previous) + + def test_positive_roster(self): + for counts in ({}, dict(total_suites=3, suites_count=3, failures=0)): + self.assertEqual(self.validate(self.rows, **counts), (True, '')) + + def test_1_invented_replacement(self): + rows = copy.deepcopy(self.rows) + rows[0]['test'] = 'tests/invented.gd' + self.assertFalse(self.validate(rows)[0]) + self.assertFalse(self.validate(rows + [rows[0]])[0]) + + def test_2_duplicate_padding(self): + self.assertFalse(self.validate(self.rows[1:] + [self.rows[1]])[0]) + + def test_3_unknown_extra(self): + self.assertFalse(self.validate(self.rows + [dict(test='invented')])[0]) + + def test_4_required_results(self): + for field, value in [('failures', 1), ('failures', False), ('failures', '0'), + ('passed', False), ('passed', 1), ('passed', None), ('failures', None)]: + with self.subTest(field=field, value=value): + rows = copy.deepcopy(self.rows) + if value is None: + del rows[0][field] + else: + rows[0][field] = value + self.assertFalse(self.validate(rows)[0]) + + def test_5_source_hash(self): + for value in (None, 'bad', '0' * 64, 42): + with self.subTest(value=value): + rows = copy.deepcopy(self.rows) + if value is None: + del rows[0]['source_sha256'] + else: + rows[0]['source_sha256'] = value + self.assertFalse(self.validate(rows)[0]) + + def test_6_missing_or_malformed_rows(self): + for rows in (None, [], {}, 'missing', [None], [{}]): + with self.subTest(rows=rows): + self.assertFalse(self.validate(rows, total_suites=3, failures=0)[0]) + for counts in (dict(total_suites=4), dict(suites_count=True), dict(failures=None)): + self.assertFalse(self.validate(self.rows, **counts)[0]) + + def init_repo(self): + subprocess.run(['git', 'init', '-q', str(self.root)], check=True, capture_output=True) + # Only the disposable repo is written; no workspace git mutation. + subprocess.run(['git', '-c', 'user.name=Checker Test', '-c', 'user.email=checker@example.invalid', + '-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-qm', 'fixture'], + cwd=self.root, check=True, capture_output=True) + return subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=self.root, + check=True, capture_output=True, text=True).stdout.strip() + + def test_7_env_mismatch_and_positive_head(self): + head = self.init_repo() + with patch.dict(os.environ, {}, clear=True): + self.assertEqual(checker.get_actual_git_head(self.root), head) + for name in ('CANDIDATE_COMMIT', 'TARGET_HEAD'): + with patch.dict(os.environ, {name: head}): + self.assertEqual(checker.get_actual_git_head(self.root), head) + with patch.dict(os.environ, {name: '1' * 40}): + self.assertEqual(checker.get_actual_git_head(self.root), '') + with patch.dict(os.environ, dict(CANDIDATE_COMMIT=head, TARGET_HEAD='1' * 40)): + self.assertEqual(checker.get_actual_git_head(self.root), '') + + def test_8_git_unavailable_env_only(self): + with patch.dict(os.environ, {'CANDIDATE_COMMIT': '1' * 40}, clear=True): + with patch('subprocess.run', side_effect=FileNotFoundError('git unavailable')): + self.assertEqual(checker.get_actual_git_head(self.root), '') + with patch('subprocess.run', return_value=subprocess.CompletedProcess([], 128, '', 'not a repo')): + self.assertEqual(checker.get_actual_git_head(self.root), '') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json new file mode 100644 index 0000000..60ba1b7 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_ALL8_CURRENT_ACCEPTANCE_AND_NEXT_WORK.json @@ -0,0 +1,98 @@ +{ + "plan_id": "P24-02", + "title": "操作と通知", + "state": "in_progress; existing Runner submitted, read-only acceptance re-audit started", + "original_denominator": { + "packages": 30, + "units": 88, + "completed": 14, + "partial_credit": false + }, + "source": "9641ed52def2ff8e5b0222449a4a83a1191e1d37", + "current_main": "32c8d1ce4314f821fe2eb3713fe5c6309b98e4c3", + "existing_project": "the-tree-p24-02-operations", + "existing_worker_checkout": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p24-02-operations/P24-02-implement", + "runner_repairs_used": 3, + "max_repairs": null, + "worker_registered": "agy (existing UI assignment retained; no new worker or model switch performed)", + "spec_sha256": "7f4258ef94169c516b5cd1a49a96d0c41f306ca2a05203a8c2a6475b69a1a9ee", + "actual_opus_selection": "docs/plans/original-completion-2026-10-02/execution/next-closure-after-p23-20261017/result.json", + "all_eight_original_conditions": [ + { + "condition_id": "placement_and_invalid_drop", + "all_original_required_text": "Original grid snap and connection warnings; valid placement commits once, invalid placement/assignment explains the actual rejection, leaves model/ledger/selection intact, supports equivalent click-based operation.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "ordinary-controls-observations-r05.json; limited valid reception and overlap rejection, full model/selection preserved.", + "missing_or_dependencies": "Invalid assignment/drop, real rejection call paths and equivalent click operations require exact final-source coverage." + }, + { + "condition_id": "modal_and_pause_resume", + "all_original_required_text": "Modal open/cancel/confirm, input focus, pausing and resuming preserve pending commands and save identity. Behind-dialog input cannot accidentally build or change assignment. Actual pause freezes simulation and ordinary resume continues exactly once.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "ordinary-controls-observations-r05.json and ordinary-menu-os-observations-r02.json; limited modal open/cancel, IME focus, save/load.", + "missing_or_dependencies": "All modal cancel/confirm, actual behind-dialog input limits, pending commands, actual pause freeze/resume once require mapped evidence." + }, + { + "condition_id": "unit_bubbles", + "all_original_required_text": "Unit overhead Zzz, anger, heart correspond to actual existing state. Observe source/model/render bindings and switching/removal without stale symbols; do not invent gameplay thresholds or hidden approved values.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "Existing source bindings were retained by old 9641 candidate; no new whole-condition evidence inferred.", + "missing_or_dependencies": "Real Zzz/anger/heart states, switching and removal without stale symbol; recheck current Runtime and draw source after integration conflicts resolved." + }, + { + "condition_id": "event_log", + "all_original_required_text": "Actual production events reach bounded recent log in sequence, readable Japanese with correct amounts/time/reasons, no duplicate events across pause/load. Reuse accepted256-entry log only with matching source applicability.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "Existing local log34 and accepted P24-03 bounded256 evidence are candidates for exact applicability reuse.", + "missing_or_dependencies": "Current production event sequence, readable time/amount/reason, pause/load no duplicate; current UI and Runtime source must match." + }, + { + "condition_id": "important_warning_cutin", + "all_original_required_text": "Original important-warning UI for invasion start, duel, defense start is connected to actual existing signals/states with clear text, dismiss/stop/resume behavior; fixture-only signal injection is local testing, not proof ordinary production trigger. Missing upstream/catalog/assets remain explicit dependencies, do not claim complete.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "Prior limited shield-positive-to-zero warning text review; fixtures are local-only. P18 backend265ce30 integrated5f6bc11 is accepted backend only.", + "missing_or_dependencies": "Actual ordinary NTR/duel/defense triggers still depend on whole P18-01/02, P19-01, P21-01 and required content; no synthetic signal pass or invented values. Read upstream-audit-r01 before choosing implementation." + }, + { + "condition_id": "click_alternatives", + "all_original_required_text": "Provide/verify click equivalents for necessary pointer/drag/modifier navigation and assignment operations. Reuse existing step/zoom/bounds mechanisms and scales; presentation controls must not change gameplay model. Manual P23 held gestures remain deferred, not waived or relabeled.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "Actual normal menu save-label entrance and limited placement controls.", + "missing_or_dependencies": "Map every required navigation/assignment equivalent. Preserve deferred P23 held gestures as unmet, not waived." + }, + { + "condition_id": "japanese_input_and_scale", + "all_original_required_text": "Actual OS Japanese composition/confirmation/cancel, persisted display of valid Japanese text and clear invalid-input feedback, representative window/display scales with clipping/focus/modal inspection. Use existing save display_name metadata for an editable optional Japanese save label if a suitable text field is absent; retain existing default name, cryptographic owned path, old-save preservation and cancel semantics. Do not add new gameplay numbers or rename save paths to user text. Explicitly classify macOS current development verification and still-required same-Windows-release platform acceptance; do not transfer prior Windows evidence.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "ordinary-menu-os-observations-r02.json and preservation-r03: actual IME cancel/confirm, Japanese display_name, old4file content preservation, distinct PID snapshot equality for one construction scenario.", + "missing_or_dependencies": "Invalid labels, every required scale/focus/clip/modal scenario, full applicable content and same-Windows candidate real-device conditions remain. Reuse only if exact relevant UI/input/source/settings/environment match." + }, + { + "condition_id": "final_validation_review", + "all_original_required_text": "Static/import, local/affected tests first; batch related fixes then run required full roster on exact final game/source/settings. Exact version/source hashes/config/inputs/artifacts and condition status. Independent separate Claude review with actual runtime evidence (not pending templates alone), current-main integration and final validation. All required conditions/evidence must hold to close P24-02; no partial credit, no counts as acceptance.", + "whole_condition_accepted": false, + "existing_limited_evidence_candidate": "Submitted local-r06 3suites167checks, parse and limited independent delta review only; no whole8 acceptance.", + "missing_or_dependencies": "Resolve actual four current-main merge conflicts retaining both accepted P23/source guards and existing P24 implementation; then static/local/impact, missing actual OS/upstream scenarios, exact final full once, registered validate, separate whole8 Opus review, normal prepare-integration/integration/final and ledger. Reuse matching current full only when all selected source/settings/input/engine pins truly match." + } + ], + "current_main_preflight": { + "exit_code": 1, + "conflict_paths": [ + "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "scenes/main/main.gd", + "scenes/main/world_preview.gd", + "tools/regression/suite-policy.json" + ], + "not_an_integration_candidate": true, + "not_tested_or_accepted": true + }, + "registered_build_directory_currently_exists": false, + "raw_proof_location_audit": "docs/plans/original-completion-2026-10-02/execution/next-closure-after-p23-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json", + "next_bounded_action": "Have the existing P24 commander inspect these actual eight-condition/source/conflict/proof facts and direct one legal same-task continuation. Root does not start a duplicate worker or force submitted to repair_pending. Missing local old proof must use exact archive restore if needed; no repeated full run just to replace available proof.", + "P04_four_unapproved_rules": "Remain pending independent of P24; approved hold/reservation/18:00 rules unchanged.", + "new_engine_runs": 0, + "new_worker_runs": 0, + "new_full_regression_runs": 0, + "unit_accepted": false, + "audited_at_jst": "2026-10-17T00:26:02.956343+09:00" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json new file mode 100644 index 0000000..4569f88 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/P24_EXISTING_LIMITED_EVIDENCE_LOCATION_AND_SHA_AUDIT.json @@ -0,0 +1,491 @@ +{ + "source": "9641ed52def2ff8e5b0222449a4a83a1191e1d37", + "audited_at_jst": "2026-10-17T00:26:02.956343+09:00", + "scope": "Read-only legacy proof availability/pins only; does not establish current main/source applicability or full P24 acceptance. No engine/OS/test started.", + "total_references": 60, + "local_matching": 60, + "local_mismatching": 0, + "local_unavailable": 0, + "rows": [ + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/submitted-local-r06.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-local-native-preflight-r06/local-ended.json", + "expected_sha256": "a49a9229bc3bde09aebff72daafbfe99d159da9bf1dfc702b036640946654b64", + "local_exists": true, + "actual_sha256": "a49a9229bc3bde09aebff72daafbfe99d159da9bf1dfc702b036640946654b64", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/submitted-local-r06.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-local-native-preflight-r06/runner-input.json", + "expected_sha256": "577442ab27679b03c4c25a8b0d362aed871599d7c37675aaafd41fa80579dd70", + "local_exists": true, + "actual_sha256": "577442ab27679b03c4c25a8b0d362aed871599d7c37675aaafd41fa80579dd70", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-r02/started.json", + "expected_sha256": "d40a0252aa547b049d30cdc9a15774131438b2535f808ead128f0f86f7bf5954", + "local_exists": true, + "actual_sha256": "d40a0252aa547b049d30cdc9a15774131438b2535f808ead128f0f86f7bf5954", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-r02/process-started.json", + "expected_sha256": "5ba807e0ee65944815542648108322586f4730b074abebb0b117dd61071140b1", + "local_exists": true, + "actual_sha256": "5ba807e0ee65944815542648108322586f4730b074abebb0b117dd61071140b1", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-r02/ended.json", + "expected_sha256": "0b188dcb42995ee09205f233116665cbf54f59a7d1e93f9d2aafc07779ae19a3", + "local_exists": true, + "actual_sha256": "0b188dcb42995ee09205f233116665cbf54f59a7d1e93f9d2aafc07779ae19a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-r02/events.jsonl", + "expected_sha256": "efa3f0eca8bf0001edc89041d968a0fdf9f474ddd5a6803db2a138e4dfd53cc5", + "local_exists": true, + "actual_sha256": "efa3f0eca8bf0001edc89041d968a0fdf9f474ddd5a6803db2a138e4dfd53cc5", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-r02/passive_os.gd", + "expected_sha256": "66e60f81f836fe648f9011660d0255f5c4b780bfdacc820829c90bb655022880", + "local_exists": true, + "actual_sha256": "66e60f81f836fe648f9011660d0255f5c4b780bfdacc820829c90bb655022880", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-consumer-r02/started.json", + "expected_sha256": "64ad63ae5b4339cd639f2d22fa076caa7d8d3f9501337fadadc1d4dcfcbbe2d0", + "local_exists": true, + "actual_sha256": "64ad63ae5b4339cd639f2d22fa076caa7d8d3f9501337fadadc1d4dcfcbbe2d0", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-consumer-r02/process-started.json", + "expected_sha256": "f116952d77720858065576f41df7116f384801e6c27acb6cf339a329589627b0", + "local_exists": true, + "actual_sha256": "f116952d77720858065576f41df7116f384801e6c27acb6cf339a329589627b0", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-consumer-r02/ended.json", + "expected_sha256": "22d0ffd1ea18c06a46dc7aa977613385d3ae5f4a81dab9ed4650f9c10d044d68", + "local_exists": true, + "actual_sha256": "22d0ffd1ea18c06a46dc7aa977613385d3ae5f4a81dab9ed4650f9c10d044d68", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-ime-normal-consumer-r02/events.jsonl", + "expected_sha256": "369eb5e182fcf5b21693380198df187654b508e288c08f6ecb0d3aa6af4ec0ca", + "local_exists": true, + "actual_sha256": "369eb5e182fcf5b21693380198df187654b508e288c08f6ecb0d3aa6af4ec0ca", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-393c3655f3e84483b9c24fd5d2955dd2-p24-02-os/saves/manual_ui_5e9f8ca3662a2ddf42984ba1403675a7/manifest.json", + "expected_sha256": "e8537a6f6cba53683cca36794adc08f7c5958b78ced74fa06e499a3f8c123e4a", + "local_exists": true, + "actual_sha256": "e8537a6f6cba53683cca36794adc08f7c5958b78ced74fa06e499a3f8c123e4a", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-menu-os-observations-r02.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-393c3655f3e84483b9c24fd5d2955dd2-p24-02-os/saves/manual_ui_5e9f8ca3662a2ddf42984ba1403675a7/world.bin", + "expected_sha256": "0424074b51f327ffb3ce3dc8e92ccb2d9d04d657db6e1461cdb83541f1cc0bee", + "local_exists": true, + "actual_sha256": "0424074b51f327ffb3ce3dc8e92ccb2d9d04d657db6e1461cdb83541f1cc0bee", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/started.json", + "expected_sha256": "bc05d5ef0ec62a3fc46e5b957f3724ef95c339078bc02eee04b3c42632bbe1ab", + "local_exists": true, + "actual_sha256": "bc05d5ef0ec62a3fc46e5b957f3724ef95c339078bc02eee04b3c42632bbe1ab", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/process-started.json", + "expected_sha256": "d6d981e157cd7e68a08e9e0133f81faac4113a48d03e74deb04d380cb03e41b1", + "local_exists": true, + "actual_sha256": "d6d981e157cd7e68a08e9e0133f81faac4113a48d03e74deb04d380cb03e41b1", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/ended.json", + "expected_sha256": "bf42d9666d4fe27051403f7d00a96f17abaa1e9b551db9d13c49ee8f7130d5c6", + "local_exists": true, + "actual_sha256": "bf42d9666d4fe27051403f7d00a96f17abaa1e9b551db9d13c49ee8f7130d5c6", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/events.jsonl", + "expected_sha256": "104fb16b406dd123b77356ef98bd5a8f0bd595d660be0870c881cfbf30ce7302", + "local_exists": true, + "actual_sha256": "104fb16b406dd123b77356ef98bd5a8f0bd595d660be0870c881cfbf30ce7302", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/passive_os.gd", + "expected_sha256": "6dceadb5abdf48fda403571f84b6a682a05e7f9ccd64391f2f51176cb5dd842b", + "local_exists": true, + "actual_sha256": "6dceadb5abdf48fda403571f84b6a682a05e7f9ccd64391f2f51176cb5dd842b", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/preexisting-slot-before.json", + "expected_sha256": "aed601473efa522c41e5ca8890c11f1eeb2b06e786fb76cba1e96263934efe82", + "local_exists": true, + "actual_sha256": "aed601473efa522c41e5ca8890c11f1eeb2b06e786fb76cba1e96263934efe82", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-r03/preexisting-slot-after.json", + "expected_sha256": "efb7170f0579bed92a42ead7c03b70dfc37df1e55dde05a96ae67666c6e38063", + "local_exists": true, + "actual_sha256": "efb7170f0579bed92a42ead7c03b70dfc37df1e55dde05a96ae67666c6e38063", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/started.json", + "expected_sha256": "135b779ce605d491bc6110235e9827c4091fefe8b86a49222655b3b713adcad7", + "local_exists": true, + "actual_sha256": "135b779ce605d491bc6110235e9827c4091fefe8b86a49222655b3b713adcad7", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/process-started.json", + "expected_sha256": "e33badfd0b68ff97ce8459fed3a6a6d7275d938fbc1705c82cea94e7ded66314", + "local_exists": true, + "actual_sha256": "e33badfd0b68ff97ce8459fed3a6a6d7275d938fbc1705c82cea94e7ded66314", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/ended.json", + "expected_sha256": "353cfa8e5ea703cb777bf0796b8a0f2f7fa55a5a36b69585414d5e7a16fae94c", + "local_exists": true, + "actual_sha256": "353cfa8e5ea703cb777bf0796b8a0f2f7fa55a5a36b69585414d5e7a16fae94c", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/events.jsonl", + "expected_sha256": "2f93c17152ed41dbbbd7b70b79383591a02ef0f9939d03cf340665ed4ee70ef7", + "local_exists": true, + "actual_sha256": "2f93c17152ed41dbbbd7b70b79383591a02ef0f9939d03cf340665ed4ee70ef7", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/passive_os.gd", + "expected_sha256": "6dceadb5abdf48fda403571f84b6a682a05e7f9ccd64391f2f51176cb5dd842b", + "local_exists": true, + "actual_sha256": "6dceadb5abdf48fda403571f84b6a682a05e7f9ccd64391f2f51176cb5dd842b", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/snapshot-000.bin", + "expected_sha256": "b242cc87abd49bb80d74b7904cfa3492ef57b59e9acfe014e12b35387990da3d", + "local_exists": true, + "actual_sha256": "b242cc87abd49bb80d74b7904cfa3492ef57b59e9acfe014e12b35387990da3d", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-preservation-consumer-r03/loaded-stored-snapshot.bin", + "expected_sha256": "b242cc87abd49bb80d74b7904cfa3492ef57b59e9acfe014e12b35387990da3d", + "local_exists": true, + "actual_sha256": "b242cc87abd49bb80d74b7904cfa3492ef57b59e9acfe014e12b35387990da3d", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-41ed37cd3b6c4de8908b4bba8fe20724-p24-02-os/saves/manual_ui/manifest.json", + "expected_sha256": "62d801bb42100ade11f3f4957bff85cac7fea573dcefea97faf3806c8a29c669", + "local_exists": true, + "actual_sha256": "62d801bb42100ade11f3f4957bff85cac7fea573dcefea97faf3806c8a29c669", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-41ed37cd3b6c4de8908b4bba8fe20724-p24-02-os/saves/manual_ui/world.bin", + "expected_sha256": "da65a181c934bf8fe0a2969db5c074863b03cf2483f2b039e5dd588b809b80b3", + "local_exists": true, + "actual_sha256": "da65a181c934bf8fe0a2969db5c074863b03cf2483f2b039e5dd588b809b80b3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-41ed37cd3b6c4de8908b4bba8fe20724-p24-02-os/saves/manual_ui_6789d75c74d103ad3912e8992a815680/manifest.json", + "expected_sha256": "cd450b442c37d497735888f6b96076ba482dca72a420aed1cf6568eb3723824b", + "local_exists": true, + "actual_sha256": "cd450b442c37d497735888f6b96076ba482dca72a420aed1cf6568eb3723824b", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-save-preservation-observations-r03.json", + "path": "/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-41ed37cd3b6c4de8908b4bba8fe20724-p24-02-os/saves/manual_ui_6789d75c74d103ad3912e8992a815680/world.bin", + "expected_sha256": "a0eb8af6fbb6abcdaea0375b4298164013229c3ba187a9d4c1e54d37ecb75202", + "local_exists": true, + "actual_sha256": "a0eb8af6fbb6abcdaea0375b4298164013229c3ba187a9d4c1e54d37ecb75202", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/started.json", + "expected_sha256": "64397b2e4b9cf67faf78bd02848492895111ac0b0b32c9d4fd629e481103b094", + "local_exists": true, + "actual_sha256": "64397b2e4b9cf67faf78bd02848492895111ac0b0b32c9d4fd629e481103b094", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/process-started.json", + "expected_sha256": "3b73172fa0124a5ef47f7004d5ae4144b057279b7246e8dd4eb84cc5a1097c17", + "local_exists": true, + "actual_sha256": "3b73172fa0124a5ef47f7004d5ae4144b057279b7246e8dd4eb84cc5a1097c17", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ended.json", + "expected_sha256": "9bbdf3d512b5f5ae4a8f9595da1cac52587a5f05009869ec7e0d7c8099c4ed8a", + "local_exists": true, + "actual_sha256": "9bbdf3d512b5f5ae4a8f9595da1cac52587a5f05009869ec7e0d7c8099c4ed8a", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/events.jsonl", + "expected_sha256": "f456f8013343264573c6cab8c7be99807210cb889a78f0a1db1461989d46a3dc", + "local_exists": true, + "actual_sha256": "f456f8013343264573c6cab8c7be99807210cb889a78f0a1db1461989d46a3dc", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/passive_os.gd", + "expected_sha256": "552f9bb40770f1a88bbfb41586782e4f8ba8dff29776336ab83ba64dbd2f4767", + "local_exists": true, + "actual_sha256": "552f9bb40770f1a88bbfb41586782e4f8ba8dff29776336ab83ba64dbd2f4767", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-000.bin", + "expected_sha256": "dfea6817f87a9c79cc970f65e19991e60741277244106f770341d71185321fd4", + "local_exists": true, + "actual_sha256": "dfea6817f87a9c79cc970f65e19991e60741277244106f770341d71185321fd4", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-001.bin", + "expected_sha256": "dfea6817f87a9c79cc970f65e19991e60741277244106f770341d71185321fd4", + "local_exists": true, + "actual_sha256": "dfea6817f87a9c79cc970f65e19991e60741277244106f770341d71185321fd4", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-002.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-003.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-004.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-005.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-006.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-007.bin", + "expected_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "local_exists": true, + "actual_sha256": "17399e4292ef33b29079f361bf91d853f0a1b036066e4e23707840d30deac4a3", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-000.json", + "expected_sha256": "ec054c36513cf2bc19b4743862b99a6d56ae0cffebe413d731c9ebb6ab53f237", + "local_exists": true, + "actual_sha256": "ec054c36513cf2bc19b4743862b99a6d56ae0cffebe413d731c9ebb6ab53f237", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-001.json", + "expected_sha256": "ec054c36513cf2bc19b4743862b99a6d56ae0cffebe413d731c9ebb6ab53f237", + "local_exists": true, + "actual_sha256": "ec054c36513cf2bc19b4743862b99a6d56ae0cffebe413d731c9ebb6ab53f237", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-002.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-003.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-004.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-005.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-006.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/ui-snapshot-007.json", + "expected_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "local_exists": true, + "actual_sha256": "07cebb2a71ff0c63608f8a218a03618e9504fd171d8c7059b15bcf901dc5f8ec", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-000.png", + "expected_sha256": "4516abb706e829094c19632e6c8005ad68877c7856af4ccfdf346e98b8dabe95", + "local_exists": true, + "actual_sha256": "4516abb706e829094c19632e6c8005ad68877c7856af4ccfdf346e98b8dabe95", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-001.png", + "expected_sha256": "9d61efee38fee61447a3ae10e95e07381f9560254388cc4ce38a79f7a0a21e6d", + "local_exists": true, + "actual_sha256": "9d61efee38fee61447a3ae10e95e07381f9560254388cc4ce38a79f7a0a21e6d", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-002.png", + "expected_sha256": "4f53747098468b37bc23078d70d091a8b1d8c78ae44547476df70d40c153d03e", + "local_exists": true, + "actual_sha256": "4f53747098468b37bc23078d70d091a8b1d8c78ae44547476df70d40c153d03e", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-003.png", + "expected_sha256": "dd60776d2370d79814e874a3944f7bce9152d6d3640bee434c624940f6e000e8", + "local_exists": true, + "actual_sha256": "dd60776d2370d79814e874a3944f7bce9152d6d3640bee434c624940f6e000e8", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-004.png", + "expected_sha256": "e8c2b8027767cd26850f02a2d87d2e594bab4c3d6ebcc5f288ec6140f332146d", + "local_exists": true, + "actual_sha256": "e8c2b8027767cd26850f02a2d87d2e594bab4c3d6ebcc5f288ec6140f332146d", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-005.png", + "expected_sha256": "7795cc8c2ee590c673f7bcd16d83e8fbfb85a8fef0af913282e9aa1df54e54c8", + "local_exists": true, + "actual_sha256": "7795cc8c2ee590c673f7bcd16d83e8fbfb85a8fef0af913282e9aa1df54e54c8", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-006.png", + "expected_sha256": "7795cc8c2ee590c673f7bcd16d83e8fbfb85a8fef0af913282e9aa1df54e54c8", + "local_exists": true, + "actual_sha256": "7795cc8c2ee590c673f7bcd16d83e8fbfb85a8fef0af913282e9aa1df54e54c8", + "sha_match": true + }, + { + "record": "docs/plans/original-completion-2026-10-02/execution/p24-02/native-os-input-r01/ordinary-controls-observations-r05.json", + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p24-02-native-os-controls-r05/frame-007.png", + "expected_sha256": "0854ba41fd73e5b092d71e7d11e99fe3efb7b25ee04c2b0c8750b5d9de6d84b3", + "local_exists": true, + "actual_sha256": "0854ba41fd73e5b092d71e7d11e99fe3efb7b25ee04c2b0c8750b5d9de6d84b3", + "sha_match": true + } + ] +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json new file mode 100644 index 0000000..8dab1d2 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/REFERENCE_MANIFEST.json @@ -0,0 +1,34 @@ +{ + "submitted": "9641ed52def2ff8e5b0222449a4a83a1191e1d37", + "main_reference": "51e8657bfbe24856186cc6530358c5459cf909b6", + "reference_only_not_product_edits": true, + "registered_base_unchanged": "1a5abc65163097d4b36a060d68f58c92093eb4e9", + "all4_current_main_files": [ + { + "path": "scenes/main/main.gd", + "reference_path": "docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt", + "sha256": "92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a", + "main_blob": "fff37f082d750bb91c71f2aa54d168c9e2cb8fe7" + }, + { + "path": "scenes/main/world_preview.gd", + "reference_path": "docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt", + "sha256": "4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2", + "main_blob": "fa32fe0344d20565b6d542da1256a94e8dee1845" + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "reference_path": "docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "sha256": "6900df6014431ff160c0d18b0dfa65ef0a3fa78eaa08922eaa96de18d5b9fd8c", + "main_blob": "2eecb803f5eb6774ff38cd353de1f02847aeb0f9" + }, + { + "path": "tools/regression/suite-policy.json", + "reference_path": "docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json", + "sha256": "7eb085d7e7a383785b4faedb503d0f6002bcad431e3188785aef901ce123769f", + "main_blob": "ad194ae425fb8123fa191ed3635f404c098f692d" + } + ], + "instruction": "Root reference snapshots in allowed docs only. Do not copy arbitrary other current-main source or merge whole main into original-base worker; this would violate unchanged contract scope. Resolve all4 targeted content conflicts preserving source behavior from both branches, then Root builds exact combined current-main test composition. No product values/acceptance weakened.", + "non_importing_reference_extensions": "Godot source snapshots are stored as .gd.source.txt to prevent duplicate class registration in later Godot imports; full source bytes and SHA pins unchanged." +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json new file mode 100644 index 0000000..2eecb80 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -0,0 +1,3777 @@ +{ + "schema_version": 1, + "scope": "exact per-suite completion label/schema; all check counts from actual raw only", + "rows": [ + { + "test": "tests/s4/test_original_relationship_model.gd", + "template": "Original relationship model: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", + "authority": { + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" + } + }, + { + "test": "tests/s4/test_original_relationship_runtime.gd", + "template": "Original relationship runtime: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "authority": { + "path": "docs/spec/acceptance/P18-01-code-r01.json", + "scope": "new backend suite marker; native execution pending" + } + }, + { + "test": "tests/ui/test_selection_departure.gd", + "template": "Selection departure: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4", + "authority": { + "path": "docs/spec/acceptance/P24-03-code-r02.json", + "scope": "new local suite source marker; actual execution pending" + } + }, + { + "test": "tests/business/test_business_closure.gd", + "template": "Business closure: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business closure: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7b13a0dc13d1276cce7515973af47ad9653620e04221c43679e059e674b1d4fb", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Business closure: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/business/test_business_operations.gd", + "template": "Business operations: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business operations: 48 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5b1975cb3e921febe5c23ae26b3c8445d92ab68de2479d23f86f565a934f04f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 156, + "text": "\tprint(\"Business operations: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/business/test_required_kitchen.gd", + "template": "Required kitchen: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p09-kitchen-single-link-candidate-r09/candidates/tests/business/test_required_kitchen.gd", + "sha256": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", + "scope": "exact unexecuted r09 source marker declaration only; no actual count or functional PASS inferred" + }, + "source_marker_lines": [ + { + "line": 481, + "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/environment/test_cleaning_access.gd", + "template": "Cleaning access: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning access: 6 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1382eab9d01dffaa396cb7fb71902538ee649a963ac9e1fd099ea7a147669884", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\tprint(\"Cleaning access: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_facility_cleanliness.gd", + "template": "Facility cleanliness: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility cleanliness: 49 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6dba789489d3e7da8cbd5056f76d7a5eb97c454b087787ccda926682c328a5f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"Facility cleanliness: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_runtime_cleanliness.gd", + "template": "Runtime cleanliness: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime cleanliness: 29 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3b5fe92855f17f1551de76fe1570dbf92c697b769721bb87582bc4d4d06e36b4", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 58, + "text": "\texpect(runtime.restore_snapshot(intact), \"restore current dirt before rollback checks\")" + }, + { + "line": 75, + "text": "\tprint(\"Runtime cleanliness: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_runtime_weather.gd", + "template": "Runtime weather: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime weather: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "047bb23573ef6a4c7e2483e3a2bfd244a3e2061fbf65c33fa08a52952a2655a0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 52, + "text": "\tprint(\"Runtime weather: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_weather_recovery.gd", + "template": "Weather recovery: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weather recovery: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d71bf3fbf1fa1d43eae2c38161a44b18fc6cc34477a1ab9dd12fca54e4ee9089", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Weather recovery: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/environment/test_weather_timeline.gd", + "template": "Weather timeline: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weather timeline: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "212dab5036f90ae2eaf1b75e0c0adaea4a572cdbfb31b07d0ef4336def4f4791", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\tprint(\"Weather timeline: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/mvp/test_amelia_donations.gd", + "template": "Chapel gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel gameplay: 38 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1bd622a3d88374f01e3d8885a84a816572ed296928f6ce7e3f8134ab15f60793", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/mvp/test_anri_floor_pricing.gd", + "template": "Anri floor pricing: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Anri floor pricing: 48 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "420cd0b14c0a4921ab3a45e612773f8afd38a31b566d1b95942d3e5defc004b1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"Anri floor pricing: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_approved_free_unique_arrears.gd", + "template": "APPROVED_FREE_UNIQUE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "APPROVED_FREE_UNIQUE checks=83 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "411d7836326f2858b68201bc3e8fec2b1d365291342939249cbf5a40a20d6b0b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 208, + "text": "\tprint(\"APPROVED_FREE_UNIQUE checks=%d failures=%d\" % [checks,failures])" + } + ] + }, + { + "test": "tests/mvp/test_chapel_boundaries.gd", + "template": "Chapel boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel boundaries: 46 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a857179fbe5186e27080ef7bae13775edcc821440cd763fd793e03ef7d27ac5c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tprint(\"Chapel boundaries: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_chapel_service.gd", + "template": "Chapel service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel service: 24 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b8631e1095ce7714d2ae581a6ee3d8fe47065184b7dd05e9104a2b7c0cacb1c1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 60, + "text": "\tprint(\"Chapel service: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_fine_cleaning_effect.gd", + "template": "Cleaning gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning gameplay: 95 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7c7df0bac1c072ed4423140e8afe431952ec2a47f76df7dbc24cab62283cae06", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/mvp/test_mvp_content_catalog.gd", + "template": "PASS: MvpContentCatalog ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: MvpContentCatalog (216 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b663b8790d02acfa836c8a6a6609f337c7991b7b91d271d1b60b76d137bbd62b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: MvpContentCatalog (%d checks)\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: MvpContentCatalog (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_operations_coordinator.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 849 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "87499bf5f45a7201cb16082df0ac1031b7b28e9c725950013252d0bb9e5efd6f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 45, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_original_midnight_settlement.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 29 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "916d0b4184842b1c8cc23c1a95db12558a8ba665b251aa6a383a42995eceaa30", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if _failures == 0 else \"FAIL\", _checks])" + } + ] + }, + { + "test": "tests/mvp/test_original_room_zones.gd", + "template": "Original room zones: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original room zones: 40 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6f1b99f2bac0e58eab30fbb061796c88fc93a21e24de7bdcd5ee39a4c02fe8df", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 41, + "text": " print(\"Original room zones: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_pending_service_pricing_report.gd", + "template": "Pending service pricing report: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Pending service pricing report: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6fbde7cb7c8df52625963ebd06ded19a238738d2a8441e516f2079f00c919fc1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 216, + "text": "\tprint(\"Pending service pricing report: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/mvp/test_premium_room.gd", + "template": "Premium room integration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Premium room integration: 15 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ce79ae9632cd17f84ca52961e098d5015921d81abcf57acc72461a16dd44baff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 48, + "text": "\tprint(\"Premium room integration: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_premium_room_guest_flow.gd", + "template": "Premium guest flow: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Premium guest flow: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "68bdba5aee805f782da3aa22bc03d4f051d165b34160151a0afe293b8e8cc8b2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Premium guest flow: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/mvp/test_progression_service.gd", + "template": "PASS: ProgressionService ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: ProgressionService (110 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f97895fceacc42fbbe14f3e77972ef980abf8f63ca9a5c8c8d4003900305b0aa", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: ProgressionService (%d checks)\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: ProgressionService (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_content_progression.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 116 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c8f7a107d024693b863feb2c9cc460383b98cf04e5ed872011bcd7024be68d60", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 41, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_mana_depletion.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 21 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8d94fd8e28b0fc22a699c630b5d68e7afd476a1305c86ecf57858b2f1d49b8ae", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_operations_acceptance.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 46 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a802afdc5786b6690a2c8b81f39eefda9c04624826d5cb90c587cf84190feea3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_operations_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 51 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d31a891906d539d9f4db1ca6c0d327956d3634aa2d89baffa08fc6c8bede67b9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_runtime_upkeep.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "eae48823d58d78c419d29728e33a5559fbe5131b49e33d04ca4e6fa3a721c29b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 102, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_staff_unique_contract_report.gd", + "template": "STAFF_UNIQUE_CONTRACT_REPORT checks= failures=", + "required": true, + "historical_marker_for_schema_only": "STAFF_UNIQUE_CONTRACT_REPORT checks=34 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93d7b00d3b27a85246dad25337a25c0036d562390e584854fe89c958ada76907", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 69, + "text": "\texpect(runtime.submit_command(&\"set_staff_work_assignment\", {&\"staff_id\": &\"heroine_anri\", &\"facility_id\": workplace.instance_id, &\"template_id\": &\"NIGHT\"}).is_success(), \"normal assignment uses existing path/role/capacity checks\")" + }, + { + "line": 116, + "text": "\tprint(\"STAFF_UNIQUE_CONTRACT_REPORT checks=%d failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/mvp/test_unique_character_validation_policy.gd", + "template": "PASS: UniqueCharacterValidationPolicy ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: UniqueCharacterValidationPolicy (108 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2b4c70506e4436e5afe4bf987ff9bba765a02394a4de86e2ed0af6ba5b68c766", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\tprint(\"PASS: UniqueCharacterValidationPolicy (%d checks)\" % checks)" + }, + { + "line": 61, + "text": "\t\tpush_error(\"FAIL: UniqueCharacterValidationPolicy (%d of %d checks failed)\" % [failures, checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_construction_boundaries.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 17 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "266c4f8f240d7508c6c282b08745c1cd813caae384fd46e1ab88f170944ab55e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 45, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_legacy_midnight.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 23 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "48009da7074b78929d9259ef4ac8baabcb192e7a337742df0d999442270d6e89", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 78, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/mvp/test_upkeep_loss_streak.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 15 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b90e6ad96b4a0989b298a62d11c349fcfac53a65cb532f206d8e7ff2223190dc", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 49, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/performance/test_digest_performance.gd", + "template": "Digest performance: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Digest performance: 83 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Digest performance: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "known_unicode_diagnostic": { + "exact_line": "Unicode parsing error, some characters were replaced with � (U+FFFD): Unexpected NUL character", + "source_sha256": "363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f", + "source_lines": [ + 250, + 251, + 252, + 253, + 254, + 255, + 256 + ], + "count_is_observed_not_fixed": true, + "scope": "deliberate UTF8/String.chr(0) digest boundary fixture only; not general error allowance" + } + }, + { + "test": "tests/performance/test_facility_reachability.gd", + "template": "Facility reachability: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility reachability: 33 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f11e7da7f2ac2eb8f80175eda65a285403e18f5d42c3c79c99c1aec9f128eb01", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 51, + "text": "\tprint(\"Facility reachability: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_lazy_digest.gd", + "template": "Lazy digest: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Lazy digest: 11 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b1d62d6c83abd514e462ea737baab113c52e76c3b76f7a0672d4a4a119617846", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Lazy digest: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_mvp_manifest_cache.gd", + "template": "MANIFEST_CACHE_CORRECTNESS checks= failures= candidate=true", + "required": true, + "historical_marker_for_schema_only": "MANIFEST_CACHE_CORRECTNESS checks=79 failures=0 candidate=true\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bcf1668f379344447b72483b6923c2eb59a2fdc29237a6cc817a31d3834d2e52", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 221, + "text": "\tprint(\"MANIFEST_CACHE_CORRECTNESS checks=%d failures=%d candidate=%s\" % [checks, failures, cached])" + } + ] + }, + { + "test": "tests/performance/test_path_membership.gd", + "template": "Path membership: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path membership: 8 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4a0eee95ee39a92eb9f8ec345d46d45f7349e3428454a22bf867b588ecc5bfa6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tprint(\"Path membership: 8 checks, %d failures\" % failures)" + } + ] + }, + { + "test": "tests/performance/test_perf_mvp_001.gd", + "template": " checks= passed= failed= skipped=", + "required": true, + "historical_marker_for_schema_only": " checks=24 passed=24 failed=0 skipped=0", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1a4bc20ea4810924e8e64bc5fec1b44fc327f1676200e978a77577d628675e5b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 62, + "text": "\t\t\" checks=%d passed=%d failed=%d skipped=%d\"" + } + ], + "json_report": { + "prefix": "PERF_MVP_001_REPORT=", + "check_summary_matches_primary": true, + "smoke_is_not_P28_maximum_performance_acceptance": true + } + }, + { + "test": "tests/performance/test_snapshot_ownership.gd", + "template": "Snapshot ownership: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Snapshot ownership: 89 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2fbcd50c490babecd43fb1611d9b3dec4134b41c80deef795835de38e6af241b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\tprint(\"Snapshot ownership: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/performance/test_system_optimization.gd", + "template": "PASS: checks; failures=", + "required": true, + "historical_marker_for_schema_only": "PASS: 282 checks; failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "374ff4b3ba78b7a724a3620d683db3835fd2a545f1de11606fd4969a3c6ede95", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 83, + "text": "\tprint(\"PASS: %d checks; failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/performance/test_world_snapshot_cache.gd", + "template": "World snapshot cache: checks, failures", + "required": true, + "historical_marker_for_schema_only": "World snapshot cache: 9 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "46f25630b30c9cc0f7cddb051a42a0c3edc486f3b81cbfab94d8d2cd0e989935", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 37, + "text": " print(\"World snapshot cache: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/run_tests.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 73 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4c8ef388e2349ff975a2af7260da0aa02231a6036ea71b4000f2bb0e6f4083d5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 10, + "text": "\tprint(\"The Tree: automated checks\")" + }, + { + "line": 17, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 19, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_command_snapshot_key_order.gd", + "template": "Command Queue snapshot key-order regression: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Command Queue snapshot key-order regression: 1228 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5803a593b0d9dbb35248d79a2d94b5a61668f5bea7687b2cb75366c8b1b33a2b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 137, + "text": "\tprint(\"Command Queue snapshot key-order regression: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s0/test_command_transaction.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1f1559646a38940db027ee83b2622f317e9b9c6c6f7d4b86f41f866eabb7e75d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_definition_registry.gd", + "template": "PASS: DefinitionRegistry ( checks)", + "required": true, + "historical_marker_for_schema_only": "PASS: DefinitionRegistry (312 checks)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bf355805e007d99addfb9b963d3566a84602cab40bdb1805ccf3d4e8d851ae17", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 26, + "text": "\t\tprint(\"PASS: DefinitionRegistry (%d checks)\" % _checks)" + }, + { + "line": 28, + "text": "\t\tpush_error(\"FAIL: DefinitionRegistry (%d of %d checks failed)\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_determinism.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 15 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a397d2b33a2c8abfae76350d8ee65b967bbc1625186a865dffd776b421cc7520", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_domain_events.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 33 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cfb0c5678f9b88ce8594ad06cd122276faa2c7f1287cd204de61c1f777508fbe", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 16, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 18, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_runtime_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 21 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "79c59678394ca98d3bc4de25e581eb2a888df1ded5ac9d5363cb4476e875f90e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s0/test_stable_id_generator.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 8 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2fac546b4788650b69832b2336d9d46f7b8b343b660d687256d98a64ef4b823c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_construction_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 105 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ce63074b84cb75d710eb3650701a9cb2acf920ff607e41be645904fc2395e2c3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 196, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 198, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_dimension_batch_save.gd", + "template": "Dimension batch save: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dimension batch save: 23 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ca397c0457996bab637e8be5217b7db2c14579a1b0df54434b4000a69e793bbf", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 56, + "text": "\truntime.free();clock.free();state.free();print(\"Dimension batch save: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_excavation_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 58 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "64dacd14dbe0bc94368879daa130e4861ff43cc63fac6bb02e4e64091ee9a750", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 77, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 79, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_facility_infrastructure_projector.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 192 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "060fc9ab24d123ba91782652e6c9d7ba61c140a5f418854a8a28e07fa0adea8c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 104, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 106, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_facility_resize_preview.gd", + "template": "Facility resize preview: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Facility resize preview: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "46339afa52739c6ab626d78ddd298e06f71366ca886954c852549658ff91bc85", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 58, + "text": "\tprint(\"Facility resize preview: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_original_facility_dimensions.gd", + "template": "Original facility dimensions: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original facility dimensions: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b56a8bf2bf0d6bea3bb2c45148e60b51de3fd033d1cb997eb71d5ae330a4950b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 46, + "text": "\tprint(\"Original facility dimensions: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_path_congestion.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 127 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "709bb680f04862fd774644b0b425150977f54346ca76f6223663c414e00f33bd", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 32, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 34, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_path_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 22 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0f48bb44c27e32f08477b9aed61a0db5c8808219490928f734c3570a07efb251", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_path_snapshot_transit_guard.gd", + "template": "Path snapshot transit guard: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path snapshot transit guard: 35 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b41d3e1ad3a4cd92bd865ba482c9e6a3d23fbd0ad276346b97c5235f2632f3af", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 47, + "text": "\tprint(\"Path snapshot transit guard: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s1/test_route_subslot_path_adapter.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 184 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a8f712e03776f91899f3311dc3d131d8370faa84032e522092dcc243683487b3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 68, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 70, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_runtime_infrastructure_integration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 180 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "aaca043097fb31a79e77c77cce1d12e34e5d8630e0ad7337af0d6860f4d45473", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 90, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 92, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 126, + "text": "\t_expect(spatial.validation_calls == 1 and adapter.validation_calls == 1, \"quarantine retains independent dependency checks\")" + } + ] + }, + { + "test": "tests/s1/test_runtime_world_construction.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 37 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "65924a9b69cd27b5923c5464bd44ab839b26fe39a51b3580e691805d5a9e0e5b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_spatial_subslot_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 112 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "06f4f69438a280ef942bbefdb9caaef1ba59e7dfd0eab71469ca29d090a8ad58", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s1/test_weighted_resize_runtime.gd", + "template": "Weighted resize Runtime: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Weighted resize Runtime: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b6d31d5b27b51eae9be36e2b0195e78f5e3f1105af01660b0c5cf13cd233baff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 188, + "text": "\tprint(\"Weighted resize Runtime: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s1/test_world_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 60 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a5e165886f161b8bc71722ac7facdd17704bc6274c88dfe7fd2229f7f2b3f2b7", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_economy_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 45 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cfe6196c0da9e39d95ae0e39f0c5404e321f4fa8be3602b58d4bf6cd3fef3969", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_pricing.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 91 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e658a7421aaef08c1c6f9a1bce42cb819ae39ee8ec0da72547b42400688c1a98", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 61 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e5a0ceef45d2fcae110f777ddb3c678374b7c9110cd40f743f88c578f0ed2d34", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_upkeep_accrual.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 38 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2985f565a3e39605965112a2bc17688315e3c0d527dd283913f9416ff0cf0ef3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_facility_upkeep_settlement.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 51 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "5a9666e8e044dbe803c33b41cc7cbb344796703665fa9f7b7a7b465020ec9aa9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\texpect(loaded.restore_snapshot(service.create_snapshot(), loaded_economy), \"receipts restore with ledger crosschecks\")" + }, + { + "line": 127, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_inventory_logistics.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 65 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ef8b87e721f57210da02b8cd0822f58e1a4c1cdaa19fe56301a03a87891e73d6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 30, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 32, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 434, + "text": "\t_expect(guard.restore_snapshot(snapshot), \"strict snapshot fixture restores before corruption checks\")" + } + ] + }, + { + "test": "tests/s2/test_kitchen_production.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 141 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e2aeb1fdbc8d1dfe9400646693a0e97919b3b86cb74a4f000ebeba01bfbee626", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 44, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_ledger_backed_resources.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 18 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "26c2c9b883ee1448345d5555ac3e97293c3bf273b42668c11a6bdc23e1184dd3", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 63, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 65, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_network_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 37 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b0a21ea663667979c109cf6fd2efff717c284398071948c897b9e99090ce12fc", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_power_projection.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 14 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e3020534b084b57a8b773967931fd2be34a1de878b4940c8805b99592bbbc46f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 42, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_mana_reserve_balance.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 33 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "07fe5f10e665cac3c8aa1b12e0c71878ffccc6d4b409250b317135345e3c8a05", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_original_facility_parameters.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 29 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3d5cc5f13f986f8cea9500a7f8d48d8f5803b8d0bee5eb0e7c44b3218a2e3d7b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\texpect(a.operation_state == FacilityInstance.OperationState.INACTIVE and not a.related_facility_operating, \"nested dependency checks actual operation\")" + }, + { + "line": 71, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/s2/test_runtime_operations_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 58 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b7c07beb887b268213a3ba484d1a4f65c542a2dc06c64c78b4f806eaeeed997b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s2/test_service_start_slots.gd", + "template": "Service start slots: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Service start slots: 25 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "286786b9a7c2c86b8fc7ce808abd84dae05f2eccf7e2b3faa3780746ce4e2133", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\tprint(\"Service start slots: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/s3/test_guest_observation_restore_order.gd", + "template": "Guest observation restore-order diagnostic: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest observation restore-order diagnostic: 52 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7abf5692a07edf8270fa9bf4d9ee89a3835741c208c4c670dc605158532284f2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 86, + "text": "\tprint(\"Guest observation restore-order diagnostic: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s3/test_guest_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 137 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0d10b68512685d2ed7d5c0c858952009333e0e47f348ace4b4ccdd0b21d23647", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 35, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 37, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_actor_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 40 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cbad1505c735f3dab4596f7ad823d6501faf0cea4b9b91cc2b6ff0422296a2de", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 27, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_actor_hardening.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 74 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1cdb1706646e70d99ac77530416ca5681582fc6e76ced455023bf5b674efffa5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 338, + "text": "\t_expect(_runtime.restore_snapshot(valid_snapshot), \"valid hardening snapshot restores before corruption checks\")" + } + ] + }, + { + "test": "tests/s3/test_runtime_path_traffic.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 91 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "92cdada10177363ac5ebae6435d0b4c6f0c7d96e6ccc2d0731487bf4f2cdceeb", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 61, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_runtime_staff_assignment.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 104 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f6004ab1601e2910268dd5d02923c32eb697fb587ad74da1f06ca4de6c21713c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d / %d checks\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_staff_payroll_operations.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 88 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "05c817b7e891ac1649072ad33102561e26280b9bdd34f6f3831c7da055044f8c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s3/test_staff_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 209 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "82e9a5de3247251ac29b52aa524fffeb4e63a62a9ab587d62004c268a0180481", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 40, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 42, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_placement_risk_preview_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 115 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "165eddf9a5c51d8d9eeb85ea89e21b1ab6067b7ee057ee0fe5b189e97a71c1ad", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 26, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_relationship_event_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 67 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b65afa74a67985059cc9e8e19684405324cfcdde277a21d7eba6062c968b24c5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 33, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 35, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s4/test_runtime_relationship_events.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 73 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "52c9df100bd85fc49b62b80d214f84750c907330255a909a83a8dbd8195aac39", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 22, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 24, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_authority_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 61 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "983ac275dc4f40f7c8058831b7239f1f4b4e2b3f12da0bbb6ea5e8ad69afe2c5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_defense_service.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 507 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6e97ace03c46091775afb5a772adc32a7add8146f36c3492e99f4ae30f6ea62b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 38, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 40, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_authority_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 65 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "33952b6253c77410c7997ddbf37e039f79bcc0ae283da76c3580ec166f928273", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 266, + "text": "\t).is_success(), \"inspector checks in through Runtime Command\")" + } + ] + }, + { + "test": "tests/s5/test_runtime_defense_autonomy.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 188 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "28ca81ddde4da49442b427ed4c55ddcf213ecb389712db585b64cd416636c403", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 25, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_defense_cycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 162 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a909e2af73aae43590cdd91d07ed2548f4fd2f675d26fa63aa74f6a5cda34357", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 27, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 29, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s5/test_runtime_event_defense.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 186 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "09bd125d85714ed69f7b35b900507136a099375a10ebb1dae0ab55886387b046", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 19, + "text": "\tif _failures == 0: print(\"PASS: %d checks\" % _checks)" + }, + { + "line": 20, + "text": "\telse: push_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s6/test_performance_budget.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 24 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1ae5490637d73b519bf9ed0e00bda0e034d5ea0e589493bcf8886e4d1ff63689", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 74, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 76, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/s6/test_runtime_save_lifecycle.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 96 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "de94d588d6ce204aa8d0221c2989b8dd900cf4e36ca51f83b464518a382a887f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 33, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 35, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 220, + "text": "\t\t\"checksum-valid but ledger-invalid formal package is preserved as a package\"" + }, + { + "line": 285, + "text": "\t\t\"multiple domain-invalid formal generations remain checksum-valid recovery candidates\"" + }, + { + "line": 348, + "text": "\t\t\"all-invalid fixture exposes three checksum-valid but domain-invalid candidates\"" + }, + { + "line": 465, + "text": "\t_expect(SaveService.save_snapshot(path, snapshot)[&\"ok\"], \"checksum-valid orphan repair fixture writes as a package\")" + }, + { + "line": 565, + "text": "\t_expect(SaveService.save_snapshot(path, snapshot)[&\"ok\"], \"%s fixture is checksum-valid\" % label)" + } + ] + }, + { + "test": "tests/s6/test_runtime_snapshot_serialization.gd", + "template": "Runtime snapshot serialization: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime snapshot serialization: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d9106f9defb6cc195b969ff040d24f1f8ceebe055ba66c39d4634a68620cde84", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 120, + "text": "\tprint(\"Runtime snapshot serialization: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/s6/test_save_operations.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 127 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "199df0b7fcc3d212cd32c79e0108dd9e5b83f9895f07553b325571c7674ed96c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 29, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 31, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 124, + "text": "\tvar contract_keys := [\"format_version\", \"game_version\", \"save_id\", \"display_name\", \"created_at_real\", \"updated_at_real\", \"absolute_minute\", \"day\", \"seed\", \"checksum_world\", \"content_pack_ids\", \"enabled_filter_profile\"]" + }, + { + "line": 165, + "text": "\t\t_expect(loaded[&\"ok\"], \"autosave generation %d remains checksum-valid\" % generation)" + }, + { + "line": 326, + "text": "\t_expect(formal_corrupt[&\"ok\"] and formal_corrupt[&\"selected\"][&\"id\"] == &\"backup\", \"checksum failure in formal save selects the verified formal backup\")" + }, + { + "line": 327, + "text": "\t_expect(formal_corrupt[&\"candidates\"][0][&\"code\"] == &\"CHECKSUM_MISMATCH\", \"corrupt formal world is reported as checksum mismatch\")" + }, + { + "line": 411, + "text": "\t_expect(migration_backup_info[&\"id\"] == &\"pre_migration_backup\" and migration_backup_info[&\"package_valid\"] and migration_backup_info[&\"requires_migration\"], \"recovery inventory retains a checksum-valid legacy migration backup without treating it as runtime-loadable\")" + }, + { + "line": 497, + "text": "\tmanifest[\"checksum_world\"] = _sha256(compressed_world)" + } + ] + }, + { + "test": "tests/s6/test_save_preflight.gd", + "template": "Save preflight: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Save preflight: 128 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "49e24247649839b6f50d19b2284368efb7f8f8b189c5010491ad428e97c1c8b9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 24, + "text": "\tprint(\"Save preflight: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 60, + "text": "\tfor key: String in [\"format_version\",\"uncompressed_size\",\"checksum_world\"]:" + }, + { + "line": 64, + "text": "\t\tif key == \"checksum_world\": values = [true,123,null,\"bad_hash\"]" + }, + { + "line": 116, + "text": "\t_write_bytes(world_path,corrupt); _assert_read_rejection(path,&\"CHECKSUM_MISMATCH\",\"corrupt world checksum\")" + } + ] + }, + { + "test": "tests/s6/test_save_recovery_preservation.gd", + "template": "Recovery read-only candidate: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Recovery read-only candidate: 234 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0fc0f75e06d4371f049dc4692e9ab0d871016b633e5ac4693661a33a7390641e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 19, + "text": "\tprint(\"Recovery read-only candidate: %d checks, %d failures\" % [_checks,_failures])" + } + ] + }, + { + "test": "tests/staff/test_dormitory_dimension_migration.gd", + "template": "Dormitory dimension migration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dormitory dimension migration: 25 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "90737c91005975601f400fe88c9a474d10713639b73d8e1354f99bc52e812a49", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 65, + "text": "\truntime.free();clock.free();state.free();print(\"Dormitory dimension migration: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_dormitory_shift_cycle.gd", + "template": "Dormitory shift cycle: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dormitory shift cycle: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ada3c0177f00648137d02450fce5f33da0a644dae9ec861ac51648770dafebda", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 54, + "text": "\tprint(\"Dormitory shift cycle: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_rescue_elevator.gd", + "template": "Elevator rescue: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator rescue: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c32cce5010eb0167c261b767cac1371baa3ca731368de47ff878a5852ed9f840", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Elevator rescue: %d checks, %d failures\" % [checks, failures]); quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_shift_rush.gd", + "template": "Shift rush: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Shift rush: 42 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "af3d47d848c1220741992612e57736df4af05969d424b79829d9977c774e8149", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 55, + "text": "\tprint(\"Shift rush: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_commute.gd", + "template": "Staff commute: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff commute: 85 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0837c92a49f8f83c03ca820eaeb23f7b1dfbc1072f8eeffcb557bb6b57b20c49", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 77, + "text": "\tprint(\"Staff commute: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/staff/test_staff_dormitory.gd", + "template": "Staff dormitory: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff dormitory: 18 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3159c45db121e9f76ce6f8e6748a26201ecfe5720b0f5c9d0b568fb82101ef8f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 56, + "text": "\tprint(\"Staff dormitory: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_rescue.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 46 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8d8c02444c35b2c41c6db42ac0195dd614835541235cd92641ca9b220f59c44c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 92, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_staff_rest.gd", + "template": "Staff rest: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff rest: 14 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fe9b9d2a8a9051850980a393b8c06b8be41de8a82d5efc998c69df3a06cf54ec", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 50, + "text": "\tprint(\"Staff rest: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/staff/test_task_work_clock.gd", + "template": "Staff task clock: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff task clock: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b131fbe922e744c2075338638e3a01811618e93ae786df3ee435318048d24f9b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 31, + "text": "\tprint(\"Staff task clock: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/supply/test_mimi_purchase_quote.gd", + "template": "Mimi purchase quote: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/candidates/tests/supply/test_mimi_purchase_quote.gd", + "sha256": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "bytes": 34683, + "scope": "exact r06 source marker declaration only; no native check count or PASS inferred" + }, + "source_marker_lines": [ + { + "line": 461, + "text": "\tprint(\"Mimi purchase quote: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/supply/test_normal_auto_purchase_chain.gd", + "template": "Normal auto purchase chain: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Normal auto purchase chain: 117 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e9809dfc465576125b1ee09a8c86a966e13cf383e8c47797c5b2919eb2bf8a9b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 96, + "text": "\tvar evidence := {\"checks\": _checks, \"failures\": _failures, \"meal_sales\": _sales(&\"cafeteria\"), \"lodging_sales\": _sales(&\"standard_room\"), \"raw_deliveries\": _completed_deliveries(SupplyCatalog.RAW_FOOD), \"meal_deliveries\": _completed_deliveries(SupplyCatalog.BASIC_MEAL), \"cash\": _state.get_resource(&\"gold\"), \"closes\": _runtime.operations_coordinator.get_day_summaries(), \"supply\": _runtime.get_supply_management_report(), \"upkeep\": _runtime.upkeep_settlement.get_report(), \"maintenance_expense\": _runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE)}" + }, + { + "line": 229, + "text": "\tprint(\"Normal auto purchase chain: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 262, + "text": "\tprint(\"SUPPLY_PREVIEW_READY checks=%d failures=0\" % _checks)" + } + ] + }, + { + "test": "tests/supply/test_normal_supply_chain.gd", + "template": "Normal supply chain: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Normal supply chain: 49 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a27646d207c67be075c5249d8448f38e9bb57586a6ad78ec3c89e96d83b2dc1d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 88, + "text": "\tvar evidence := {\"checks\": _checks, \"failures\": _failures, \"meal_sales\": _sales(&\"cafeteria\"), \"lodging_sales\": _sales(&\"standard_room\"), \"raw_deliveries\": _completed_deliveries(SupplyCatalog.RAW_FOOD), \"meal_deliveries\": _completed_deliveries(SupplyCatalog.BASIC_MEAL), \"cash\": _state.get_resource(&\"gold\"), \"closes\": _runtime.operations_coordinator.get_day_summaries(), \"supply\": _runtime.get_supply_management_report(), \"upkeep\": _runtime.upkeep_settlement.get_report(), \"maintenance_expense\": _runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE)}" + }, + { + "line": 185, + "text": "\tprint(\"Normal supply chain: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 218, + "text": "\tprint(\"SUPPLY_PREVIEW_READY checks=%d failures=0\" % _checks)" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase.gd", + "template": "SUPPLY_AUTO_PURCHASE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "SUPPLY_AUTO_PURCHASE checks=175 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2551b145d583e1b63b0731290ef097603aae302347ea306770f53f750237ddfe", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 364, + "text": "\tprint(\"SUPPLY_AUTO_PURCHASE checks=%d failures=%d\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase_boundaries.gd", + "template": "Supply auto purchase boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply auto purchase boundaries: 120 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "cc1a9e8fda5fc25f9fcfb391463a05957fd2ec649ae50acf2b219c3c9efaa970", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 65, + "text": "\t\tfile.store_string(JSON.stringify({\"checks\": checks, \"failures\": failures, \"actual_user_data_dir\": OS.get_user_data_dir(), \"actual_resource_dir\": ProjectSettings.globalize_path(\"res://\"), \"actual_profile\": profile, \"output\": output, \"external_evidence\": external_evidence, \"observations\": observations, \"normal_business_acceptance\": false, \"p05_full_acceptance\": false}, \"\\t\", true, true) + \"\\n\")" + }, + { + "line": 67, + "text": "\tprint(\"Supply auto purchase boundaries: %d checks, %d failures\" % [checks, failures])" + }, + { + "line": 113, + "text": "\tobservations.append({\"boundary\": label, \"report\": report, \"before_digest\": SimulationDigest.compute(before), \"after_digest\": SimulationDigest.compute(after), \"whole_bytes_equal\": var_to_bytes(before) == var_to_bytes(after), \"checks_so_far\": checks, \"failures_so_far\": failures})" + }, + { + "line": 224, + "text": "\texpect(runtime.staff_service.get_staff(PORTER).state == StaffRuntime.State.AVAILABLE, \"porter physically checks in\")" + } + ] + }, + { + "test": "tests/supply/test_supply_auto_purchase_destruction.gd", + "template": "Supply auto purchase destruction: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply auto purchase destruction: 65 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "229c24e67bc6e59146f91e9ae111794ad39a4d9245c84aa8731b65c43c33a6f5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 85, + "text": "\tsummary[\"checks\"] = checks" + }, + { + "line": 96, + "text": "\tprint(\"P05_AUTO_DESTRUCTION phase=%s checks=%d failures=%d\" % [mode, checks, failures])" + }, + { + "line": 97, + "text": "\tprint(\"Supply auto purchase destruction: %d checks, %d failures\" % [checks, failures])" + } + ], + "auxiliary": [ + { + "template": "P05_AUTO_DESTRUCTION phase=standard checks= failures=", + "required": true, + "equal_primary_check_count": true + } + ] + }, + { + "test": "tests/supply/test_supply_logistics_bridge.gd", + "template": "PASS: checks / failures", + "required": true, + "historical_marker_for_schema_only": "PASS: 121 checks / 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2d6ed12048b9f17e2f44208bb386d9e06eefa7962e21507fc8e63500aa66052e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t_expect(_worker().state == StaffRuntime.State.AVAILABLE and StaffCommuteService.is_at_facility(_worker(), _runtime.facility_service.get_instance(_source)), \"porter checks in physically before taking a supply job\")" + }, + { + "line": 361, + "text": "\tprint(\"%s: %d checks / %d failures\" % [\"PASS\" if _failures == 0 else \"FAIL\", _checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_management_service.gd", + "template": "Supply management service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply management service: 84 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "de70213e9a07cb461614114e55d3b5371249a36250222a6eb48654103faea3ce", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 336, + "text": "\tprint(\"Supply management service: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/supply/test_supply_save_migration.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 20 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a281937594be9f177103e83d0cba48d54f2d69c7303c937d0303b5f8c787f0ae", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 84, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 86, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/transport/test_elevator_service.gd", + "template": "Elevator service: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator service: 123 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "e70de48a8e6f7928690d0495110451c26d9dec180c2dd7033c793155f099c2f6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 25, + "text": "\tprint(\"Elevator service: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_stairs.gd", + "template": "Runtime stairs: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime stairs: 67 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "72f368006ddecd26c3b29f3bcf622e89b8becee337c071d2f246d2b583617bda", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 82, + "text": "\tprint(\"Runtime stairs: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_transport.gd", + "template": "Runtime transport: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime transport: 43 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d9530f391e124d80cecec70c3f40e791298bea9314a065adc7a2b9698eb49d01", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 86, + "text": "\t\tprint(\"Runtime transport: %d checks, %d failures\" % [_checks, _failures])" + }, + { + "line": 190, + "text": "\tprint(\"Runtime transport: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_runtime_transport_comparison.gd", + "template": "Runtime transport comparison: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Runtime transport comparison: 51 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 70, + "text": "\t\t&\"improvement_accepted\": _is_improved(before, after), &\"checks\": _checks, &\"failures\": _failures}" + }, + { + "line": 336, + "text": "\tprint(\"Runtime transport comparison: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_boundaries.gd", + "template": "Transport boundaries: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport boundaries: 57 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1a5a647c45ffab674a8f58b8bc72692c5a30090c9b99a1e6f67194bbe3586b34", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 96, + "text": "\tprint(\"Transport boundaries: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_comparison.gd", + "template": "Path and cabin comparison: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path and cabin comparison: 30 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7e80e9ef80500278c56f0d1ada5c911f43e3c0a9bc370045fdbebd33c2f02fd5", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 59, + "text": "\t\t&\"improvement_accepted\": _is_improved(before, after), &\"checks\": _checks, &\"failures\": _failures}" + }, + { + "line": 270, + "text": "\tprint(\"Path and cabin comparison: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_density_migration.gd", + "template": "Transport density migration: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport density migration: 36 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "0860fdd20576ad2044d44a99d8533393bf95f4b4c69997cc50f2391299343f54", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 106, + "text": "\tprint(\"Transport density migration: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/transport/test_transport_save.gd", + "template": "Transport save: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Transport save: 29 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d4b41a71f2ddc43b1beb14ba8548541ce4875b6640815440e0c14a183c8ddfea", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 93, + "text": "\tprint(\"Transport save: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_actor_selection.gd", + "template": "Actor selection: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Actor selection: 100 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ecc08464c20990f0a4bd5d1e5d8d6fceb6f3636c959790842a08539fc54ad911", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 183, + "text": "\tprint(\"Actor selection: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_anri_recruitment.gd", + "template": "Anri recruitment UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Anri recruitment UI: 22 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 110, + "text": "\tprint(\"Anri recruitment UI: %d checks, %d failures\" % [checks,failures]); quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_approved_free_unique_arrears_ui.gd", + "template": "APPROVED_FREE_UNIQUE_UI phase=write checks= failures=", + "required": true, + "historical_marker_for_schema_only": "APPROVED_FREE_UNIQUE_UI phase=write checks=29 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b713a7591c1d9d871c4301159a0333154ffe622373f1807e6f13a0cf0a3cbb3c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 163, + "text": "\tprint(\"APPROVED_FREE_UNIQUE_UI phase=%s checks=%d failures=%d\" % [\"read\" if \"--read\" in OS.get_cmdline_user_args() else \"write\",checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_business_management.gd", + "template": "Business management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Business management UI: 72 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7ba2445ca0f9591fbc2ca21b3eab3043283f982b94c485c32832493af5989da6", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 298, + "text": "\tprint(\"Business management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_chapel_gameplay.gd", + "template": "Chapel gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Chapel gameplay: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "ae56f5b96d89c7fde3fca3c8fab42fc612ad2d4e066a226fcaa784b96d075b1f", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 85, + "text": "\tprint(\"Chapel gameplay: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_cleaning_gameplay.gd", + "template": "Cleaning gameplay: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Cleaning gameplay: 90 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "781a7386b2b70cae0d4a6a3d17b7540d8a32655368a2c03268e7e6427d85af82", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 129, + "text": "\tprint(\"Cleaning gameplay: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_dashboard_coalescing.gd", + "template": "Dashboard coalescing: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dashboard coalescing: 10 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93899d08d0b14d72cbbac732bda90aff76bdc54da23acd97ec9ac371fe45fb7c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 40, + "text": "\tprint(\"Dashboard coalescing: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_defense_management.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 105 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4511eb00a0539af9fb31302269431e3722988d900996085f9c7b10f5dba86035", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 184, + "text": "\t\t_expect(root.get_texture().get_image().save_png(capture) == OK, \"capture actual defense popup after source-label and saved-selection checks\")" + }, + { + "line": 202, + "text": "\tif _failures == 0: print(\"PASS: %d checks\" % _checks)" + }, + { + "line": 203, + "text": "\telse: push_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_dimension_migration_ui.gd", + "template": "Dimension migration UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Dimension migration UI: 12 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "97320ec735651ef78f17f750d957827a952be24eecf326ec70d7a6f460abbb3e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 54, + "text": "\tprint(\"Dimension migration UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_elevator_management.gd", + "template": "Elevator management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator management UI: 20 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1d7ed05d39f34d91f2ef485f3930de26e7136224f07ff9fec1b768726a365d68", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 94, + "text": "\tprint(\"Elevator management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_first_operating_day.gd", + "template": "First operating day: checks, failures", + "required": true, + "historical_marker_for_schema_only": "First operating day: 63 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f95a04f98bc6d11441e6485c8f8fa809daebb4d26938850e523de557c2827304", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 244, + "text": "\tprint(\"First operating day: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_guest_decision_log.gd", + "template": "Guest decision log: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest decision log: 28 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "79fd5ffce10f86af50e43c32e04837c8c5f8823d5af8c2432beac05b68f10725", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 91, + "text": "\tprint(\"Guest decision log: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_guest_observation.gd", + "template": "Guest observer: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Guest observer: 22 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "64e265aa819fff4a52460ac9bc3c4bf82b12316e38fc364bb17677a22080432c", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 67, + "text": "\tprint(\"Guest observer: %d checks, %d failures\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_layer_presentation.gd", + "template": "Layer presentation: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Layer presentation: 27 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2080a64df66c66a28c6d73fc5678a2e8d221e230e6afb38f2550d12bfd6deea7", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"Layer presentation: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_layer_skin.gd", + "template": "Layer skin: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Layer skin: 16 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f4ddf3882ae7a95eeec04a9cca195efd02cd89c2629db3f184df7eb457ac7593", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 66, + "text": "\tprint(\"Layer skin: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_main_audio_feedback.gd", + "template": "Main audio feedback: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6b190284a9d9a7eb32dce08a73bd3ae7aced23f45fd1c654cc1789da5745a15b", + "source_marker_lines": [ + { + "line": 85, + "text": "\tprint(\"Main audio feedback: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_main_audio_feedback.gd", + "sha256": "6b190284a9d9a7eb32dce08a73bd3ae7aced23f45fd1c654cc1789da5745a15b", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_main_dashboard.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 168 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "867c4794be574b31e2739cb7afff92b1e07fbc3de24adf6660798ce7b9ace11d", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 209, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 211, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_opening_assignment.gd", + "template": "Opening assignment: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Opening assignment: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "143cbd7ca4589540b5f04693dc7c43df8808b87934844d3dd21d33e34f813fa2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 67, + "text": "\tprint(\"Opening assignment: %d checks, %d failures\"%[checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_original_build_pointer.gd", + "template": "Original build viewport input: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Original build viewport input: 36 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a04a79c5e766659d10c4564d1f11c6554a27cb8af71431295e10bc4967012913", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 87, + "text": "\tprint(\"Original build viewport input: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_original_facility_controls.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 14 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "7aed4ad0564e3690f64bc3ab3149e6b36df6ccd8bea0530d1ecd8dd1f87fb576", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 72, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/ui/test_pixel_tower.gd", + "template": "PASS: pixel tower checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 243 pixel tower checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "93bdbb195f2ceec07dbe052e0d0a3017d84b56f1e8ec46b87be8d98105ec0f88", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 60, + "text": "\t\tprint(\"PASS: %d pixel tower checks\" % _checks)" + }, + { + "line": 62, + "text": "\t\tpush_error(\"FAIL: %d of %d pixel tower checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/ui/test_rank_pointer.gd", + "template": "Rank viewport input: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Rank viewport input: 11 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2c51510f80fb57068c6c06aa56566936685e679720b0fd853d8f814566e730f2", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 94, + "text": "\tprint(\"Rank viewport input: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_room_presentation.gd", + "template": "PASS: room presentation checks; failures=", + "required": true, + "historical_marker_for_schema_only": "PASS: 279 room presentation checks; failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fb460cfcc82fcd28624910594558f0542847364d0f6da6c0a68c322508e9bee1", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 152, + "text": "\tprint(\"PASS: %d room presentation checks; failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_selection_autosave.gd", + "template": "First operating day: checks, failures", + "required": true, + "historical_marker_for_schema_only": "First operating day: 75 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [] + }, + { + "test": "tests/ui/test_selection_lifecycle.gd", + "template": "Selection lifecycle: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Selection lifecycle: 38 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b1172419e53fbffccb8f6cda046539fee8be1fe0ad711c3aff7f840b73f77895", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 149, + "text": "\tprint(\"Selection lifecycle: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_management.gd", + "template": "Staff management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff management UI: 131 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "6bd701cf3ac2386768db34b59d6298bed3f59a3aed0795e0854134051cdcf2e0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 324, + "text": "\tprint(\"Staff management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_rescue.gd", + "template": "Rescue UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Rescue UI: 13 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "2ab4a58ef7cce5d1b2e03250dbe92ed6957f38c71e538cdc4f425fa198193bc8", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 80, + "text": "\tprint(\"Rescue UI: %d checks, %d failures\" % [checks,failures])" + } + ] + }, + { + "test": "tests/ui/test_staff_rest.gd", + "template": "Staff rest UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Staff rest UI: 8 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "accfc9c0ee2eed5192085d01d19bbdce65f6171551b22e5a0b564f68c7080eaa", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 39, + "text": "\tprint(\"Staff rest UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/ui/test_stair_transport_details.gd", + "template": "Stair and transport details UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Stair and transport details UI: 28 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8c5f5d695b5baa430dc4a10c01ff9ffdd4461dc33f60efb237552949683d1357", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 158, + "text": "\tprint(\"Stair and transport details UI: %d checks, %d failures\" % [_checks,_failures])" + } + ] + }, + { + "test": "tests/ui/test_supply_auto_purchase_ui.gd", + "template": "SUPPLY_AUTO_PURCHASE_UI checks= failures=", + "required": true, + "historical_marker_for_schema_only": "SUPPLY_AUTO_PURCHASE_UI checks=28 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "a187b5b5badd8d5e1b9539dbb3a7fad9dae64101e97f5b89a87e8bc55ed10132", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/full-regression-r03-review-r01/verification.json", + "sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 137, + "text": "\tprint(\"SUPPLY_AUTO_PURCHASE_UI checks=%d failures=%d\" % [checks, failures])" + } + ] + }, + { + "test": "tests/ui/test_supply_management.gd", + "template": "Supply management UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Supply management UI: 42 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "b5a16eac691f2ab36283729c461b5730c6a66db64ff1b2dc29df0c30308fd9df", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 264, + "text": "\tprint(\"Supply management UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_ui_audio_feedback.gd", + "template": "UI audio feedback: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "source_marker_lines": [ + { + "line": 162, + "text": "\tprint(\"UI audio feedback: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_ui_audio_feedback.gd", + "sha256": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_ui_presentation_audio_contract.gd", + "template": "UI presentation audio contract: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "332047b31123715996d6ee55254b36c043ea481b206dd21a418077ad74e05648", + "source_marker_lines": [ + { + "line": 165, + "text": "\tprint(\"UI presentation audio contract: %d checks, %d failures\" % [_checks, _failures])" + } + ], + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-and-presentation-candidate-r06/candidate/tests/ui/test_ui_presentation_audio_contract.gd", + "sha256": "332047b31123715996d6ee55254b36c043ea481b206dd21a418077ad74e05648", + "candidate_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "scope": "artifact-only unadopted audio suite source declaration; raw actual acceptance separate" + } + }, + { + "test": "tests/ui/test_upkeep_closed_ui.gd", + "template": "PASS: checks (phase write)", + "required": true, + "historical_marker_for_schema_only": "PASS: 23 checks (phase write)\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "3127cabc2c04361403160ca0f0def65a3dbb6b37f545fd4ca964e15dc3a055ef", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 134, + "text": "\tprint(\"%s: %d checks (phase %s)\" % [\"PASS\" if failures == 0 else \"FAIL\", checks, \"read\" if \"--read\" in OS.get_cmdline_user_args() else \"write\"])" + } + ] + }, + { + "test": "tests/ui/test_upkeep_inspector.gd", + "template": "PASS: checks", + "required": true, + "historical_marker_for_schema_only": "PASS: 6 checks\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "bd27b4c44cca6b95c83a8d2e328f8222079b2c36768d26504fdb3fdc8a9739ff", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 53, + "text": "\tprint(\"%s: %d checks\" % [\"PASS\" if failures == 0 else \"FAIL\", checks])" + } + ] + }, + { + "test": "tests/ui/test_world_tree_rank.gd", + "template": "World tree rank UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "World tree rank UI: 27 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "1f628d0b4f326b021db7d9b34cff9cc3f94a5197edbfb193979a3ce0ac9cf289", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 89, + "text": "\tprint(\"World tree rank UI: %d checks, %d failures\" % [checks,failures]);quit(failures)" + } + ] + }, + { + "test": "tests/world/test_elevator_topology.gd", + "template": "Elevator topology: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Elevator topology: 93 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "4710a6bba103d82a4d2febea1a2c30d029c0f1ccd2a3d9a6c1712b4fb372033b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 38, + "text": "\tprint(\"Elevator topology: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_legacy_stairs.gd", + "template": "Legacy stairs: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Legacy stairs: 85 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "258d759537aec8b7b90cab107985c9c6a16f10e85ae7ae92a9c54cc94417d48e", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 37, + "text": "\tprint(\"Legacy stairs: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_path_transit_density.gd", + "template": "Path transit density: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path transit density: 46 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "8de5fc8bdf8631ff41fcd4862cea40daa3316040f381f182dc6fe09dba9f3cb9", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 15, + "text": "\tprint(\"Path transit density: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_path_vertical_policy.gd", + "template": "Path vertical policy: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Path vertical policy: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "140cdf552309f89cd93bf1a4d38827a671a66461203f0858f1c2651a8e6e8473", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 13, + "text": "\tprint(\"Path vertical policy: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/world/test_stair_infrastructure.gd", + "template": "Stair infrastructure: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Stair infrastructure: 106 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "f6d1a29b43875b7b1fc945611c2ed15fbdcb8bb7220addf283b30fedf2ccba28", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json", + "sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "scope": "historical exact label/schema; check count is not an expected future value" + }, + "source_marker_lines": [ + { + "line": 43, + "text": "\tprint(\"Stair infrastructure: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_event_log_ui.gd", + "template": "Event log UI: checks, failures", + "required": true, + "historical_marker_for_schema_only": "Event log UI: 34 checks, 0 failures\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p20-log-limited-native-actual-root-review-r03/review.json", + "bytes": 993, + "sha256": "8eec3a8492aa74f571eb21447c6911cea64d176e0f813152f226b2b4122ba387", + "scope": "successful limited actual UI marker/source schema only; full169 pending" + }, + "source_marker_lines": [ + { + "line": 120, + "text": "\tprint(\"Event log UI: %d checks, %d failures\" % [_checks, _failures])" + } + ] + }, + { + "test": "tests/ui/test_paired_bgm.gd", + "template": "PASS: checks", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "fcafe858caef15f99faa9ad8aa7971828e9053adc1357077f0a26f0f17d9bbc0", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p22-paired-bgm-playback-main-window-actual-root-review-r01/review.json", + "sha256": "aca77047dc5e5efc1c0c2a6aeeb8e8c34c51e74243ab762f596150194869823b", + "scope": "same PASS completion shape; original63 core assertions excluded from dedicated new BGM suite; actual count from raw only" + }, + "source_marker_lines": [ + { + "line": 23, + "text": "\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + }, + { + "line": 321, + "text": "\t\tprint(\"PASS: %d checks\" % _checks)" + }, + { + "line": 323, + "text": "\t\tpush_error(\"FAIL: %d of %d checks failed\" % [_failures, _checks])" + } + ] + }, + { + "test": "tests/mvp/test_tomari_floor_trouble.gd", + "template": "TOMARI_FLOOR_TROUBLE checks= failures=", + "required": true, + "historical_marker_for_schema_only": "TOMARI_FLOOR_TROUBLE checks=64 failures=0\n", + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "09581fbc32b29d2b5773e0859f22ca58c947927d11e6523027ffe277bd7ac303", + "authority": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-tomari-floor-trouble-focused-actual-root-review-r07/review.json", + "bytes": 1367, + "sha256": "419bab962014351da3d727e4d9cb51e686410271dac809b526b3668600fc583d", + "scope": "actual focused controlled Runtime/native47 then64check marker; counts remain collected from raw, normal joining not accepted" + }, + "source_marker_lines": [ + { + "line": 135, + "text": "\tprint(\"TOMARI_FLOOR_TROUBLE checks=%d failures=%d\" % [checks,failures])" + } + ] + }, + { + "test": "tests/s1/test_p08_combined_migration.gd", + "template": "P08 combined migration: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "9284a8cfb478b4c8d69f6d8166eec6c41107e3902029289b3c0e9ba48c370855", + "authority": { + "path": "docs/spec/acceptance/P08-02.json", + "scope": "new standalone mixed suite; native execution pending" + } + }, + { + "test": "tests/ui/test_p08_normal_placement_ops.gd", + "template": "P08 normal placement: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + }, + { + "test": "tests/transport/test_runtime_build_preview.gd", + "template": "Runtime build preview: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "c881b60bce7276047884303a6823da9abfaaf8c1e85dbc5f2c0ff13f0d742449", + "authority": { + "path": "docs/spec/acceptance/P08-03.json", + "scope": "normal placement; native and OS pending" + } + } + ,{ + "test": "tests/ui/test_p23_life_observation.gd", + "template": "P23 life observation: checks, failures", + "required": true, + "fixed_check_count_used": false, + "source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8", + "authority": { + "path": "docs/spec/acceptance/P23-03.json", + "scope": "component and read-only observation only; native unexecuted; whole unit blocked" + } + } + ], + "unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting", + "roster_count_is_not_hardcoded": true, + "original155_authority_sha256": "aa0c90bb4106f592e083bfdc5983d8cc90dcf857477d779d1851ce33edb0d3ad", + "current164_review_sha256": "13ae528efd59ec486d705a28e10fb63fed57a5a3f0bba4be1b99518d3fc9561b", + "audio3_manifest_sha256": "5e2cf82041c617008099742a4a5611af789922ab1c37740997344caa6470e060", + "production_changes": [], + "engine_runs": 0, + "mimi_purchase_v2_candidate_binding": { + "existing4_GD_source_changes": { + "autoload/simulation_runtime.gd": { + "base_sha256": "dafee4833aa68fb97c8a8c43efc4aa6b9d29d83e00129bd2b99fd99210946b89", + "candidate_sha256": "8efbe3aafe519d5b67ba96025a3324b9b724e169aa757b2d31262066ab7b83bb" + }, + "scripts/core/save_service.gd": { + "base_sha256": "79a8c3654868c81d3e623a429d8fdfe51f15d098cef8d5b60be091d5200b285d", + "candidate_sha256": "3e3d2aeeff7b7610b48bba24ca83610225f93aa8e8deef50c93186ee360fd8b7" + }, + "scripts/supply/supply_management_service.gd": { + "base_sha256": "bd99c1418537415b574ff936c74817c721071fdcf4b31b1891347a7b6ae84e30", + "candidate_sha256": "e4ad420191020485c027004f64bb00fad9da195fe956ef10dc0c787412018f7f" + }, + "scripts/ui/supply_management_panel.gd": { + "base_sha256": "b3425f797f2e56990bffd64a9770fcfd0e4b487b7420e848b43bb7c44f41bc4d", + "candidate_sha256": "c23c84c5afb2960b51384d80703c3e454059b46eb04e237df52f62291de7db06" + } + }, + "old169_suite_source_SHA_rewritten": false, + "source_schema_scope": "prospective Runtime15/formal2; original v1-only14/formal1/generic/legacy1-13 immutable branches remain required", + "actual_new_marker": null, + "actual_check_count": null, + "actual_new_UID3": null, + "adoption": false, + "source_r06_seal": { + "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p17-mimi-purchase-v2-source-candidate-r06/input-seal.json", + "sha256": "7c01c096524d9560fb691f7fb1e0b5a718592b964574f8fe0153beb1ec76a60d", + "bytes": 3818 + } + } +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt new file mode 100644 index 0000000..fff37f0 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/main.gd.source.txt @@ -0,0 +1,3202 @@ +extends Control + +const UIAudioFeedbackScript := preload("res://scripts/ui/ui_audio_feedback.gd") +const UIAudioSettingsDialogScript := preload("res://scripts/ui/ui_audio_settings_dialog.gd") +const UIAudioOwnerShutdownScript := preload("res://scripts/ui/ui_audio_owner_shutdown.gd") +const PairedBGMScript := preload("res://scripts/ui/paired_bgm.gd") +var _paired_bgm: Variant +signal ui_audio_window_close_drained(report: Dictionary) +var audio_settings_path := "user://settings/audio.cfg" +var _audio_feedback: Variant +var _audio_settings_dialog: Variant +var _window_close_in_progress := false + +const RankDialogScript := preload("res://scripts/ui/world_tree_rank_dialog.gd") +const EventLogDialogScript := preload("res://scripts/ui/event_log_dialog.gd") +const ManualSaveSlotsDialogScript := preload("res://scripts/ui/manual_save_slots_dialog.gd") + +const OpeningGuideScript := preload("res://scripts/ui/inn_opening_guide.gd") +const OperatingLedgerPanelScript := preload("res://scripts/ui/operating_ledger_panel.gd") +const StaffManagementPanelScript := preload("res://scripts/ui/staff_management_panel.gd") +const SupplyManagementPanelScript := preload("res://scripts/ui/supply_management_panel.gd") +const BusinessManagementPanelScript := preload("res://scripts/ui/business_management_panel.gd") +const DefenseManagementPanelScript := preload("res://scripts/ui/defense_management_panel.gd") +const WorldPreviewScript := preload("res://scenes/main/world_preview.gd") +const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd") +const DefaultVisualTheme := preload("res://assets/themes/japanese_fantasy/visual-theme-v1.tres") +var _last_presented_close_minute := 0 + +const MANUAL_SAVE_PATH := "user://saves/manual_ui" +const MVP_FACILITY_IDS: Array[StringName] = [ + &"cleaning_room", + &"chapel", + &"reception", + &"standard_room", + &"premium_room", + &"cafeteria", + &"warehouse", + &"kitchen", + &"public_bath", + &"staff_waiting_room", + &"staff_dormitory", + &"infirmary", + &"security_station", + &"tavern", + &"massage_room", + &"guest_elevator", + &"service_elevator", + &"guest_stairs", + &"service_stairs", + &"staff_door", + &"guest_corridor", + &"service_corridor", + &"adult_private_room", +] +const FACILITY_STATE_NAMES := [ + "故障", + "到達不能", + "魔力不足", + "人員不足", + "閉鎖中", + "低下運転", + "営業中", +] +const ELEVATOR_PHASE_NAMES := {&"IDLE": "待機", &"PICKUP": "迎車中", &"BOARDING": "乗車中", &"TRAVEL": "運行中", &"ALIGHTING": "降車中"} +const FACILITY_REASON_NAMES := { + &"BROKEN": "故障", + &"DESTROYED": "破壊による休止", + &"RAIDED": "摘発による休止", + &"RELATED_FACILITY_INACTIVE": "関連施設が未稼働", + &"NO_ACCESS": "経路なし", + &"NO_POWER": "魔力供給なし", + &"NO_STAFF": "必要人員不足", + &"CLOSED_BY_OPERATOR": "手動閉鎖", + &"OUTSIDE_OPENING_HOURS": "営業時間外", + &"LOW_DURABILITY": "耐久度低下", +} +const PREVIEW_REASON_NAMES := { + "INVALID_DEFINITION": "施設データが不正です", + "MISSING_DEPENDENCY": "建設システムを利用できません", + "OUT_OF_BOUNDS": "世界の範囲外です", + "ZONE_MISMATCH": "地上・境界・地下の条件が合いません", + "LAYER_MISMATCH": "表層/裏層が合いません", + "OVERLAP": "ほかの施設または工事と重なります", + "UNSUPPORTED": "構造を支えられません", + "INSUFFICIENT_RESOURCES": "建設資源が不足しています", +} +const EXCAVATION_REASON_NAMES := { + &"OUT_OF_BOUNDS": "世界の範囲外です", + &"NOT_UNDERGROUND": "地下のマスだけ掘削できます", + &"ALREADY_EXCAVATED": "すでに掘削済みです", + &"ALREADY_RESERVED": "このマスは掘削中です", + &"NO_EXCAVATED_NEIGHBOR": "掘削済みの隣接マスから延ばしてください", + &"NO_ENTRANCE_CONNECTION": "地上入口までつながる経路がありません", + &"SUPPORT_REQUIRED": "硬い地盤を支える補強施設が必要です", + &"INSUFFICIENT_RESOURCES": "掘削資源が不足しています", + &"MISSING_DEPENDENCY": "掘削システムを利用できません", +} +const EVENT_CHOICE_NAMES := { + &"event.relationship.threshold.acknowledge": "向き合う", + &"event.relationship.threshold.defer": "今は保留する", +} +const STAFF_ROLE_NAMES := { + &"attendant": "接客", + &"bartender": "酒場", + &"clerk": "会計", + &"cleaner": "清掃", + &"cook": "調理", + &"guard": "警備", + &"maintenance": "保守", + &"masseur": "施術", + &"medic": "医療", + &"porter": "物流", + &"receptionist": "受付", &"innkeeper": "女将", &"cleric": "聖職者", +} +const STAFF_SHIFT_NAMES := { + &"day": "日勤", + &"night": "夜勤", + &"any": "柔軟勤務", +} +const STAFF_SKILL_NAMES := { + &"service": "接客", + &"operation": "運営", + &"combat": "戦闘", + &"underworld": "裏社会", +} + +@export var visual_theme: VisualThemeProfile = DefaultVisualTheme +var manual_save_path := MANUAL_SAVE_PATH +var manual_save_slots_dialog: ConfirmationDialog + +var time_label: Label +var phase_label: Label +var rank_button: Button +var rank_dialog: ConfirmationDialog +var event_log_dialog: AcceptDialog +var weather_label: Label +var guest_count_label: Button +var dimension_migration_dialog: ConfirmationDialog +var _dimension_migration_digest := "" +var guest_observer: AcceptDialog +var lodging_count_label: Label +var alert_count_label: Label +var defense_manager: AcceptDialog +var alert_icon_node: TextureRect +var resource_labels: Dictionary = {} +var resource_icon_nodes: Dictionary = {} +var world_preview: WorldPreview +var front_button: Button +var back_button: Button +var excavation_button: Button +var demolition_button: Button +var demolition_dialog: ConfirmationDialog +var _demolition_mode := false +var _demolition_target: StringName = &"" +var _pending_demolition_commands: Dictionary = {} +var pan_button: Button +var pause_button: Button +var speed_buttons: Dictionary = {} +var dashboard_summary_label: Label +var foreground_event_panel: VBoxContainer +var foreground_event_label: Label +var foreground_event_choices: VBoxContainer +var staff_management_panel: VBoxContainer +var staff_management_toggle_button: Button +var _staff_management_panel_open := false +var staff_candidate_panel: VBoxContainer +var staff_candidate_summary_label: Label +var staff_candidate_toggle_button: Button +var staff_candidate_buttons: Dictionary = {} +var selected_heading_label: Label +var selected_detail_label: Label +var related_facility_picker: OptionButton +var _pending_related_commands: Dictionary = {} +var selected_entity_icon: TextureRect +var selected_elevator_controls: VBoxContainer +var elevator_operation_button: Button +var elevator_operation_hint: Label +var build_status_label: Label +var build_buttons: Dictionary = {} +var navigation_buttons: Dictionary = {} +var _interface_layer := -1 +var opening_guide: Control +var operating_ledger_panel: VBoxContainer +var supply_management_panel: VBoxContainer +var business_management_panel: VBoxContainer +var _business_panel_open := false +var _supply_panel_open := false +var operations_controls: VBoxContainer +var _ledger_panel_open := false +var management_panel: Control +var management_toggle_button: Button +var palette_scroll: ScrollContainer +var palette_toggle_button: Button +var _management_panel_open := false +var _build_palette_open := false + +var _runtime: Variant = null +var _selected_entity_id: StringName = &"" +var _selected_entity_kind: StringName = &"" +var _selected_definition_id: StringName = &"" +var _selected_definition: Variant = null +var _build_rotation := 0 +var _build_sequence := 1 +var _excavation_sequence := 1 +var _hire_sequence := 1 +var _elevator_sequence := 1 +var _pending_elevator_commands: Dictionary = {} +var _pending_build_commands: Dictionary = {} +var _excavation_mode := false +var _pending_excavation_commands: Dictionary = {} +var _pending_hire_commands: Dictionary = {} +var _staff_candidate_panel_open := false +var _operations_start_report: Dictionary = {} + + +func _ready() -> void: + _runtime = get_node_or_null("/root/SimulationRuntime") + _pause_fresh_opening() + if _runtime != null and _runtime.has_method("enable_production_save_policy"): + _runtime.enable_production_save_policy() + if _runtime != null \ + and _runtime.has_method("get_operations_report") \ + and _runtime.has_method("enable_production_operations"): + var current_operations: Dictionary = _runtime.call(&"get_operations_report") + if not bool(current_operations.get(&"enabled", false)): + _operations_start_report = _runtime.call(&"enable_production_operations") + _synchronize_ui_command_state() + _setup_ui_audio() + UIAudioOwnerShutdownScript.register_owner(get_tree(), self) + get_tree().root.close_requested.connect(_on_window_close_requested) + _build_interface() + _connect_signals() + _populate_build_palette() + _populate_staff_candidates() + _on_time_changed(GameClock.day, GameClock.get_hour(), GameClock.get_minute()) + _on_phase_changed(GameClock.get_phase()) + for resource_name: StringName in GameState.resources: + _on_resource_changed(resource_name, GameState.get_resource(resource_name)) + _set_layer(GameState.active_layer) + _update_pause_and_speed() + _refresh_dashboard() + if not _operations_start_report.is_empty() \ + and not bool(_operations_start_report.get(&"ok", false)): + _set_status( + "本番運営を開始できませんでした: %s" % _result_message(_operations_start_report), + false + ) + _focus_initial_view.call_deferred() + + +func _exit_tree() -> void: + var tree := get_tree() + if tree != null: + if tree.root.close_requested.is_connected(_on_window_close_requested): + tree.root.close_requested.disconnect(_on_window_close_requested) + UIAudioOwnerShutdownScript.unregister_owner(tree, self) + + +func _on_window_close_requested() -> void: + # The shared coordinator drains every registered Main before one quit. + # Main stays alive while its audio child is freed and the backend releases. + var tree := get_tree() + UIAudioOwnerShutdownScript.close_owners(tree) + + +func _report_audio_close_drained(report: Dictionary) -> void: + ui_audio_window_close_drained.emit(report) + + +func _prepare_owned_audio_close() -> Array[Dictionary]: + _window_close_in_progress = true + set_process(false) + set_process_input(false) + set_process_unhandled_input(false) + set_process_unhandled_key_input(false) + hide() + var audio: Node = _audio_feedback as Node + _audio_feedback = null + var targets := UIAudioOwnerShutdownScript.capture_and_release(audio) + if is_instance_valid(_paired_bgm): + targets.append_array(_paired_bgm.capture_owned_targets()) + _paired_bgm.prepare_owner_shutdown() + _paired_bgm.queue_free() + _paired_bgm = null + return targets + + +func _focus_initial_view() -> void: + await get_tree().process_frame + world_preview.focus_operating_area() + + +func _pause_fresh_opening() -> void: + if _runtime == null or GameClock.absolute_minute != 360: + return + if not (_runtime.get(&"latest_restore_safety_report") as Dictionary).is_empty(): + return + var operations: Dictionary = _runtime.call(&"get_operations_report") + if bool(operations.get(&"enabled", false)): + return + if not _call_array(_service(&"construction_service"), &"get_facilities").is_empty() \ + or not _call_array(_service(&"construction_service"), &"get_jobs").is_empty() \ + or not _call_array(_service(&"staff_service"), &"get_staff_members").is_empty(): + return + var queue: Variant = _runtime.get(&"command_queue") + if queue != null and not queue.get_accepted_command_metadata().is_empty(): + return + GameClock.set_paused(true) + + +func _unhandled_key_input(event: InputEvent) -> void: + if not event.pressed or event.echo: + return + var handled := true + match event.keycode: + KEY_ESCAPE: + _clear_active_tool() + KEY_E: + _toggle_excavation_mode() + KEY_H: + _toggle_staff_candidate_panel() + KEY_B: + _toggle_build_palette() + KEY_I: + _set_management_panel_open(not _management_panel_open) + KEY_SPACE: + _toggle_pause() + KEY_TAB: + _set_layer(1 - world_preview.layer_mode) + KEY_1: + _set_speed(1) + KEY_2: + _set_speed(2) + KEY_3: + _set_speed(3) + KEY_R: + if _selected_definition == null: + handled = false + else: + _rotate_build_preview() + _: + handled = false + if handled: + if _audio_feedback != null: + _audio_feedback.request_click() + get_viewport().set_input_as_handled() + + +func _build_interface() -> void: + var background := ColorRect.new() + background.name = "InterfaceBackground" + background.color = _visual_color(&"window_background", Color("24170f")) + background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + background.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(background) + + var root_margin := MarginContainer.new() + root_margin.add_theme_constant_override("margin_left", 6) + root_margin.add_theme_constant_override("margin_top", 6) + root_margin.add_theme_constant_override("margin_right", 6) + root_margin.add_theme_constant_override("margin_bottom", 6) + root_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(root_margin) + + var layout := VBoxContainer.new() + layout.add_theme_constant_override("separation", 4) + root_margin.add_child(layout) + layout.add_child(_create_top_bar()) + + var content := HBoxContainer.new() + content.name = "MainContent" + content.size_flags_vertical = Control.SIZE_EXPAND_FILL + content.add_theme_constant_override("separation", 4) + layout.add_child(content) + content.add_child(_create_navigation_rail()) + + var map_panel := PanelContainer.new() + map_panel.name = "MapPanel" + map_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + map_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + map_panel.add_theme_stylebox_override( + "panel", + _themed_panel_style(&"panel_outer", _visual_color(&"window_background", Color("1e1712")), _visual_color(&"border_gold", Color("9b6a32")), 8, 5) + ) + content.add_child(map_panel) + world_preview = WorldPreviewScript.new() + world_preview.name = "WorldPreview" + world_preview.custom_minimum_size = Vector2(600, 380) + world_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL + world_preview.size_flags_vertical = Control.SIZE_EXPAND_FILL + world_preview.set_visual_theme(visual_theme) + map_panel.add_child(world_preview) + opening_guide = OpeningGuideScript.new() + world_preview.add_child(opening_guide) + opening_guide.setup(_runtime, GameClock, GameState.active_layer) + opening_guide.position = Vector2(8, 8) + opening_guide.size = Vector2(302, 0) + opening_guide.action_requested.connect(_on_opening_action) + # The inspector overlays the map only when requested, leaving the full + # cross-section available while watching the simulation. + management_panel = _create_side_panel() + world_preview.add_child(management_panel) + management_panel.set_anchors_and_offsets_preset(Control.PRESET_RIGHT_WIDE) + management_panel.offset_left = -320 + management_panel.offset_right = -8 + management_panel.offset_top = 8 + management_panel.offset_bottom = -8 + management_panel.visible = false + + layout.add_child(_create_bottom_bar()) + + +func _create_top_bar() -> Control: + var panel := PanelContainer.new() + panel.name = "TopBar" + panel.custom_minimum_size.y = 46 + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_wood", _visual_color(&"dark_panel", Color("6b3e1d")), _visual_color(&"border_gold", Color("d5a552")), 8, 8)) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 7) + panel.add_child(row) + + var title_box := VBoxContainer.new() + title_box.add_theme_constant_override("separation", 0) + row.add_child(title_box) + var title := Label.new() + title.text = "THE TREE" + title.custom_minimum_size.x = 98 + title.add_theme_font_size_override("font_size", 17) + title.add_theme_color_override("font_color", Color("fff0c8")) + title_box.add_child(title) + rank_button=Button.new() + rank_button.name="WorldTreeRankButton" + rank_button.text="世界樹ランク 1" + rank_button.flat=true + rank_button.alignment=HORIZONTAL_ALIGNMENT_LEFT + rank_button.add_theme_font_size_override("font_size",10) + rank_button.add_theme_stylebox_override("normal",StyleBoxEmpty.new()) + rank_button.tooltip_text="世界樹のランクと昇格条件を確認" + title_box.add_child(rank_button) + rank_dialog=RankDialogScript.new() + rank_dialog.configure(_runtime) + rank_dialog.configure_audio(Callable(_audio_feedback, "submit_command")) + _audio_feedback.bind_button(rank_dialog.get_ok_button()) + _audio_feedback.bind_button(rank_dialog.get_cancel_button()) + rank_dialog.status_requested.connect(func(message: String, success: bool) -> void: + _set_status(message,success) + _refresh_dashboard() + ) + add_child(rank_dialog) + rank_button.pressed.connect(rank_dialog.show_rank) + + row.add_child(VSeparator.new()) + time_label = Label.new() + time_label.custom_minimum_size.x = 104 + time_label.add_theme_font_size_override("font_size", 12) + time_label.add_theme_color_override("font_color", Color("fff5d9")) + row.add_child(time_label) + phase_label = Label.new() + phase_label.custom_minimum_size.x = 48 + phase_label.add_theme_font_size_override("font_size", 12) + phase_label.add_theme_color_override("font_color", Color("ffd37a")) + row.add_child(phase_label) + weather_label = _hud_value_label("晴れ", 48) + weather_label.name = "WeatherLabel" + weather_label.tooltip_text = "嵐の間は新規入館が停止します。現在のお客様は館内に残ります。" + weather_label.mouse_filter = Control.MOUSE_FILTER_STOP + row.add_child(weather_label) + + guest_count_label = Button.new() + guest_count_label.text = "来客 0人" + guest_count_label.tooltip_text = "来客の欲求と現在の目的地を見る" + guest_count_label.custom_minimum_size.x = 72 + guest_count_label.add_theme_font_size_override("font_size", 12) + guest_count_label.pressed.connect(_show_guest_observer) + lodging_count_label = _hud_value_label("宿泊 0人", 72) + row.add_child(guest_count_label) + row.add_child(lodging_count_label) + for resource_name: StringName in [&"gold", &"mana", &"materials", &"faith"]: + var resource_block := HBoxContainer.new() + resource_block.add_theme_constant_override("separation", 2) + var resource_icon := TextureRect.new() + resource_icon.name = "%sIcon" % String(resource_name).capitalize() + resource_icon.texture = visual_theme.resource_icon(resource_name) if visual_theme != null else null + resource_icon.custom_minimum_size = Vector2(16, 16) + resource_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + resource_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + resource_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + resource_icon_nodes[resource_name] = resource_icon + resource_block.add_child(resource_icon) + var label := _hud_value_label("", 54) + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + resource_labels[resource_name] = label + resource_block.add_child(label) + row.add_child(resource_block) + alert_count_label = _hud_value_label("警戒 0", 64) + alert_count_label.mouse_filter = Control.MOUSE_FILTER_STOP + alert_count_label.focus_mode = Control.FOCUS_ALL + alert_count_label.gui_input.connect(_on_defense_notice_input) + alert_count_label.add_theme_color_override("font_color", Color("ffb095")) + alert_icon_node = _icon_rect(visual_theme.control_icon(&"alert") if visual_theme != null else null, 20) + alert_icon_node.name = "AlertIcon" + row.add_child(alert_icon_node) + row.add_child(alert_count_label) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(spacer) + pause_button = _button("停止", _toggle_pause, 52) + row.add_child(pause_button) + for value: int in [1, 2, 3]: + var speed_button := _button("x%d" % value, _set_speed.bind(value), 42) + speed_button.toggle_mode = true + speed_buttons[value] = speed_button + row.add_child(speed_button) + return panel + + +func _create_navigation_rail() -> Control: + var panel := PanelContainer.new() + panel.name = "NavigationRail" + panel.custom_minimum_size.x = 64 + panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_leather", _visual_color(&"dark_panel_alt", Color("513018")), _visual_color(&"border_gold", Color("bd8540")), 8, 7)) + var column := VBoxContainer.new() + column.add_theme_constant_override("separation", 4) + panel.add_child(column) + var entries := [ + [&"staff", "従業員"], + [&"facility", "施設"], + [&"product", "商品"], + [&"business", "営業"], + [&"explore", "探索"], + [&"investment", "投資"], + [&"ledger", "帳簿"], + ] + for entry: Array in entries: + var mode := StringName(entry[0]) + var button := _button(String(entry[1]), _activate_management_mode.bind(mode), 50) + button.custom_minimum_size.y = 46 + button.toggle_mode = true + button.add_theme_font_size_override("font_size", 12) + _apply_button_icon(button, visual_theme.navigation_icon(&"ledger" if mode == &"business" else mode) if visual_theme != null else null, 16) + navigation_buttons[mode] = button + column.add_child(button) + var nav_spacer := Control.new() + nav_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL + column.add_child(nav_spacer) + var layer_hint := Label.new() + layer_hint.text = "TAB\n表 / 裏" + layer_hint.tooltip_text = "ホイール: 拡大縮小\n画面移動ボタン → 左ドラッグ: 移動\n中ボタンドラッグ: 移動\nHome: 全体表示" + layer_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + layer_hint.add_theme_font_size_override("font_size", 10) + layer_hint.add_theme_color_override("font_color", Color("e2c38c")) + column.add_child(layer_hint) + return panel + + +func _create_side_panel() -> Control: + var panel := PanelContainer.new() + panel.name = "SidePanel" + panel.custom_minimum_size.x = 312 + panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_parchment", _visual_color(&"light_panel", Color("f4e3bd")), _visual_color(&"border_gold", Color("a96f2f")), 8, 10)) + var scroll := ScrollContainer.new() + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + panel.add_child(scroll) + var column := VBoxContainer.new() + column.custom_minimum_size.x = 278 + column.size_flags_horizontal = Control.SIZE_EXPAND_FILL + column.add_theme_constant_override("separation", 8) + scroll.add_child(column) + var root_column := column + + var inspector_heading := HBoxContainer.new() + var inspector_title := _section_heading("運営状況") + inspector_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL + inspector_heading.add_child(inspector_title) + inspector_heading.add_child(_button("閉じる I", _set_management_panel_open.bind(false), 64)) + column.add_child(inspector_heading) + dashboard_summary_label = Label.new() + dashboard_summary_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + dashboard_summary_label.add_theme_color_override("font_color", Color("4d341f")) + dashboard_summary_label.add_theme_font_size_override("font_size", 13) + column.add_child(dashboard_summary_label) + foreground_event_panel = VBoxContainer.new() + foreground_event_panel.name = "ForegroundEventPanel" + foreground_event_panel.add_theme_constant_override("separation", 6) + foreground_event_panel.visible = false + column.add_child(foreground_event_panel) + foreground_event_panel.add_child(_section_heading("要対応イベント")) + foreground_event_label = Label.new() + foreground_event_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + foreground_event_label.add_theme_color_override("font_color", Color("8f3f29")) + foreground_event_label.add_theme_font_size_override("font_size", 13) + foreground_event_panel.add_child(foreground_event_label) + foreground_event_choices = VBoxContainer.new() + foreground_event_choices.name = "ForegroundEventChoices" + foreground_event_choices.add_theme_constant_override("separation", 5) + foreground_event_panel.add_child(foreground_event_choices) + column.add_child(HSeparator.new()) + operations_controls = VBoxContainer.new() + operations_controls.name = "OperationsControls" + operations_controls.add_theme_constant_override("separation", 8) + column.add_child(operations_controls) + column = operations_controls + + var candidate_heading_row := HBoxContainer.new() + candidate_heading_row.add_child(_section_heading("スタッフ")) + var candidate_heading_spacer := Control.new() + candidate_heading_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + candidate_heading_row.add_child(candidate_heading_spacer) + var defense_button := _button("防衛", _show_defense_manager, 52) + defense_button.name = "DefenseManagementButton" + candidate_heading_row.add_child(defense_button) + staff_management_toggle_button = _button("雇用済み", _toggle_staff_management_panel, 62) + staff_management_toggle_button.name = "StaffManagementToggleButton" + staff_management_toggle_button.toggle_mode = true + candidate_heading_row.add_child(staff_management_toggle_button) + staff_candidate_toggle_button = _button( + "候補を見る H", + _toggle_staff_candidate_panel, + 108 + ) + staff_candidate_toggle_button.name = "StaffCandidateToggleButton" + candidate_heading_row.add_child(staff_candidate_toggle_button) + column.add_child(candidate_heading_row) + staff_management_panel = StaffManagementPanelScript.new() + staff_management_panel.configure(_runtime, _apply_button_style) + staff_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + staff_management_panel.status_requested.connect(_on_staff_management_status) + staff_management_panel.visible = false + column.add_child(staff_management_panel) + staff_candidate_panel = VBoxContainer.new() + staff_candidate_panel.name = "StaffCandidatePanel" + staff_candidate_panel.visible = false + staff_candidate_panel.add_theme_constant_override("separation", 7) + column.add_child(staff_candidate_panel) + staff_candidate_summary_label = Label.new() + staff_candidate_summary_label.name = "StaffCandidateSummary" + staff_candidate_summary_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + staff_candidate_summary_label.add_theme_color_override("font_color", Color("765438")) + staff_candidate_summary_label.add_theme_font_size_override("font_size", 12) + staff_candidate_panel.add_child(staff_candidate_summary_label) + var candidate_list := VBoxContainer.new() + candidate_list.name = "StaffCandidateList" + candidate_list.add_theme_constant_override("separation", 7) + staff_candidate_panel.add_child(candidate_list) + column.add_child(HSeparator.new()) + + selected_heading_label = _section_heading("選択中の施設") + column.add_child(selected_heading_label) + var selected_row := HBoxContainer.new() + selected_row.add_theme_constant_override("separation", 8) + column.add_child(selected_row) + selected_entity_icon = TextureRect.new() + selected_entity_icon.name = "SelectedEntityIcon" + selected_entity_icon.custom_minimum_size = Vector2(24, 24) + selected_entity_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + selected_entity_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + selected_entity_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + selected_row.add_child(selected_entity_icon) + selected_detail_label = Label.new() + selected_detail_label.mouse_filter = Control.MOUSE_FILTER_PASS + selected_detail_label.text = "地図上の施設をクリックすると、営業状態を確認できます。" + selected_detail_label.custom_minimum_size.y = 112 + selected_detail_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + selected_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + selected_detail_label.add_theme_color_override("font_color", Color("4d341f")) + selected_detail_label.add_theme_font_size_override("font_size", 13) + selected_row.add_child(selected_detail_label) + related_facility_picker = OptionButton.new() + related_facility_picker.name = "RelatedFacilityPicker" + related_facility_picker.visible = false + related_facility_picker.tooltip_text = "自施設の稼働に必要な関連施設を指定します。休止状態は解除しません。" + related_facility_picker.item_selected.connect(_on_related_facility_selected) + column.add_child(related_facility_picker) + selected_elevator_controls = VBoxContainer.new() + selected_elevator_controls.name = "SelectedElevatorControls" + selected_elevator_controls.visible = false + column.add_child(selected_elevator_controls) + elevator_operation_button = _button("昇降機を停止", _on_elevator_operation_pressed, 160) + elevator_operation_button.name = "ElevatorOperationButton" + selected_elevator_controls.add_child(elevator_operation_button) + elevator_operation_hint = Label.new() + elevator_operation_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + elevator_operation_hint.add_theme_font_size_override("font_size", 12) + elevator_operation_hint.add_theme_color_override("font_color", Color("654d37")) + selected_elevator_controls.add_child(elevator_operation_hint) + + operating_ledger_panel = OperatingLedgerPanelScript.new() + root_column.add_child(operating_ledger_panel) + operating_ledger_panel.setup(_runtime, GameClock, _apply_button_style, GameState.active_layer) + operating_ledger_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + operating_ledger_panel.status_requested.connect(_on_staff_management_status) + operating_ledger_panel.visible = false + supply_management_panel = SupplyManagementPanelScript.new() + supply_management_panel.configure(_runtime, _apply_button_style) + supply_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + supply_management_panel.status_requested.connect(_on_staff_management_status) + supply_management_panel.action_requested.connect(_activate_management_mode) + supply_management_panel.visible = false + root_column.add_child(supply_management_panel) + business_management_panel = BusinessManagementPanelScript.new() + business_management_panel.configure(_runtime, _apply_button_style) + business_management_panel.configure_audio(Callable(_audio_feedback, "submit_command")) + business_management_panel.status_requested.connect(_on_staff_management_status) + business_management_panel.action_requested.connect(_activate_management_mode) + business_management_panel.visible = false + root_column.add_child(business_management_panel) + return panel + + +func _create_bottom_bar() -> Control: + var panel := PanelContainer.new() + panel.name = "BottomBar" + panel.add_theme_stylebox_override("panel", _themed_panel_style(&"panel_wood", _visual_color(&"dark_panel", Color("24382e")), _visual_color(&"border_gold", Color("7d8862")), 0, 5)) + var column := VBoxContainer.new() + column.add_theme_constant_override("separation", 4) + panel.add_child(column) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 5) + column.add_child(row) + + palette_toggle_button = _button("建築 B", _toggle_build_palette, 76) + palette_toggle_button.name = "BuildPaletteToggleButton" + palette_toggle_button.toggle_mode = true + row.add_child(palette_toggle_button) + front_button = _button("客用・表層", _set_layer.bind(0), 96) + back_button = _button("裏方・裏層", _set_layer.bind(1), 96) + _apply_button_icon(front_button, visual_theme.control_icon(&"front_layer") if visual_theme != null else null, 16) + _apply_button_icon(back_button, visual_theme.control_icon(&"back_layer") if visual_theme != null else null, 16) + front_button.toggle_mode = true + back_button.toggle_mode = true + row.add_child(front_button) + row.add_child(back_button) + excavation_button = _button("掘削 E", _toggle_excavation_mode, 72) + excavation_button.name = "ExcavationButton" + excavation_button.toggle_mode = true + _apply_button_icon(excavation_button, visual_theme.control_icon(&"excavate") if visual_theme != null else null, 16) + row.add_child(excavation_button) + row.add_child(VSeparator.new()) + var rotate_button := _button("回転 R", _rotate_build_preview, 72) + _apply_button_icon(rotate_button, visual_theme.control_icon(&"rotate") if visual_theme != null else null, 16) + row.add_child(rotate_button) + row.add_child(_button("解除 Esc", _clear_active_tool, 80)) + pan_button = _button("画面移動", _toggle_pan_mode, 90) + pan_button.name = "PanModeButton" + pan_button.toggle_mode = true + pan_button.tooltip_text = "左ドラッグとホイールで画面を上下移動。Ctrl+ホイールで拡大縮小。もう一度押すか Esc で解除、Homeで全体表示。" + row.add_child(pan_button) + var focus_button := _button("営業階", func() -> void: world_preview.focus_operating_area(), 64) + focus_button.name = "FocusOperatingAreaButton" + focus_button.tooltip_text = "現在の層の建物を見やすい大きさで表示。Homeで全体表示。" + row.add_child(focus_button) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(spacer) + management_toggle_button = _button("運営 I", _toggle_management_panel, 72) + management_toggle_button.name = "ManagementToggleButton" + management_toggle_button.toggle_mode = true + row.add_child(management_toggle_button) + event_log_dialog = EventLogDialogScript.new() + event_log_dialog.configure(_runtime) + add_child(event_log_dialog) + var event_log_button := _button("記録", event_log_dialog.show_events, 62) + event_log_button.name = "EventLogButton" + event_log_button.tooltip_text = "最近のイベントを確認" + row.add_child(event_log_button) + var save_button := _button("保存", _save_manual, 62) + save_button.name = "ManualSaveButton" + _apply_button_icon(save_button, visual_theme.control_icon(&"save") if visual_theme != null else null, 16) + row.add_child(save_button) + var audio_button := _button("音設定", _show_audio_settings, 62) + audio_button.name = "AudioSettingsButton" + row.add_child(audio_button) + var load_menu := MenuButton.new() + load_menu.name = "SaveLoadMenu" + load_menu.text = "読込 ▾" + load_menu.custom_minimum_size = Vector2(64,28) + load_menu.add_theme_font_size_override("font_size",12) + _apply_button_style(load_menu) + load_menu.get_popup().add_item("手動セーブを読み込む",0) + load_menu.get_popup().add_item("旧寸法を修正して読み込む",1) + load_menu.get_popup().add_item("保存スロットを選んで読込",2) + load_menu.get_popup().id_pressed.connect(func(id: int): + if id == 0: _load_manual() + elif id == 1: _preview_dimension_migration() + elif id == 2: _show_manual_load_slots()) + row.add_child(load_menu) + + palette_scroll = ScrollContainer.new() + palette_scroll.name = "BuildPaletteScroll" + palette_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO + palette_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + palette_scroll.custom_minimum_size.y = 62 + palette_scroll.visible = false + column.add_child(palette_scroll) + var palette_row := HBoxContainer.new() + palette_row.name = "BuildPaletteRow" + palette_row.add_theme_constant_override("separation", 4) + palette_scroll.add_child(palette_row) + var grid := GridContainer.new() + grid.name = "BuildPalette" + grid.columns = MVP_FACILITY_IDS.size() + grid.add_theme_constant_override("h_separation", 4) + grid.add_theme_constant_override("v_separation", 0) + palette_row.add_child(grid) + demolition_button = _button("撤去", _toggle_demolition_mode, 72) + demolition_button.name = "DemolitionButton" + demolition_button.toggle_mode = true + palette_row.add_child(demolition_button) + + build_status_label = Label.new() + build_status_label.name = "BuildStatusLabel" + build_status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + build_status_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + build_status_label.add_theme_font_size_override("font_size", 11) + build_status_label.add_theme_color_override("font_color", Color("c7d3a8")) + column.add_child(build_status_label) + return panel + + +func _toggle_build_palette() -> void: + _set_build_palette_open(not _build_palette_open) + + +func _set_build_palette_open(open: bool) -> void: + _build_palette_open = open + if palette_scroll != null: + palette_scroll.visible = open + if palette_toggle_button != null: + palette_toggle_button.set_pressed_no_signal(open) + palette_toggle_button.text = "建築を閉じる B" if open else "建築 B" + if not open: + var bottom := find_child("BottomBar", true, false) as Control + if bottom != null: bottom.reset_size.call_deferred() + + +func _toggle_management_panel() -> void: + _set_management_panel_open(not _management_panel_open) + + +func _set_management_panel_open(open: bool) -> void: + _management_panel_open = open + if management_panel != null: + management_panel.visible = open + if management_toggle_button != null: + management_toggle_button.set_pressed_no_signal(open) + if not open: + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + + +func _activate_management_mode(mode: StringName) -> void: + _set_management_panel_open(mode != &"facility") + _set_supply_panel_open(mode == &"product") + _set_business_panel_open(mode == &"business") + _set_ledger_panel_open(mode == &"ledger") + for raw_mode: Variant in navigation_buttons: + (navigation_buttons[raw_mode] as Button).set_pressed_no_signal(StringName(raw_mode) == mode) + match mode: + &"staff": + _set_staff_management_panel_open(true) + &"facility": + _set_build_palette_open(true) + _set_staff_candidate_panel_open(false) + _set_status("建築する施設を下のパレットから選んでください。", true, true) + &"business": + _set_status("施設ごとの料金と新規受付の開閉を設定できます。", true, true) + &"product": + _set_status("倉庫への仕入れ、厨房の自動生産、施設への配送を確認できます。", true, true) + &"explore": + _set_status("探索管理は一覧画面の実装準備中です。", true, true) + &"investment": + _set_status("投資管理は一覧画面の実装準備中です。", true, true) + &"ledger": + _set_status("本日の資金増減と、00:00に確定した営業日の損益を表示します。", true, true) + + +func _set_ledger_panel_open(open: bool) -> void: + _ledger_panel_open = open + if open: + _set_supply_panel_open(false) + _set_business_panel_open(false) + if operating_ledger_panel != null: + operating_ledger_panel.visible = open + if open: + operating_ledger_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _supply_panel_open and not _business_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _supply_panel_open and not _business_panel_open + if open: + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + + +func _set_supply_panel_open(open: bool) -> void: + _supply_panel_open = open + if open: + _set_business_panel_open(false) + _set_ledger_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + if supply_management_panel != null: + supply_management_panel.visible = open + if open: + supply_management_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _ledger_panel_open and not _business_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _ledger_panel_open and not _business_panel_open + + +func _set_business_panel_open(open: bool) -> void: + _business_panel_open = open + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_staff_management_panel_open(false) + if business_management_panel != null: + business_management_panel.visible = open + if open: + business_management_panel.refresh() + if operations_controls != null: + operations_controls.visible = not open and not _ledger_panel_open and not _supply_panel_open + if dashboard_summary_label != null: + dashboard_summary_label.visible = not open and not _ledger_panel_open and not _supply_panel_open + + +func _on_opening_action(action: StringName) -> void: + match action: + &"build_reception", &"build_room", &"build_corridor", &"build_stairs": + _set_ledger_panel_open(false) + _set_management_panel_open(false) + _set_build_palette_open(true) + var definitions := {&"build_reception": &"reception", &"build_room": &"standard_room", &"build_corridor": &"guest_corridor", &"build_stairs": &"guest_stairs"} + _select_build_definition(definitions[action]) + &"staff": + _activate_management_mode(&"staff") + if _call_array(_service(&"staff_service"), &"get_staff_members").is_empty(): + _set_staff_candidate_panel_open(true) + &"resume": + if GameClock.paused: + _toggle_pause() + &"ledger": + _activate_management_mode(&"ledger") + + +func _connect_signals() -> void: + GameClock.time_changed.connect(_on_time_changed) + GameClock.phase_changed.connect(_on_phase_changed) + GameState.resource_changed.connect(_on_resource_changed) + world_preview.facility_selected.connect(_on_facility_selected) + world_preview.facility_selected.connect(_on_audio_world_selection) + world_preview.staff_assignment_dropped.connect(func(data: Dictionary, facility_id: StringName) -> void: + staff_management_panel.assign_from_drop(data, facility_id) + ) + world_preview.legacy_stair_selected.connect(_on_legacy_stair_selected) + world_preview.legacy_stair_selected.connect(_on_audio_world_selection) + world_preview.construction_selected.connect(_on_construction_selected) + world_preview.construction_selected.connect(_on_audio_world_selection) + world_preview.cell_clicked.connect(_on_map_cell_clicked) + world_preview.build_preview_changed.connect(_on_build_preview_changed) + world_preview.excavation_preview_changed.connect(_on_excavation_preview_changed) + if _runtime != null and _runtime is Object: + if _runtime.has_signal(&"state_updated"): + _runtime.connect(&"state_updated", Callable(self, "_on_snapshot_published")) + elif _runtime.has_signal(&"snapshot_published"): + _runtime.connect(&"snapshot_published", Callable(self, "_on_snapshot_published")) + if _runtime.has_signal(&"checkpoint_presentation_requested"): + _runtime.connect(&"checkpoint_presentation_requested", Callable(self, "_on_checkpoint_presentation_requested")) + if _runtime.has_signal(&"save_operation_completed"): + _runtime.connect(&"save_operation_completed", Callable(self, "_on_save_operation_completed")) + if _runtime.has_signal(&"command_completed"): + _runtime.connect(&"command_completed", Callable(self, "_on_command_completed")) + + +func _populate_build_palette() -> void: + var palette := find_child("BuildPalette", true, false) as GridContainer + if palette == null: + return + var registry: Variant = _service(&"definition_registry") + for definition_id: StringName in MVP_FACILITY_IDS: + var definition: Variant = null + if registry != null and registry.has_method(&"get_facility"): + definition = registry.call(&"get_facility", definition_id) + if definition == null: + continue + var button := Button.new() + button.toggle_mode = true + button.custom_minimum_size = Vector2(116, 50) + button.text = _build_button_text(definition) + button.tooltip_text = String(_definition_value(definition, &"description", "")) + button.add_theme_font_size_override("font_size", 12) + _apply_button_icon(button, visual_theme.facility_icon(definition_id) if visual_theme != null else null, 22) + _apply_button_style(button) + button.toggled.connect(_on_build_button_toggled.bind(definition_id)) + build_buttons[definition_id] = button + palette.add_child(button) + if build_buttons.is_empty(): + _set_status("施設定義を読み込めませんでした。", false) + else: + _set_status("B 建築 / H 雇用 / I 運営 ホイール 拡大縮小・中ボタン 移動・Home 全体表示", true, true) + + +func _populate_staff_candidates() -> void: + var list := find_child("StaffCandidateList", true, false) as VBoxContainer + if list == null: + return + for child: Node in list.get_children(): + list.remove_child(child) + child.queue_free() + staff_candidate_buttons.clear() + var summaries := _staff_candidate_summaries() + for summary: Dictionary in summaries: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + if candidate_id.is_empty(): + continue + var card := PanelContainer.new() + card.name = "Candidate_%s" % String(candidate_id) + card.add_theme_stylebox_override( + "panel", + _themed_panel_style(&"panel_leather", Color("132630"), Color("31505d"), 7, 8) + ) + var content := VBoxContainer.new() + content.add_theme_constant_override("separation", 4) + card.add_child(content) + var detail := Label.new() + detail.name = "CandidateDetail_%s" % String(candidate_id) + detail.text = _staff_candidate_text(summary) + detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + detail.add_theme_color_override("font_color", Color("c8d7dc")) + detail.add_theme_font_size_override("font_size", 12) + content.add_child(detail) + var hire_button := Button.new() + hire_button.name = "Hire_%s" % String(candidate_id) + hire_button.text = "無料で加入" if bool(summary.get(&"unique", false)) else "雇用する" + hire_button.custom_minimum_size.y = 30 + _apply_button_style(hire_button) + hire_button.pressed.connect(_hire_staff_candidate.bind(candidate_id)) + content.add_child(hire_button) + staff_candidate_buttons[candidate_id] = hire_button + list.add_child(card) + _refresh_staff_candidates() + + +func _staff_candidate_summaries() -> Array[Dictionary]: + if _runtime == null or not _runtime.has_method(&"get_mvp_staff_candidate_summaries"): + return [] + var value: Variant = _runtime.call(&"get_mvp_staff_candidate_summaries") + var result: Array[Dictionary] = [] + if value is Array: + for item: Variant in value: + if item is Dictionary: + result.append((item as Dictionary).duplicate(true)) + return result + + +func _staff_candidate_text(summary: Dictionary) -> String: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + var display_name := String(summary.get(&"display_name", candidate_id)) + var role_id := StringName(summary.get(&"role_id", &"")) + var shift_id := StringName(summary.get(&"preferred_shift", &"any")) + var skills_value: Variant = summary.get(&"skills", {}) + var skills: Dictionary = skills_value if skills_value is Dictionary else {} + var skill_parts: Array[String] = [] + for skill_id: StringName in [&"service", &"operation", &"combat", &"underworld"]: + skill_parts.append("%s%d" % [ + String(STAFF_SKILL_NAMES.get(skill_id, String(skill_id))), + int(skills.get(skill_id, 0)), + ]) + return "%s(%d歳) %s / %s\n契約 G%d 日給 G%d\n技能 %s" % [ + display_name, + int(summary.get(&"age", 0)), + String(STAFF_ROLE_NAMES.get(role_id, String(role_id))), + String(STAFF_SHIFT_NAMES.get(shift_id, String(shift_id))), + int(summary.get(&"signing_cost", 0)), + int(summary.get(&"daily_wage", 0)), + "・".join(skill_parts), + ] + + +func _toggle_staff_management_panel() -> void: + _set_staff_management_panel_open(not _staff_management_panel_open) + + +func _set_staff_management_panel_open(open: bool) -> void: + _staff_management_panel_open = open + if staff_management_panel != null: + staff_management_panel.visible = open + if staff_management_toggle_button != null: + staff_management_toggle_button.set_pressed_no_signal(open) + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + _set_staff_candidate_panel_open(false) + _set_management_panel_open(true) + staff_management_panel.refresh() + _set_status("従業員を選び、配属先と毎日の勤務帯を変更できます。", true, true) + + +func _on_staff_management_status(message: String, success: bool) -> void: + _set_status(message, success) + + +func _toggle_staff_candidate_panel() -> void: + _set_staff_candidate_panel_open(not _staff_candidate_panel_open) + + +func _set_staff_candidate_panel_open(open: bool) -> void: + _staff_candidate_panel_open = open + if staff_candidate_panel != null: + staff_candidate_panel.visible = open + _refresh_staff_candidates() + if open: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_business_panel_open(false) + _set_staff_management_panel_open(false) + _set_management_panel_open(true) + _set_status("雇用候補を選んでください。契約金と日給は確定前に表示されます。", true, true) + + +func _refresh_staff_candidates() -> void: + if staff_candidate_panel == null or staff_candidate_summary_label == null: + return + var summaries := _staff_candidate_summaries() + var hired_count := 0 + for summary: Dictionary in summaries: + var candidate_id := StringName(summary.get(&"candidate_id", &"")) + var hired := bool(summary.get(&"hired", false)) + if hired: + hired_count += 1 + var button := staff_candidate_buttons.get(candidate_id) as Button + if button == null: + continue + var pending := _pending_hire_commands.values().has(candidate_id) + button.disabled = hired or pending + button.text = ("加入済み" if bool(summary.get(&"unique", false)) else "雇用済み") if hired else "手続き中…" if pending else ("無料で加入" if bool(summary.get(&"unique", false)) else "雇用する") + staff_candidate_summary_label.text = "未雇用 %d人 / 雇用済み %d人" % [ + maxi(summaries.size() - hired_count, 0), + hired_count, + ] + if staff_candidate_toggle_button != null: + staff_candidate_toggle_button.text = ( + "候補を閉じる H" if _staff_candidate_panel_open + else "候補を見る H(%d)" % maxi(summaries.size() - hired_count, 0) + ) + + +func _hire_staff_candidate(candidate_id: StringName) -> void: + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("雇用手続きを送信できません。", false) + return + if _pending_hire_commands.values().has(candidate_id): + _set_status("この候補の雇用手続きはすでに受理されています。", false) + return + var summaries := _staff_candidate_summaries() + var selected: Dictionary = {} + for summary: Dictionary in summaries: + if StringName(summary.get(&"candidate_id", &"")) == candidate_id: + selected = summary + break + if selected.is_empty(): + _set_status("雇用候補を読み込めませんでした。", false) + return + if bool(selected.get(&"hired", false)): + _set_status("%sはすでに雇用済みです。" % String(selected.get(&"display_name", candidate_id)), false) + _refresh_staff_candidates() + return + var sequence := _hire_sequence + _hire_sequence += 1 + var idempotency_key := StringName( + "ui.hire.%s.%08d" % [String(candidate_id), sequence] + ) + var result: Variant = _submit_ui_command(&"hire_staff_candidate", + {&"candidate_id": candidate_id}, + &"player", + candidate_id, + 75, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("雇用手続きへ応答がありません。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_hire_failure_message(result), false) + _refresh_staff_candidates() + return + var display_name := String(selected.get(&"display_name", candidate_id)) + if result.details is Dictionary and result.details.has(&"staff_id"): + _set_status("%sを雇用しました(契約 G%d / 日給 G%d)。" % [ + display_name, + int(selected.get(&"signing_cost", 0)), + int(selected.get(&"daily_wage", 0)), + ], true) + else: + _pending_hire_commands[int(result.command_id)] = candidate_id + _set_status("%sの雇用手続きを受理しました。" % display_name, true) + _refresh_dashboard() + + +func _hire_failure_message(result: Variant) -> String: + var details: Variant = result.details + if details is Dictionary: + var reason := StringName(details.get(&"reason", &"")) + match reason: + &"ARREARS_REMAIN", &"UNPAID_EXPENSES": + return "雇用不可: 未払経費 G%dを清算してください。" % int(details.get(&"arrears_total", 0)) + &"BANKRUPT": + return "雇用不可: 資金破綻後は新しい契約を結べません。" + &"EXTERNAL_ROLLBACK_FAILED": + return "雇用不可: 運営データの復旧が必要です。" + var message := String(result.message).to_lower() + if int(result.code) == int(CommandResult.Code.DUPLICATE_COMMAND_ID): + return "この候補はすでに雇用済みです。" + if "insufficient" in message or "balance" in message: + return "雇用不可: 契約金が不足しています。" + return _command_failure_message(result) + + +func _build_button_text(definition: Variant) -> String: + var name := String(_definition_value(definition, &"display_name", _definition_value(definition, &"id", "施設"))) + var cost_value: Variant = _definition_value(definition, &"construction_cost", {}) + var gold := 0 + var materials := 0 + if cost_value is Dictionary: + gold = int(cost_value.get(&"gold", cost_value.get("gold", 0))) + materials = int(cost_value.get(&"materials", cost_value.get("materials", 0))) + return "%s\nG %d / 資材 %d" % [name, gold, materials] + + +func _on_build_button_toggled(pressed: bool, definition_id: StringName) -> void: + if not pressed: + if _selected_definition_id == definition_id: + _clear_build_selection() + return + _select_build_definition(definition_id) + + +func _select_build_definition(definition_id: StringName) -> void: + var registry: Variant = _service(&"definition_registry") + if registry == null or not registry.has_method(&"get_facility"): + _set_status("建設パレットを利用できません。", false) + return + var definition: Variant = registry.call(&"get_facility", definition_id) + if definition == null: + _set_status("選択した施設定義が見つかりません。", false) + return + _set_demolition_mode(false, false) + _set_excavation_mode(false, false) + _set_pan_mode(false) + _selected_definition_id = definition_id + _selected_definition = definition + _build_rotation = 0 + for raw_id: Variant in build_buttons: + (build_buttons[raw_id] as Button).set_pressed_no_signal(StringName(raw_id) == definition_id) + var target_layer := clampi(int(_definition_value(definition, &"layer", 0)), 0, 1) + _set_layer(target_layer) + world_preview.set_build_definition(definition, _build_rotation) + _set_status("%sを選択中。配置するマスをクリックしてください。" % String(_definition_value(definition, &"display_name", definition_id)), true, true) + + +func _clear_build_selection(announce := true) -> void: + _selected_definition_id = &"" + _selected_definition = null + _build_rotation = 0 + for button_value: Variant in build_buttons.values(): + (button_value as Button).set_pressed_no_signal(false) + if world_preview != null: + world_preview.clear_build_definition() + if announce: + _set_status("建設選択を解除しました。", true, true) + + +func _clear_active_tool() -> void: + if _demolition_mode: + _set_demolition_mode(false) + return + if world_preview.pan_mode: + _set_pan_mode(false) + _set_status("画面移動を解除しました。施設をクリックすると情報を表示します。", true, true) + return + if _supply_panel_open or _business_panel_open: + _set_management_panel_open(false) + return + if _staff_management_panel_open: + _set_staff_management_panel_open(false) + return + if _staff_candidate_panel_open: + _set_staff_candidate_panel_open(false) + return + if _excavation_mode: + _set_excavation_mode(false) + return + _clear_build_selection() + _set_build_palette_open(false) + _set_management_panel_open(false) + + +func _toggle_excavation_mode() -> void: + _set_excavation_mode(not _excavation_mode) + + +func _toggle_pan_mode() -> void: + _set_pan_mode(not world_preview.pan_mode) + _set_status("画面移動中:左ドラッグで移動、ホイールで上下移動、Ctrl+ホイールで拡大縮小。Escで解除。" if world_preview.pan_mode else "画面移動を解除しました。施設をクリックすると情報を表示します。", true, true) + + +func _set_pan_mode(enabled: bool) -> void: + if enabled: + _set_demolition_mode(false, false) + _clear_build_selection(false) + _set_excavation_mode(false, false) + _set_build_palette_open(false) + world_preview.set_pan_mode(enabled) + if pan_button != null: + pan_button.set_pressed_no_signal(enabled) + + +func _set_excavation_mode(enabled: bool, announce := true) -> void: + if enabled: + _set_demolition_mode(false, false) + _set_pan_mode(false) + _excavation_mode = enabled + if excavation_button != null: + excavation_button.set_pressed_no_signal(_excavation_mode) + if world_preview != null: + world_preview.set_excavation_mode(_excavation_mode) + if not _excavation_mode: + if announce: + _set_status("掘削モードを解除しました。", true, true) + return + _clear_build_selection(false) + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "掘削モード" + selected_detail_label.text = "地下のマスにカーソルを合わせると、費用と所要時間を確認できます。" + var gate_message := _excavation_gate_message() + _set_status( + gate_message if not gate_message.is_empty() else "地下の掘削候補を選んでください。FRONT / BACKは同時に開通します。", + gate_message.is_empty(), + true + ) + + +func _rotate_build_preview() -> void: + if _selected_definition == null: + _set_status("先に建設する施設を選択してください。", false, true) + return + var allowed_value: Variant = _definition_value(_selected_definition, &"allowed_rotations", [0]) + var allowed: Array[int] = [] + if allowed_value is Array: + for rotation_value: Variant in allowed_value: + allowed.append(int(rotation_value)) + allowed.sort() + if allowed.is_empty(): + allowed = [0] + var index := allowed.find(_build_rotation) + _build_rotation = allowed[(index + 1) % allowed.size()] + world_preview.set_build_rotation(_build_rotation) + _set_status("回転 %d°。地図のマスをクリックして建設します。" % _build_rotation, true, true) + + +func _on_map_cell_clicked(cell: Vector3i) -> void: + if _audio_feedback != null: + _audio_feedback.request_click() + if _demolition_mode: + _preview_demolition(cell) + return + if _excavation_mode: + _start_excavation(cell) + return + if _selected_definition == null: + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_elevator_controls.visible = false + selected_entity_icon.texture = null + selected_heading_label.text = "選択中のマス" + selected_detail_label.text = "座標 (%d, %d)\nレイヤー %s\n\n施設のないマスです。" % [cell.x, cell.y, "FRONT / 表層" if cell.z == 0 else "BACK / 裏層"] + return + var preview := world_preview.get_build_preview_report(cell) + if not bool(preview.get(&"can_build", false)): + if _audio_feedback != null: + _audio_feedback.reject_local(&"BUILD_PREVIEW_REJECTED") + _set_status(_preview_failure_message(preview), false) + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var idempotency_key := StringName("ui.build.%08d" % _build_sequence) + _build_sequence += 1 + var result: Variant = _submit_ui_command(&"build_facility", + {&"definition_id": _selected_definition_id, &"origin": cell, &"rotation": _build_rotation}, + &"player", + &"", + 50, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("建設指示を送信できませんでした。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_command_failure_message(result), false) + return + var name := String(_definition_value(_selected_definition, &"display_name", _selected_definition_id)) + if result.details is Dictionary and result.details.has(&"job_id"): + _set_status("%sの建設を開始しました(%d, %d / %d°)。" % [name, cell.x, cell.y, _build_rotation], true) + else: + _pending_build_commands[int(result.command_id)] = _selected_definition_id + _set_status("%sの建設指示を受理しました(%d, %d / %d°)。" % [name, cell.x, cell.y, _build_rotation], true) + _refresh_dashboard() + + +func _on_build_preview_changed(report: Dictionary) -> void: + if _selected_definition == null or world_preview.get_hovered_cell() == null: + return + if bool(report.get(&"can_build", false)): + var cell: Vector3i = world_preview.get_hovered_cell() + var connection: Dictionary = report.get(&"connection", {}) + var warning := " · 入口への経路未接続" if connection.get(&"status", &"") == &"DISCONNECTED" else " · 経路確認不可" if connection.get(&"status", &"") == &"UNAVAILABLE" else "" + _set_status("配置可能 (%d, %d)・%d°%s — クリックで建設" % [cell.x, cell.y, _build_rotation, warning], true, true) + else: + _set_status(_preview_failure_message(report), false, true) + + +func _on_excavation_preview_changed(report: Dictionary) -> void: + if not _excavation_mode or world_preview.get_hovered_cell() == null: + return + var gate_message := _excavation_gate_message() + if not gate_message.is_empty(): + _set_status("掘削不可: %s%s" % [gate_message, _excavation_cost_and_time(report)], false, true) + return + var cell: Vector3i = world_preview.get_hovered_cell() + if bool(report.get(&"ok", false)): + _set_status( + "掘削可能 (%d, %d)・%s%s — クリックでFRONT / BACKを開通" % [ + cell.x, + cell.y, + "FRONT / 表層" if cell.z == 0 else "BACK / 裏層", + _excavation_cost_and_time(report), + ], + true, + true + ) + else: + _set_status(_excavation_failure_message(report), false, true) + + +func _start_excavation(cell: Vector3i) -> void: + var report := world_preview.get_excavation_preview_report(cell) + var gate_message := _excavation_gate_message() + if not gate_message.is_empty(): + _set_status("掘削不可: %s%s" % [gate_message, _excavation_cost_and_time(report)], false) + return + if not bool(report.get(&"ok", false)): + _set_status(_excavation_failure_message(report), false) + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var spatial_position := Vector2i(cell.x, cell.y) + if _pending_excavation_commands.values().has(spatial_position): + _set_status("このマスの掘削指示はすでに受理されています。", false) + return + var idempotency_key := _excavation_idempotency_key(cell) + var result: Variant = _submit_ui_command(&"start_excavation", + {&"cell": Vector3i(cell.x, cell.y, world_preview.layer_mode)}, + &"player", + &"", + 50, + -1, + idempotency_key + ) + if result == null or not result.has_method(&"is_success"): + _set_status("掘削指示を送信できませんでした。", false) + return + if not bool(result.call(&"is_success")): + _set_status(_excavation_command_failure_message(result), false) + return + if result.details is Dictionary and result.details.has(&"job_id"): + _select_excavation_job(StringName(result.details.get(&"job_id", &""))) + _set_status("掘削を開始しました(%d, %d / FRONT・BACK)。" % [cell.x, cell.y], true) + else: + _pending_excavation_commands[int(result.command_id)] = spatial_position + _set_status("掘削指示を受理しました(%d, %d / FRONT・BACK)。" % [cell.x, cell.y], true) + _refresh_dashboard() + + +func _excavation_idempotency_key(cell: Vector3i) -> StringName: + # One click gets one stable retry identity. A later player retry receives a + # new sequence so a previously cached validation failure cannot block it. + var sequence := _excavation_sequence + _excavation_sequence += 1 + return StringName( + "ui.excavation.%d.%d.%08d" % [cell.x, cell.y, sequence] + ) + + +func _on_facility_selected(instance_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = instance_id + _selected_entity_kind = &"facility" + _refresh_selected_entity() + + +func _on_legacy_stair_selected(stair_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = stair_id + _selected_entity_kind = &"legacy_stairs" + world_preview.selected_entity_id = stair_id + _refresh_selected_entity() + + +func _on_construction_selected(job_id: StringName) -> void: + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + _set_excavation_mode(false, false) + _selected_entity_id = job_id + _selected_entity_kind = &"construction" + _refresh_selected_entity() + + +func _refresh_dashboard() -> void: + if event_log_dialog != null and event_log_dialog.visible: + event_log_dialog.refresh() + if rank_button != null and _service(&"progression_service") != null: + rank_button.text="世界樹ランク %d" % _service(&"progression_service").current_rank + if rank_dialog != null and rank_dialog.visible: rank_dialog.refresh() + if weather_label != null and is_instance_valid(_runtime) and _runtime.has_method("get_weather_report"): + var weather_report: Dictionary = _runtime.get_weather_report() + var weather_names := {&"clear": "晴れ", &"rain": "雨", &"storm": "嵐", &"mana_fog": "マナ霧"} + weather_label.text = weather_names.get(weather_report.get(&"kind", &""), "天候 —") + var guests := _call_array(_service(&"guest_service"), &"get_guests") + var queued_guests := 0 + var served_guests := 0 + for guest: Variant in guests: + if guest == null: + continue + if int(guest.state) == 4: + queued_guests += 1 + elif int(guest.state) == 5: + served_guests += 1 + var staff_members := _call_array(_service(&"staff_service"), &"get_staff_members") + var available_staff := 0 + var working_staff := 0 + for staff: Variant in staff_members: + if staff == null: + continue + if int(staff.state) == 2: + available_staff += 1 + elif int(staff.state) in [3, 4]: + working_staff += 1 + var event_service: Variant = _service(&"event_service") + var events := _call_array(event_service, &"get_instances") + var active_events := 0 + for event_instance: Variant in events: + if event_instance != null and int(event_instance.state) in [0, 1, 2, 7]: + active_events += 1 + var active_jobs := 0 + for job: Variant in _call_array(_service(&"construction_service"), &"get_jobs"): + if job != null and int(job.state) not in [2, 3]: + active_jobs += 1 + var heat_text := "—" + var heat_value := 0 + var authority: Variant = _service(&"authority_service") + if authority != null and authority.has_method(&"get_authority_state"): + var heat_state: Variant = authority.call(&"get_authority_state") + if heat_state != null: + heat_value = int(heat_state.heat) + heat_text = "%d / 100" % heat_value + var foreground_text := "なし" + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + if foreground != null: + foreground_text = String(foreground.resolved_event_id) + _refresh_foreground_event(event_service) + var defense_summary: Dictionary = {} + var defense: Variant = _service(&"defense_service") + if defense != null and defense.has_method(&"get_threat_summary"): + defense_summary = defense.call(&"get_threat_summary") + var threat_count := int(defense_summary.get(&"total", 0)) + var defense_text := "防衛 準備中 %d / 戦闘中 %d / 次波待ち %d" % [ + int(defense_summary.get(&"preparing", 0)), int(defense_summary.get(&"active", 0)), + int(defense_summary.get(&"between_waves", 0))] + var operations_text := _operations_dashboard_text() + dashboard_summary_label.text = ( + "客 %d人(待機 %d / 利用 %d)\n" % [guests.size(), queued_guests, served_guests] + + "従業員 %d人(待機 %d / 作業 %d)\n" % [staff_members.size(), available_staff, working_staff] + + "イベント %d件 前景: %s\n" % [active_events, foreground_text] + + "警戒度 %s  建設中 %d件" % [heat_text, active_jobs] + # Keep a stable row so threat changes do not move staff drag targets. + + "\n" + defense_text + + operations_text + ) + if guest_count_label != null: + guest_count_label.text = "来客 %d人" % guests.size() + if lodging_count_label != null: + var lodging_guests := 0 + for guest: Variant in guests: + if guest != null and int(guest.planned_exit_at) >= GameClock.absolute_minute + 360: + lodging_guests += 1 + lodging_count_label.text = "宿泊 %d人" % lodging_guests + if alert_count_label != null: + alert_count_label.text = "警戒 %d" % heat_value + (" / 襲撃 %d" % threat_count if threat_count > 0 else "") + alert_count_label.tooltip_text = (defense_text + "\n防衛参加中の従業員は配属変更できません。") if threat_count > 0 else "進行中の襲撃はありません。" + alert_count_label.add_theme_color_override( + "font_color", + Color("ffb498") if heat_value >= 60 or threat_count > 0 else Color("ebca84") if heat_value >= 30 else Color("c7d3a8") + ) + _refresh_staff_candidates() + _refresh_selected_entity() + _update_pause_and_speed() + if opening_guide != null: + opening_guide.refresh() + if operating_ledger_panel != null and _ledger_panel_open: + operating_ledger_panel.refresh() + + +func _refresh_foreground_event(event_service: Variant) -> void: + if foreground_event_panel == null \ + or foreground_event_label == null \ + or foreground_event_choices == null: + return + for child: Node in foreground_event_choices.get_children(): + foreground_event_choices.remove_child(child) + child.queue_free() + var foreground: Variant = null + if event_service != null and event_service.has_method(&"get_foreground_instance"): + foreground = event_service.call(&"get_foreground_instance") + if foreground == null or int(foreground.state) != 2: + foreground_event_panel.visible = false + return + foreground_event_panel.visible = true + if _audio_feedback != null: + _audio_feedback.observe_foreground(foreground) + _set_ledger_panel_open(false) + _set_supply_panel_open(false) + _set_management_panel_open(true) + var actors: Array[String] = [] + for actor_value: Variant in foreground.actor_ids: + actors.append(String(actor_value)) + foreground_event_label.text = ( + "%s\n関係者: %s" % [ + String(foreground.resolved_event_id), + "・".join(actors) if not actors.is_empty() else "なし", + ] + ) + var definition: Variant = null + if event_service.has_method(&"get_definition"): + definition = event_service.call(&"get_definition", foreground.resolved_event_id) + var choices_value: Variant = _definition_value(definition, &"choices", []) + if not choices_value is Array: + return + for choice: Variant in choices_value: + if choice == null: + continue + var choice_id := StringName(_definition_value(choice, &"choice_id", &"")) + if choice_id.is_empty(): + continue + var choice_button := Button.new() + choice_button.text = _event_choice_button_text(choice) + choice_button.custom_minimum_size.y = 34 + _apply_button_style(choice_button) + choice_button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + choice_button.pressed.connect( + _submit_foreground_event_choice.bind( + foreground.event_instance_id, + foreground.choice_instance_id, + choice_id + ) + ) + foreground_event_choices.add_child(choice_button) + + +func _event_choice_button_text(choice: Variant) -> String: + var choice_id := StringName(_definition_value(choice, &"choice_id", &"")) + var text_key := StringName(_definition_value(choice, &"text_key", choice_id)) + var title := String(EVENT_CHOICE_NAMES.get(text_key, String(choice_id))) + var cost_value: Variant = _definition_value(choice, &"cost", {}) + if not cost_value is Dictionary or cost_value.is_empty(): + return title + var cost_parts: Array[String] = [] + var cost_keys: Array = cost_value.keys() + cost_keys.sort_custom(func(left: Variant, right: Variant) -> bool: + return String(left) < String(right) + ) + for raw_key: Variant in cost_keys: + cost_parts.append("%s %d" % [String(raw_key), int(cost_value[raw_key])]) + return "%s(%s)" % [title, " / ".join(cost_parts)] + + +func _submit_foreground_event_choice( + event_instance_id: StringName, + choice_instance_id: StringName, + choice_id: StringName +) -> void: + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("イベント選択を送信できません。", false) + return + var idempotency_key := StringName( + "ui.event.choice.%s.%s" % [String(choice_instance_id), String(choice_id)] + ) + var result: Variant = _submit_ui_command(&"submit_event_choice", + { + &"event_instance_id": event_instance_id, + &"choice_instance_id": choice_instance_id, + &"choice_id": choice_id, + }, + &"player", + event_instance_id, + 100, + -1, + idempotency_key + ) + var succeeded: bool = result != null \ + and result.has_method(&"is_success") \ + and bool(result.call(&"is_success")) + _set_status( + "イベントの選択を確定しました。" if succeeded \ + else _command_failure_message(result) if result != null else "イベント選択に失敗しました。", + succeeded + ) + _refresh_dashboard() + _update_pause_and_speed() + + +func _operations_dashboard_text() -> String: + if _runtime == null or not _runtime.has_method(&"get_operations_report"): + return "" + var report_value: Variant = _runtime.call(&"get_operations_dashboard_report") if _runtime.has_method(&"get_operations_dashboard_report") else _runtime.call(&"get_operations_report") + if not report_value is Dictionary: + return "" + var report: Dictionary = report_value + if not bool(report.get(&"enabled", false)): + return "\n運営日次処理 未開始" + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = coordinator_value if coordinator_value is Dictionary else {} + var terminal := StringName(coordinator.get(&"terminal_state", &"RUNNING")) + var state_text := "営業中" + if terminal == &"BANKRUPT": + state_text = "ゲーム終了" + elif bool(report.get(&"runtime_failure_latched", false)): + state_text = "処理失敗・再試行待ち" + elif bool(report.get(&"tick_blocked", false)): + state_text = "確認待ち" + var day_profit_text := "未確定" + var summaries_value: Variant = coordinator.get(&"day_summaries", []) + if summaries_value is Array and not summaries_value.is_empty(): + var latest_value: Variant = summaries_value.back() + if latest_value is Dictionary: + day_profit_text = "G %+d" % int(latest_value.get(&"net_profit", 0)) + var old_financial_history: bool = summaries_value is Array and not summaries_value.is_empty() and int(summaries_value.back().get(&"day", 0)) <= int(coordinator.get(&"legacy_history_last_day", 0)) + return ( + "\n行動点 %d / 6  未払経費 G %d\n" % [ + int(report.get(&"personal_action_points", 0)), + int(report.get(&"arrears_total", coordinator.get(&"arrears_total", 0))), + ] + + "運営状態 %s  前日損益 %s" % [state_text, day_profit_text] + + "\n%s %d / 3回" % ["旧版の連続未払い" if old_financial_history else "連続赤字", int(coordinator.get(&"consecutive_insolvency_count", 0))] + ) + + +func _refresh_selected_entity() -> void: + related_facility_picker.visible = false + selected_elevator_controls.visible = false + selected_detail_label.tooltip_text = "" + if _selected_entity_id.is_empty(): + _move_selected_detail_first(false) + selected_entity_icon.texture = null + selected_heading_label.text = "掘削モード" if _excavation_mode else "選択中の施設" + selected_detail_label.text = ( + "地下のマスにカーソルを合わせると、費用と所要時間を確認できます。" + if _excavation_mode else "地図上の施設をクリックすると、営業状態を確認できます。" + ) + return + if _selected_entity_kind == &"construction": + _move_selected_detail_first(false) + _refresh_selected_construction() + return + if _selected_entity_kind == &"excavation": + _move_selected_detail_first(false) + _refresh_selected_excavation() + return + if _refresh_selected_stairs(): + return + var shaft := _selected_elevator_report() + if not shaft.is_empty(): + _refresh_selected_elevator(shaft) + return + _move_selected_detail_first(false) + var facility_service: Variant = _service(&"facility_service") + if facility_service == null or not facility_service.has_method(&"get_instance"): + selected_detail_label.text = "施設サービスを利用できません。" + return + var facility: Variant = facility_service.call(&"get_instance", _selected_entity_id) + if facility == null: + var construction_facility := _construction_facility(_selected_entity_id) + if construction_facility.is_empty(): + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "選択中の施設" + selected_detail_label.text = "選択していた施設は現在の世界に存在しません。" + selected_entity_icon.texture = null + return + var construction_definition_id := StringName(construction_facility.get(&"definition_id", &"")) + var infrastructure_definition: Variant = _facility_definition(construction_definition_id) + selected_heading_label.text = String(_definition_value(infrastructure_definition, &"display_name", construction_definition_id)) + selected_entity_icon.texture = visual_theme.facility_icon(construction_definition_id) if visual_theme != null else null + selected_detail_label.text = "設備完成\n" + _facility_upkeep_text(_selected_entity_id) + return + var definition: Variant = _facility_definition(facility.definition_id) + selected_entity_icon.texture = visual_theme.facility_icon(facility.definition_id) if visual_theme != null else null + related_facility_picker.clear() + related_facility_picker.add_item("関連施設:指定なし") + related_facility_picker.set_item_metadata(0, &"") + for related: FacilityInstance in _runtime.facility_service.get_instances(): + if related.instance_id == facility.instance_id: + continue + var related_definition: Variant = _facility_definition(related.definition_id) + related_facility_picker.add_item("関連施設:%s (%d, %d)" % [String(_definition_value(related_definition, &"display_name", related.definition_id)), related.origin.x, related.origin.y]) + var index := related_facility_picker.item_count - 1 + related_facility_picker.set_item_metadata(index, related.instance_id) + if related.instance_id == facility.related_facility_id: + related_facility_picker.select(index) + related_facility_picker.visible = true + var display_name := String(_definition_value(definition, &"display_name", facility.definition_id)) + var state_index := clampi(int(facility.state), 0, FACILITY_STATE_NAMES.size() - 1) + var reasons := _facility_reasons(facility.state_reasons) + var queue_count: int = facility.queue_entries.size() + var service_count: int = facility.active_services.size() + var staff_count: int = facility.active_staff_ids.size() + var occupied_slots: int = service_count + var related_name := "なし" + if not facility.related_facility_id.is_empty(): + var related: FacilityInstance = _runtime.facility_service.get_instance(facility.related_facility_id) + related_name = "撤去済み" if related == null else "%s (%d, %d)" % [String(_definition_value(_facility_definition(related.definition_id), &"display_name", related.definition_id)), related.origin.x, related.origin.y] + selected_heading_label.text = display_name + selected_detail_label.text = ( + "状態 %s%s\n" % [FACILITY_STATE_NAMES[state_index], "(%s)" % reasons if not reasons.is_empty() else ""] + + "待機列 %d / %d  サービス %d / %d\n" % [queue_count, int(facility.queue_capacity), occupied_slots, int(facility.capacity)] + + "稼働区分 %s\n顧客同時利用 %d / %s\n" % [["未稼働", "稼働", "休止"][facility.operation_state], facility.customer_count(), str(facility.customer_capacity) if facility.customer_capacity >= 0 else "未設定"] + + "関連施設 %s(%s)\n従業員上限 %d\n" % [related_name, "稼働条件成立" if facility.related_facility_operating else "未成立", facility.staff_max] + + "配置人員 %d / 必要 %d  耐久 %d%%\n" % [staff_count, int(facility.staff_min), int(facility.durability) / 100] + + _facility_upkeep_text(_selected_entity_id) + + "基本料金 G %d  所要 %d分\n" % [int(facility.base_price), int(facility.base_service_minutes)] + + "提供サービス %s\n" % {&"": "未設定", &"appetite": "食欲", &"relaxation": "リラックス欲", &"sexual": "性欲", &"gambling": "ギャンブル欲"}.get(facility.service_attribute, "未設定") + + "座標 (%d, %d) %s" % [facility.origin.x, facility.origin.y, "FRONT" if facility.origin.z == 0 else "BACK"] + ) + + +func _facility_upkeep_text(facility_id: StringName) -> String: + if _runtime == null or not _runtime.has_method(&"get_facility_upkeep_report"): + return "" + var report: Dictionary = _runtime.call(&"get_facility_upkeep_report", facility_id) + var pending := float(report[&"unsettled_units"]) / float(report[&"gold_denominator"]) + return "維持費 %dG / 日\n精算前 約%.2fG 未払い %dG\n閉店・休止中も計上\n完成から撤去までの時間で按分\n" % [int(report[&"daily_rate"]), pending, int(report[&"arrears"])] + + +func _on_related_facility_selected(index: int) -> void: + if _runtime == null or _selected_entity_id.is_empty(): + return + var related_id := StringName(related_facility_picker.get_item_metadata(index)) + var result: Variant = _submit_ui_command(&"set_facility_conditions", {&"facility_id": _selected_entity_id, &"conditions": {&"related_facility_id": related_id}}) + if result.is_success() and not result.details.has(&"instance_id"): + _pending_related_commands[int(result.command_id)] = true + _set_status("関連施設の変更を受け付けました。" if result.is_success() else result.message, result.is_success(), true) + _refresh_selected_entity() + + +func _refresh_selected_stairs() -> bool: + var from_cell := Vector3i.ZERO + var to_cell := Vector3i.ZERO + var definition_id: StringName = &"" + var legacy := _selected_entity_kind == &"legacy_stairs" + var legacy_access: StringName = &"" + if legacy: + for stair: Dictionary in world_preview.get_legacy_stair_records(): + if StringName(stair[&"id"]) == _selected_entity_id: + from_cell = stair[&"from"] + legacy_access = StringName(stair.get(&"access_id", &"")) + to_cell = stair[&"to"] + definition_id = &"guest_stairs" if from_cell.z == 0 else &"service_stairs" + break + else: + var facility := _construction_facility(_selected_entity_id) + definition_id = StringName(facility.get(&"definition_id", &"")) + if definition_id not in [&"guest_stairs", &"service_stairs"]: + return false + var origin: Variant = facility.get(&"origin", Vector3i.ZERO) + if origin is Array and origin.size() == 3: + origin = Vector3i(int(origin[0]), int(origin[1]), int(origin[2])) + to_cell = origin + from_cell = to_cell+Vector3i(0,-1,0) + if definition_id.is_empty(): + return false + _move_selected_detail_first(true) + var staff_only := legacy_access == &"staff" if legacy else definition_id == &"service_stairs" + selected_heading_label.text = "既設の階段" if legacy else ("業務用階段" if staff_only else "客用階段") + selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null + selected_detail_label.text = ( + "接続階 %s ↔ %s\n" % [_elevator_floor_name(mini(from_cell.y,to_cell.y)), _elevator_floor_name(maxi(from_cell.y,to_cell.y))] + + "利用者 %s\n" % ("従業員専用・物資運搬可" if staff_only else "客・従業員用") + + "階段で昇り降りします。魔力は不要です。\n" + + "座標 (%d, %d) %s" % [to_cell.x,maxi(from_cell.y,to_cell.y),"BACK" if to_cell.z == 1 else "FRONT"] + ) + return true + + +func _selected_elevator_report() -> Dictionary: + if _selected_entity_kind != &"facility" or _selected_entity_id.is_empty(): + return {} + var transport: Variant = _service(&"elevators") + if transport == null or not transport.has_method(&"get_report"): + return {} + for shaft: Dictionary in transport.call(&"get_report").get(&"shafts", []): + if (shaft.get(&"module_ids", []) as Array).has(_selected_entity_id): + return shaft + return {} + + +func _refresh_selected_elevator(shaft: Dictionary) -> void: + _move_selected_detail_first(true) + var construction_facility := _construction_facility(_selected_entity_id) + var definition_id := StringName(construction_facility.get(&"definition_id", &"guest_elevator")) + var definition: Variant = _facility_definition(definition_id) + selected_heading_label.text = String(_definition_value(definition, &"display_name", "昇降機")) + selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null + var powered := bool(shaft.get(&"powered", true)) + var operator_open := bool(shaft.get(&"operator_open", true)) + var state_text := String(ELEVATOR_PHASE_NAMES.get(StringName(shaft.get(&"phase", &"IDLE")), "待機")) + if not operator_open: + state_text = "手動停止" + elif not powered: + state_text = "魔力供給なし" + var floor_index := int(shaft.get(&"current_floor", 0)) + var target_index := int(shaft.get(&"boarding_floor", floor_index) if StringName(shaft.get(&"phase", &"IDLE")) == &"PICKUP" else shaft.get(&"target_floor", floor_index)) + var stops: Array = shaft.get(&"stops", []) + var stop_text := "—" + if not stops.is_empty(): + stop_text = "%s 〜 %s" % [_elevator_floor_name(stops.front().y), _elevator_floor_name(stops.back().y)] + selected_detail_label.text = ( + "状態 %s\n" % state_text + + "現在階 %s  目的階 %s\n" % [_elevator_floor_name(floor_index), _elevator_floor_name(target_index)] + + "乗客 %d / %d人  待機 %d人\n" % [int(shaft.get(&"passenger_count", 0)), int(shaft.get(&"capacity", 0)), int(shaft.get(&"waiting_count", 0))] + + "停止階 %s\n" % stop_text + + "魔力 %s  %s" % ["供給中" if powered else "不足", "客・従業員用" if definition_id == &"guest_elevator" else "従業員・物資用"] + ) + selected_detail_label.text += _elevator_wait_text(StringName(shaft.get(&"shaft_id", &""))) + selected_elevator_controls.visible = true + elevator_operation_button.disabled = _pending_elevator_commands.values().has(shaft.get(&"shaft_id", &"")) + elevator_operation_button.text = "指示を受理済み…" if elevator_operation_button.disabled else ("昇降機を停止" if operator_open else "運行を再開") + elevator_operation_hint.text = "停止すると最寄りの階で降車し、待機中の呼び出しを解除します。" if operator_open else ("再開すると新しい呼び出しを受け付けます。" if powered else "魔力が供給されると運行を再開できます。") + + +func _elevator_wait_text(shaft_id: StringName) -> String: + selected_detail_label.tooltip_text = "保持している直近256件の出来事のうち、過去60分の乗車を集計。現在の最長待ちは、いま並んでいる人の待ち時間です。" + if _runtime == null or not _runtime.has_method(&"get_elevator_wait_statistics"): + return "\n最近の乗車記録 計測待ち" + var statistics: Dictionary = _runtime.call(&"get_elevator_wait_statistics", shaft_id) + var samples := int(statistics.get(&"sample_count", 0)) + var text := "\n最近の乗車記録 %d人" % samples + if samples == 0: + text += "(計測待ち)" + else: + text += "\n平均待ち %.1f分 / 95%% %.1f分" % [float(statistics.get(&"mean_wait_minutes",0)),float(statistics.get(&"p95_wait_minutes",0))] + return text+"\n現在の最長待ち %d分" % int(statistics.get(&"oldest_wait_minutes",0)) + + +func _move_selected_detail_first(first: bool) -> void: + var column := selected_heading_label.get_parent() + var selected_row := selected_detail_label.get_parent() + # The transport action fits without scrolling; other management views keep + # their existing staff-first layout. + var desired_index := 0 if first else column.get_child_count()-4 + if selected_heading_label.get_index() == desired_index: + return + column.move_child(selected_heading_label, 0 if first else -1) + column.move_child(selected_row, 1 if first else -1) + column.move_child(selected_elevator_controls, 2 if first else -1) + column.move_child(related_facility_picker, 3 if first else -1) + + +func _elevator_floor_name(floor_index: int) -> String: + return "%dF" % (floor_index+1) if floor_index >= 0 else "B%d" % -floor_index + + +func _on_elevator_operation_pressed() -> void: + var shaft := _selected_elevator_report() + if shaft.is_empty() or _runtime == null: + return + var shaft_id := StringName(shaft[&"shaft_id"]) + if _pending_elevator_commands.values().has(shaft_id): + return + var key := StringName("ui.elevator.%08d" % _elevator_sequence) + _elevator_sequence += 1 + var open_value := not bool(shaft.get(&"operator_open", true)) + var result: Variant = _submit_ui_command(&"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": open_value}, &"player", &"", 50, -1, key) + if result == null or not result.is_success(): + _set_status("昇降機の指示を反映できませんでした。", false) + return + if not result.details.has(&"shaft_id"): + _pending_elevator_commands[int(result.command_id)] = shaft_id + _set_status("昇降機の運行指示を受理しました。", true) + else: + var message := "昇降機を停止しました。" + if open_value: + message = "昇降機の運行を再開しました。" if bool(shaft.get(&"powered", true)) else "運行を許可しました。魔力の供給待ちです。" + _set_status(message, true) + _refresh_selected_entity() + + +func _synchronize_elevator_pending() -> void: + var queue: Variant = _service(&"command_queue") + if queue == null: + return + if queue.pending_count() == 0: + _pending_elevator_commands.clear() + return + _pending_elevator_commands.clear() + for command: Dictionary in queue.create_snapshot().get("pending", []): + if StringName(command.get("command_type", &"")) == &"set_elevator_operation": + _pending_elevator_commands[int(command.get("command_id", 0))] = StringName((command.get("payload", {}) as Dictionary).get(&"shaft_id", &"")) + + +func _refresh_selected_construction() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_job"): + selected_detail_label.text = "建設サービスを利用できません。" + return + var job: Variant = construction.call(&"get_job", _selected_entity_id) + if job == null: + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + selected_heading_label.text = "選択中の施設" + selected_detail_label.text = "選択していた工事は現在の世界に存在しません。" + selected_entity_icon.texture = null + return + var definition: Variant = _facility_definition(job.definition_id) + selected_entity_icon.texture = visual_theme.facility_icon(job.definition_id) if visual_theme != null else null + var display_name := String(_definition_value(definition, &"display_name", job.definition_id)) + var state_names := ["計画済み", "建設中", "完成", "中止", "停止中"] + var job_state := clampi(int(job.state), 0, state_names.size() - 1) + selected_heading_label.text = "%s(工事)" % display_name + selected_detail_label.text = ( + "状態 %s\n" % state_names[job_state] + + "進捗 %d%%  予定 %d分\n" % [int(job.progress) / 100, int(job.build_minutes)] + + "座標 (%d, %d) %s\n" % [job.origin.x, job.origin.y, "FRONT" if int(job.layer) == 0 else "BACK"] + + ("停止理由 %s" % String(job.state_reason) if not String(job.state_reason).is_empty() else "建設はシミュレーション時間とともに進みます。") + ) + + +func _select_excavation_job(job_id: StringName) -> void: + if job_id.is_empty(): + return + _selected_entity_id = job_id + _selected_entity_kind = &"excavation" + world_preview.selected_entity_id = &"" + world_preview.clear_hovered_cell() + _refresh_selected_excavation() + + +func _refresh_selected_excavation() -> void: + selected_entity_icon.texture = visual_theme.control_icon(&"excavate") if visual_theme != null else null + var excavation: Variant = _service(&"excavation_service") + if excavation == null or not excavation.has_method(&"get_job"): + selected_detail_label.text = "掘削サービスを利用できません。" + return + var job_value: Variant = excavation.call(&"get_job", _selected_entity_id) + if not job_value is Dictionary or job_value.is_empty(): + _selected_entity_id = &"" + _selected_entity_kind = &"" + selected_heading_label.text = "掘削モード" if _excavation_mode else "選択中の施設" + selected_detail_label.text = "選択していた掘削作業は現在の世界に存在しません。" + return + var job: Dictionary = job_value + var state := StringName(job.get(&"state", &"")) + var state_text := "掘削中" + if state == &"PAUSED": + state_text = "一時停止" + elif state == &"COMPLETED": + state_text = "完了" + var position: Vector2i = job.get(&"position", Vector2i.ZERO) + var cost_value: Variant = job.get(&"cost", {}) + var cost: Dictionary = cost_value if cost_value is Dictionary else {} + var guidance := "両レイヤーが開通しました。" + if state == &"ACTIVE": + guidance = "掘削はシミュレーション時間とともに進みます。" + elif state == &"PAUSED": + guidance = "停止理由 %s" % String(job.get(&"state_reason", "")) + selected_heading_label.text = "地下掘削(%d, %d)" % [position.x, position.y] + selected_detail_label.text = ( + "状態 %s  進捗 %d%%\n" % [state_text, clampi(int(job.get(&"progress", 0)) / 100, 0, 100)] + + "所要 %d分  費用 G %d / 資材 %d\n" % [ + int(job.get(&"build_minutes", 0)), + int(cost.get(&"gold", cost.get("gold", 0))), + int(cost.get(&"materials", cost.get("materials", 0))), + ] + + "対象 FRONT / 表層・BACK / 裏層\n" + + guidance + ) + + +func _preview_failure_message(report: Dictionary) -> String: + var codes_value: Variant = report.get(&"error_codes", []) + if codes_value is Array and not codes_value.is_empty(): + var code := String(codes_value[0]) + if PREVIEW_REASON_NAMES.has(code): + return "配置不可: %s" % PREVIEW_REASON_NAMES[code] + var code_reasons: Variant = report.get(&"reasons", []) + if code_reasons is Array and not code_reasons.is_empty(): + var reason: Variant = code_reasons[0] + for candidate: Variant in code_reasons: + if candidate is Dictionary and String(candidate.get(&"code", "")) == code: + reason = candidate + break + if reason is Dictionary: + var message := String(reason.get(&"message", "")) + if not message.is_empty(): + return "配置不可: %s" % message + var reasons_value: Variant = report.get(&"reasons", []) + if reasons_value is Array and not reasons_value.is_empty() and reasons_value[0] is Dictionary: + return "配置不可: %s" % String(reasons_value[0].get(&"message", "条件を満たしていません")) + return "このマスには配置できません。" + + +func _excavation_failure_message(report: Dictionary) -> String: + var errors_value: Variant = report.get(&"errors", []) + var reason := "条件を満たしていません" + if errors_value is Array and not errors_value.is_empty(): + var code := StringName(errors_value[0]) + reason = String(EXCAVATION_REASON_NAMES.get(code, String(code))) + if code == &"INSUFFICIENT_RESOURCES": + var missing_value: Variant = report.get(&"missing_resources", {}) + if missing_value is Dictionary and not missing_value.is_empty(): + var missing: Dictionary = missing_value + reason += "(不足: G %d / 資材 %d)" % [ + int(missing.get(&"gold", missing.get("gold", 0))), + int(missing.get(&"materials", missing.get("materials", 0))), + ] + return "掘削不可: %s%s" % [reason, _excavation_cost_and_time(report)] + + +func _excavation_cost_and_time(report: Dictionary) -> String: + var cost_value: Variant = report.get(&"cost", {}) + var cost: Dictionary = cost_value if cost_value is Dictionary else {} + var minutes := int(report.get(&"build_minutes", 0)) + if cost.is_empty() and minutes <= 0: + return "" + return "(費用 G %d / 資材 %d・所要 %d分)" % [ + int(cost.get(&"gold", cost.get("gold", 0))), + int(cost.get(&"materials", cost.get("materials", 0))), + minutes, + ] + + +func _excavation_gate_message() -> String: + var progression: Variant = _service(&"progression_service") + if progression == null: + return "世界樹ランクを確認できません" + var current_rank := int(_definition_value(progression, &"current_rank", 1)) + if current_rank < 2: + return "世界樹ランク2で地下掘削が解禁されます(現在ランク%d)" % current_rank + if _runtime == null or not _runtime.has_method(&"get_operations_report"): + return "運営状態を確認できません" + var report_value: Variant = _runtime.call(&"get_operations_report") + if not report_value is Dictionary: + return "運営状態を確認できません" + var report: Dictionary = report_value + if not bool(report.get(&"enabled", false)): + return "本番運営が始まるまで掘削できません" + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = coordinator_value if coordinator_value is Dictionary else {} + if StringName(coordinator.get(&"terminal_state", &"RUNNING")) == &"BANKRUPT": + return "資金破綻により新しい掘削を開始できません" + if bool(coordinator.get(&"external_quarantined", false)): + return "運営データの復旧が必要なため掘削を開始できません" + var arrears := int(report.get(&"arrears_total", coordinator.get(&"arrears_total", 0))) + if arrears > 0: + return "未払経費 G %dを清算するまで掘削できません" % arrears + return "" + + +func _excavation_command_failure_message(result: Variant) -> String: + var details: Variant = result.details + if details is Dictionary: + var report_value: Variant = details.get(&"report", null) + if report_value is Dictionary: + return _excavation_failure_message(report_value) + var reason := StringName(details.get(&"reason", &"")) + match reason: + &"ARREARS_REMAIN", &"UNPAID_EXPENSES": + return "掘削不可: 未払経費 G %dを清算してください" % int(details.get(&"arrears_total", 0)) + &"BANKRUPT": + return "掘削不可: 資金破綻により新しい工事は開始できません" + &"EXTERNAL_ROLLBACK_FAILED": + return "掘削不可: 運営データの復旧が必要です" + if details.has(&"required_rank"): + return "掘削不可: 世界樹ランク%dが必要です(現在ランク%d)" % [ + int(details.get(&"required_rank", 2)), + int(details.get(&"current_rank", 1)), + ] + if int(result.code) == int(CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY): + return "この掘削指示はすでに受理されています。" + return _command_failure_message(result) + + +func _command_failure_message(result: Variant) -> String: + var message := String(result.message) + var details: Variant = result.details + if details is Dictionary: + var report_value: Variant = details.get(&"placement_report", null) + if report_value is Dictionary: + return _preview_failure_message(report_value) + return "操作に失敗しました: %s" % (message if not message.is_empty() else "不明なエラー") + + +func _save_manual() -> void: + _save_manual_at_path(manual_save_path, "手動保存") + + +func _save_manual_at_path(save_path: String, display_name: String) -> bool: + if _runtime == null or not _runtime.has_method(&"save_to_path"): + _set_status("保存サービスへ接続できません。", false) + return false + var directory_error := DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(save_path).get_base_dir()) + if directory_error != OK and directory_error != ERR_ALREADY_EXISTS: + _set_status("保存フォルダを作成できませんでした。", false) + return false + var metadata := {&"display_name": display_name, &"interface_selection": _capture_interface_selection()} + var thumbnail := _capture_manual_thumbnail_png() + if not thumbnail.is_empty(): metadata[&"thumbnail_png"] = thumbnail + var result: Variant = _runtime.call(&"save_to_path", save_path, metadata) + if result is Dictionary and bool(result.get(&"ok", false)): + manual_save_path = save_path + _set_status("手動セーブを保存しました。", true) + return true + if result is Dictionary and StringName(result.get(&"code", &"")) == &"FORMAT_MIGRATION_REQUIRED": + _set_status("元の保存を残すため、別のスロットへ保存してください。", false) + _ensure_manual_save_slots().show_new_save(save_path) + else: + _set_status("保存に失敗しました: %s" % _result_message(result), false) + return false + + +func _capture_manual_thumbnail_png() -> PackedByteArray: + # Capture the existing rendered game; no simulation update or new art source. + # Dummy/headless rendering retains the existing SaveService fallback. + if DisplayServer.get_name() == "headless": return PackedByteArray() + var viewport := get_viewport() + if viewport == null: return PackedByteArray() + var texture := viewport.get_texture() + if texture == null: return PackedByteArray() + var image := texture.get_image() + if image == null or image.is_empty(): return PackedByteArray() + var scale := minf(320.0 / float(image.get_width()), 180.0 / float(image.get_height())) + var width := maxi(1, roundi(float(image.get_width()) * scale)) + var height := maxi(1, roundi(float(image.get_height()) * scale)) + image.resize(width, height, Image.INTERPOLATE_LANCZOS) + return image.save_png_to_buffer() + + +func _load_manual(strict: bool = false) -> void: + _load_manual_at_path(manual_save_path, strict) + + +func _load_manual_at_path(save_path: String, strict: bool = false) -> void: + if _runtime == null or ( + not _runtime.has_method(&"load_with_recovery") + and not _runtime.has_method(&"load_from_path") + ): + _set_status("読込サービスへ接続できません。", false) + return + var result: Variant = ( + _runtime.call(&"load_with_recovery", save_path) + if not strict and _runtime.has_method(&"load_with_recovery") + else _runtime.call(&"load_from_path", save_path) + ) + if result is Dictionary and bool(result.get(&"ok", false)): + manual_save_path = save_path + # Real Runtime already emitted its common completion notification. + if not _runtime.has_signal(&"save_operation_completed"): + _apply_loaded_interface(result) + _set_status("手動セーブを読み込みました。", true) + else: + _set_status("読み込みに失敗しました: %s" % _result_message(result), false) + + +func _ensure_manual_save_slots() -> Variant: + if manual_save_slots_dialog == null: + manual_save_slots_dialog = ManualSaveSlotsDialogScript.new() + manual_save_slots_dialog.connect(&"new_slot_requested", _save_new_manual_slot) + manual_save_slots_dialog.connect(&"load_slot_requested", _load_selected_manual_slot) + add_child(manual_save_slots_dialog) + return manual_save_slots_dialog + + +func _save_new_manual_slot(path: String, display_name: String) -> void: + if not ManualSaveSlotsDialogScript.is_fresh_slot(path) or path.get_base_dir() != manual_save_path.get_base_dir(): + _set_status("新しい保存先を用意できませんでした。元の保存は変更していません。", false) + return + _save_manual_at_path(path, display_name) + + +func _show_manual_load_slots() -> void: + _ensure_manual_save_slots().show_load_slots(manual_save_path) + + +func _load_selected_manual_slot(path: String) -> void: + if path.get_base_dir() != manual_save_path.get_base_dir() or not ManualSaveSlotsDialogScript.is_owned_slot_name(path.get_file()): + _set_status("この保存先を読み込めません。", false) + return + _load_manual_at_path(path) + +func _capture_interface_selection() -> Dictionary: + var selection := {&"version": 1, &"kind": String(_selected_entity_kind), &"id": String(_selected_entity_id), &"staff_open": _staff_management_panel_open, &"guest_open": false} + selection[&"product_open"] = _supply_panel_open + if supply_management_panel != null: + selection[&"product_tab"] = String(supply_management_panel.selected_tab) + if staff_management_panel != null: + selection[&"staff_id"] = String(staff_management_panel.get("selected_staff_id")) + if guest_observer != null: + var panel := guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + if panel != null: + selection[&"guest_id"] = String(panel.selected_guest_id) + selection[&"guest_open"] = guest_observer.visible + if defense_manager != null: + var panel := defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + if panel != null: + selection[&"defense_id"] = String(panel.selected_raid_id) + selection[&"defense_open"] = defense_manager.visible + return selection + + +func _on_checkpoint_presentation_requested(presentation: Dictionary) -> void: + presentation[&"interface_selection"] = _capture_interface_selection() + + +func _on_save_operation_completed(result: Dictionary) -> void: + if not bool(result.get(&"ok", false)) or StringName(result.get(&"operation", &"")) not in [&"load", &"recovery", &"recovery_candidate"]: + return + _apply_loaded_interface(result) + + +func _apply_loaded_interface(result: Dictionary) -> void: + if _audio_feedback != null: + _audio_feedback.begin_loaded_epoch(_current_audio_foreground()) + _pending_build_commands.clear() + _pending_demolition_commands.clear() + _set_demolition_mode(false, false) + _pending_excavation_commands.clear() + _pending_hire_commands.clear() + _pending_elevator_commands.clear() + _synchronize_ui_command_state() + if staff_management_panel != null: + staff_management_panel.refresh() + if supply_management_panel != null: + supply_management_panel.refresh() + if business_management_panel != null: + business_management_panel.refresh() + _selected_entity_id = &"" + _selected_entity_kind = &"" + world_preview.selected_entity_id = &"" + world_preview.set_runtime(_runtime) + _set_layer(GameState.active_layer) + _restore_interface_selection(result.get(&"manifest", {})) + _restore_actor_interface_selection(result.get(&"manifest", {})) + _restore_defense_interface_selection(result.get(&"manifest", {})) + _restore_product_interface_selection(result.get(&"manifest", {})) + _refresh_dashboard() + _update_pause_and_speed() + + +func _valid_interface_selection_version(value: Variant) -> bool: + # JSON numbers may be decoded as floats; booleans are never versions. + return typeof(value) in [TYPE_INT, TYPE_FLOAT] and value == 1 + + +func _restore_interface_selection(manifest: Variant) -> void: + # Presentation metadata is optional. It must never invalidate gameplay, + # and recovery must use the manifest of the actually loaded candidate. + if not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)): + return + var kind_value: Variant = selection.get("kind", "") + var id_value: Variant = selection.get("id", "") + if not kind_value is String or not id_value is String or id_value.is_empty() or id_value.length() > 256: + return + var kind := StringName(kind_value) + var entity_id := StringName(id_value) + var exists := false + match kind: + &"facility": + exists = _service(&"facility_service").get_instance(entity_id) != null or not _construction_facility(entity_id).is_empty() + &"construction": + exists = _service(&"construction_service").get_job(entity_id) != null + &"excavation": + exists = not _service(&"excavation_service").get_job(entity_id).is_empty() + &"legacy_stairs": + for stair: Dictionary in world_preview.get_legacy_stair_records(): + if StringName(stair.get(&"id", &"")) == entity_id: + exists = true + break + if not exists: + return + _selected_entity_kind = kind + _selected_entity_id = entity_id + world_preview.selected_entity_id = entity_id if kind != &"excavation" else &"" + _set_management_panel_open(true) + + +func _restore_actor_interface_selection(manifest: Variant) -> void: + # Only successful gameplay loads reach here. Reset stale live selections, + # then restore each optional inspector independently of the map selection. + _set_staff_management_panel_open(false) + if staff_management_panel != null: + staff_management_panel.call("clear_selection") + if guest_observer != null: + guest_observer.hide() + var old_panel := guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + if old_panel != null: + old_panel.clear_selection() + if not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or not _valid_interface_selection_version(selection.get("version", 0)): + return + var staff_id: Variant = selection.get("staff_id", "") + if staff_id is String and not staff_id.is_empty() and staff_id.length() <= 256 and _service(&"staff_service").has_staff(StringName(staff_id)): + staff_management_panel.call("select_staff", StringName(staff_id)) + if selection.get("staff_open", false) is bool and selection.get("staff_open", false): + _activate_management_mode(&"staff") + var guest_id: Variant = selection.get("guest_id", "") + if guest_id is String and not guest_id.is_empty() and guest_id.length() <= 256 and _service(&"guest_service").has_guest(StringName(guest_id)): + var panel := _ensure_guest_observer() + panel.selected_guest_id = StringName(guest_id) + panel.refresh() + if selection.get("guest_open", false) is bool and selection.get("guest_open", false): + _show_guest_observer() + + +func _restore_product_interface_selection(manifest: Variant) -> void: + # Additive optional presentation: old packages keep their existing behavior. + # No purchase draft, quantity, policy, assignment or command is restored here. + if supply_management_panel == null or not manifest is Dictionary: + return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary: + return + var version: Variant = selection.get("version", 0) + if not (version is int or version is float) or version != 1: + return + var open_value: Variant = selection.get("product_open", null) + var tab_value: Variant = selection.get("product_tab", null) + if not open_value is bool or not tab_value is String: + return + if tab_value not in ["purchase", "kitchen", "delivery"]: + return + # A captured main panel cannot be both staff and product. Ignore a + # contradictory optional product claim without overriding restored staff. + if open_value and selection.get("staff_open", false) is bool and selection.get("staff_open", false): + return + supply_management_panel.select_tab(StringName(tab_value)) + if open_value: + _activate_management_mode(&"product") + else: + _set_supply_panel_open(false) + + +func _result_message(result: Variant) -> String: + if result is Dictionary: + var message := String(result.get(&"message", result.get(&"code", "保存データがありません"))) + return message if not message.is_empty() else "保存データがありません" + return "サービスから応答がありません" + + +func _synchronize_ui_command_state() -> void: + if _runtime == null: + return + var queue: Variant = _runtime.get(&"command_queue") + if queue == null: + return + if queue.has_method(&"get_accepted_command_metadata"): + var metadata_value: Variant = queue.call(&"get_accepted_command_metadata") + if metadata_value is Array: + for raw_metadata: Variant in metadata_value: + if not raw_metadata is Dictionary: + continue + _advance_ui_sequence_from_key(String(raw_metadata.get("idempotency_key", ""))) + if not queue.has_method(&"create_snapshot"): + return + var snapshot_value: Variant = queue.call(&"create_snapshot") + if not snapshot_value is Dictionary: + return + var pending_value: Variant = snapshot_value.get("pending", []) + if not pending_value is Array: + return + for raw_pending: Variant in pending_value: + if not raw_pending is Dictionary: + continue + var command_id := int(raw_pending.get("command_id", 0)) + var command_type := StringName(raw_pending.get("command_type", &"")) + var payload_value: Variant = raw_pending.get("payload", {}) + var payload: Dictionary = payload_value if payload_value is Dictionary else {} + if command_id <= 0: + continue + match command_type: + &"set_elevator_operation": + _pending_elevator_commands[command_id] = StringName(payload.get(&"shaft_id", &"")) + &"build_facility": + var definition_id := StringName(payload.get(&"definition_id", payload.get("definition_id", &""))) + if not definition_id.is_empty(): + _pending_build_commands[command_id] = definition_id + &"start_excavation": + var cell_value: Variant = payload.get(&"cell", payload.get("cell", null)) + if cell_value is Vector3i: + _pending_excavation_commands[command_id] = Vector2i(cell_value.x, cell_value.y) + &"hire_staff_candidate": + var candidate_id := StringName(payload.get(&"candidate_id", payload.get("candidate_id", &""))) + if not candidate_id.is_empty(): + _pending_hire_commands[command_id] = candidate_id + + +func _advance_ui_sequence_from_key(key: String) -> void: + if key.is_empty(): + return + var parts := key.split(".") + if parts.is_empty(): + return + var suffix := String(parts[parts.size() - 1]) + if not suffix.is_valid_int(): + return + var next_sequence := int(suffix) + 1 + if key.begins_with("ui.build."): + _build_sequence = maxi(_build_sequence, next_sequence) + elif key.begins_with("ui.excavation."): + _excavation_sequence = maxi(_excavation_sequence, next_sequence) + elif key.begins_with("ui.hire."): + _hire_sequence = maxi(_hire_sequence, next_sequence) + elif key.begins_with("ui.elevator."): + _elevator_sequence = maxi(_elevator_sequence, next_sequence) + + +var _dashboard_refresh_pending := false + + +func _queue_dashboard_refresh() -> void: + if _dashboard_refresh_pending: + return + _dashboard_refresh_pending = true + _flush_dashboard_refresh.call_deferred() + + +func _flush_dashboard_refresh() -> void: + if not _dashboard_refresh_pending or not is_inside_tree(): + return + _dashboard_refresh_pending = false + _refresh_dashboard() + + +func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void: + _synchronize_elevator_pending() + _queue_dashboard_refresh() + _update_pause_and_speed() + var operation: Dictionary = _snapshot.get(&"systems", {}).get(&"runtime", {}).get(&"operations", {}).get(&"coordinator", {}) + var summaries: Array = operation.get(&"day_summaries", []) + if summaries.is_empty(): + _last_presented_close_minute = 0 + return + var latest: Dictionary = summaries.back() + var closed_at := int(latest.get(&"closed_at", 0)) + if closed_at > _last_presented_close_minute: + _last_presented_close_minute = closed_at + operating_ledger_panel.select_latest_close() + _activate_management_mode(&"ledger") + var losses := int(latest.get(&"consecutive_insolvency_count", 0)) + if int(latest[&"day"]) <= int(operation.get(&"legacy_history_last_day", 0)): + _set_status("旧版で確定した決算を表示しています。", true, true) + return + _set_status("3回連続の赤字決算でゲーム終了です。帳簿を確認できます。" if String(latest.get(&"terminal_state", "")) == "BANKRUPT" else "第%d日 決算:利益 %+dG・連続赤字 %d回" % [int(latest[&"day"]), int(latest[&"net_profit"]), losses], true, true) + else: + _last_presented_close_minute = closed_at + + +func _on_command_completed(result: Variant) -> void: + if result == null: + return + if _pending_demolition_commands.has(int(result.command_id)): + _pending_demolition_commands.erase(int(result.command_id)) + _show_demolition_result(result) + return + if _pending_related_commands.has(int(result.command_id)): + _pending_related_commands.erase(int(result.command_id)) + _set_status("関連施設の設定を反映しました。" if result.is_success() else result.message, result.is_success()) + _refresh_selected_entity() + return + if _pending_elevator_commands.has(int(result.command_id)): + _pending_elevator_commands.erase(int(result.command_id)) + _set_status("昇降機の運行設定を反映しました。" if result.is_success() else "昇降機の運行設定を反映できませんでした。", result.is_success()) + _refresh_selected_entity() + return + if _pending_hire_commands.has(int(result.command_id)): + var candidate_id := StringName(_pending_hire_commands[int(result.command_id)]) + _pending_hire_commands.erase(int(result.command_id)) + var display_name := String(candidate_id) + for summary: Dictionary in _staff_candidate_summaries(): + if StringName(summary.get(&"candidate_id", &"")) == candidate_id: + display_name = String(summary.get(&"display_name", candidate_id)) + break + if bool(result.call(&"is_success")): + _set_status("%sを雇用しました。" % display_name, true) + else: + _set_status(_hire_failure_message(result), false) + _refresh_dashboard() + return + if _pending_excavation_commands.has(int(result.command_id)): + var position: Vector2i = _pending_excavation_commands[int(result.command_id)] + _pending_excavation_commands.erase(int(result.command_id)) + if bool(result.call(&"is_success")): + _select_excavation_job(StringName(result.details.get(&"job_id", &""))) + _set_status("掘削を開始しました(%d, %d / FRONT・BACK)。" % [position.x, position.y], true) + else: + _set_status(_excavation_command_failure_message(result), false) + return + if not _pending_build_commands.has(int(result.command_id)): + return + var definition_id := StringName(_pending_build_commands[int(result.command_id)]) + _pending_build_commands.erase(int(result.command_id)) + if bool(result.call(&"is_success")): + var definition: Variant = _facility_definition(definition_id) + _set_status("%sの建設を開始しました。" % String(_definition_value(definition, &"display_name", definition_id)), true) + else: + _set_status(_command_failure_message(result), false) + + +func _set_layer(value: int) -> void: + var normalized := clampi(value, 0, 1) + if is_instance_valid(_paired_bgm): + _paired_bgm.set_layer(normalized) + if world_preview != null: + world_preview.layer_mode = normalized + GameState.active_layer = normalized + if front_button != null: + front_button.set_pressed_no_signal(normalized == 0) + if back_button != null: + back_button.set_pressed_no_signal(normalized == 1) + for label: Label in [dashboard_summary_label, staff_candidate_summary_label, selected_detail_label, elevator_operation_hint]: + if label != null: LayerSkin.register_text(label, normalized) + # Styles are registered in the saved layer during construction. + # Reapplying them before the initial container layout causes extra sizing. + if _interface_layer >= 0 and _interface_layer != normalized: + LayerSkin.apply_tree(self, normalized) + if rank_dialog != null: rank_dialog.apply_layer_skin(normalized) + if event_log_dialog != null: event_log_dialog.apply_layer_skin(normalized) + if manual_save_slots_dialog != null: manual_save_slots_dialog.apply_layer_skin(normalized) + _interface_layer = normalized + if foreground_event_label != null: + foreground_event_label.add_theme_color_override("font_color", Color("ffb498") if normalized == 1 else Color("8f3f29")) + var background := get_node_or_null("InterfaceBackground") as ColorRect + if background != null: + background.color = Color("10191e") if normalized == 1 else _visual_color(&"window_background", Color("24170f")) + EventBus.layer_changed.emit(normalized) + + +func _toggle_pause() -> void: + var event_service: Variant = _service(&"event_service") + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + if foreground != null and int(foreground.state) == 2: + GameClock.set_paused(true) + _set_status("先に要対応イベントの選択肢を決めてください。", false) + _update_pause_and_speed() + return + if _runtime != null \ + and _runtime.has_method(&"get_operations_report") \ + and _runtime.has_method(&"acknowledge_operations_pause"): + var report_value: Variant = _runtime.call(&"get_operations_report") + if report_value is Dictionary: + var report: Dictionary = report_value + var coordinator_value: Variant = report.get(&"coordinator", {}) + var coordinator: Dictionary = ( + coordinator_value if coordinator_value is Dictionary else {} + ) + if StringName(coordinator.get(&"terminal_state", &"RUNNING")) == &"BANKRUPT": + _set_status("資金破綻により運営は終了しています。", false) + return + if bool(report.get(&"enabled", false)) \ + and int(report.get(&"deferred_runtime_minute", -1)) >= 0: + var retry_value: Variant = _runtime.call(&"acknowledge_operations_pause") + var retry_ok := retry_value is Dictionary and bool(retry_value.get(&"ok", false)) + _set_status( + "運営処理を再開しました。" if retry_ok \ + else "運営処理を再開できません: %s" % _result_message(retry_value), + retry_ok + ) + _refresh_dashboard() + _update_pause_and_speed() + return + GameClock.toggle_pause() + _update_pause_and_speed() + + +func _set_speed(value: int) -> void: + GameClock.set_speed(value) + _update_pause_and_speed() + + +func _update_pause_and_speed() -> void: + var operations_deferred := false + var operations_terminal := false + var event_waiting := false + if _runtime != null and _runtime.has_method(&"get_operations_report"): + var report_value: Variant = _runtime.call(&"get_operations_dashboard_report") if _runtime.has_method(&"get_operations_dashboard_report") else _runtime.call(&"get_operations_report") + if report_value is Dictionary: + operations_deferred = bool(report_value.get(&"enabled", false)) \ + and int(report_value.get(&"deferred_runtime_minute", -1)) >= 0 + var coordinator_value: Variant = report_value.get(&"coordinator", {}) + if coordinator_value is Dictionary: + operations_terminal = StringName( + coordinator_value.get(&"terminal_state", &"RUNNING") + ) == &"BANKRUPT" + var event_service: Variant = _service(&"event_service") + if event_service != null and event_service.has_method(&"get_foreground_instance"): + var foreground: Variant = event_service.call(&"get_foreground_instance") + event_waiting = foreground != null and int(foreground.state) == 2 + if phase_label != null: + if operations_terminal: + phase_label.text = "運営終了" + elif operations_deferred: + phase_label.text = "要確認" + elif event_waiting: + phase_label.text = "選択待ち" + else: + phase_label.text = "停止中" if GameClock.paused else _phase_name(GameClock.get_phase()) + phase_label.add_theme_color_override("font_color", Color("e88972") if GameClock.paused else Color("f0c574")) + if pause_button != null: + pause_button.text = ( + "破綻" + if operations_terminal else ( + "運営再試行" + if operations_deferred else ( + "イベント待ち" + if event_waiting else ("再開" if GameClock.paused else "停止") + ) + ) + ) + pause_button.disabled = operations_terminal or event_waiting + for raw_speed: Variant in speed_buttons: + var button := speed_buttons[raw_speed] as Button + button.set_pressed_no_signal(not GameClock.paused and int(raw_speed) == GameClock.speed) + + +func _on_time_changed(current_day: int, hour: int, minute: int) -> void: + time_label.text = "%d日目 %02d:%02d" % [current_day, hour, minute] + _update_pause_and_speed() + + +func _on_phase_changed(phase: GameClock.Phase) -> void: + if not GameClock.paused: + phase_label.text = _phase_name(phase) + + +func _phase_name(phase: GameClock.Phase) -> String: + match phase: + GameClock.Phase.MORNING: + return "朝" + GameClock.Phase.DAY: + return "昼" + GameClock.Phase.EVENING: + return "夕方" + _: + return "夜" + + +const RESOURCE_TITLES := {&"gold": "所持金", &"mana": "マナ", &"materials": "資材", &"faith": "信仰"} + +func _resource_title(resource_name: StringName) -> String: + return String(RESOURCE_TITLES.get(resource_name, String(resource_name))) + + +func _on_resource_changed(resource_name: StringName, value: int) -> void: + if not resource_labels.has(resource_name): + return + var label := resource_labels[resource_name] as Label + label.text = "%s %d" % [_resource_title(resource_name), value] + if resource_name == &"mana": + label.mouse_filter = Control.MOUSE_FILTER_STOP + label.tooltip_text = "残量0で全館停止。接客・調理・搬入・エレベーターと新規入館が停止します。\n残量と配電が回復すると再開します。破壊・摘発による休止は解除されません。" + if value <= 0: + label.text += "・停止" + label.add_theme_color_override("font_color", Color("ffb095")) + else: + label.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff2d0"))) + + +func _button(text_value: String, callback: Callable, minimum_width := 82) -> Button: + var button := Button.new() + button.text = text_value + button.custom_minimum_size = Vector2(minimum_width, 28) + button.add_theme_font_size_override("font_size", 12) + button.pressed.connect(callback) + _apply_button_style(button) + return button + + +func _hud_value_label(text_value: String, minimum_width: float) -> Label: + var label := Label.new() + label.text = text_value + label.custom_minimum_size.x = minimum_width + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.add_theme_font_size_override("font_size", 12) + label.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff2d0"))) + return label + + +func _icon_rect(icon_texture: Texture2D, side: float) -> TextureRect: + var icon := TextureRect.new() + icon.texture = icon_texture + icon.custom_minimum_size = Vector2(side, side) + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + return icon + + +func _apply_button_icon(button: Button, icon_texture: Texture2D, maximum_width: int) -> void: + if icon_texture == null: + return + button.icon = icon_texture + button.add_theme_constant_override("icon_max_width", maximum_width) + button.expand_icon = true + + +func _apply_button_style(button: Button) -> void: + if _audio_feedback != null: + _audio_feedback.bind_button(button) + button.add_theme_color_override("font_color", _visual_color(&"text_light", Color("fff0c8"))) + button.add_theme_color_override("font_hover_color", Color("fff8e5")) + button.add_theme_color_override("font_pressed_color", Color("fff8e5")) + button.add_theme_color_override("font_disabled_color", Color("a98e68")) + button.add_theme_stylebox_override("normal", _themed_panel_style(&"button_normal", _visual_color(&"button_normal", Color("75451f")), _visual_color(&"border_gold", Color("b77b37")), 7, 7)) + button.add_theme_stylebox_override("hover", _themed_panel_style(&"button_hover", _visual_color(&"button_hover", Color("8d5628")), _visual_color(&"border_gold", Color("e4b45c")), 7, 7)) + button.add_theme_stylebox_override("pressed", _themed_panel_style(&"button_pressed", _visual_color(&"button_pressed", Color("496528")), _visual_color(&"border_gold", Color("d8bd61")), 7, 7)) + button.add_theme_stylebox_override("focus", _themed_panel_style(&"button_pressed", _visual_color(&"button_pressed", Color("496528")), _visual_color(&"border_gold", Color("f2d97d")), 7, 7)) + button.add_theme_stylebox_override("disabled", _themed_panel_style(&"button_disabled", _visual_color(&"button_disabled", Color("574334")), Color("7b6955"), 7, 7)) + + +func _section_heading(text_value: String) -> Label: + var heading := Label.new() + heading.text = text_value + heading.add_theme_font_size_override("font_size", 14) + heading.add_theme_color_override("font_color", _visual_color(&"text_dark", Color("55371e"))) + LayerSkin.register_text(heading, GameState.active_layer) + return heading + + +func _panel_style(color: Color, border_color: Color, radius := 8, margin := 12) -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = color + style.border_color = border_color + style.set_border_width_all(1) + style.set_corner_radius_all(mini(radius, 1)) + style.set_content_margin_all(margin) + style.shadow_size = 0 + return style + + +func _themed_panel_style(skin_id: StringName, color: Color, border_color: Color, radius := 0, margin := 5) -> StyleBox: + # Small flat frames keep attention on the animated cross-section. + var style := _panel_style(color, border_color, mini(radius, 1), mini(margin, 6)) + LayerSkin.register_style(style, skin_id, GameState.active_layer) + return style + + +func _set_status(message: String, success: bool, quiet := false) -> void: + if build_status_label == null: + return + build_status_label.text = message + build_status_label.tooltip_text = message + if quiet: + build_status_label.add_theme_color_override("font_color", Color("c7d3a8") if success else Color("ffb498")) + else: + build_status_label.add_theme_color_override("font_color", Color("d8e8ad") if success else Color("ffb498")) + + +func _visual_color(property_name: StringName, fallback: Color) -> Color: + if visual_theme == null: + return fallback + var value: Variant = visual_theme.get(property_name) + return value if value is Color else fallback + + +func _service(property_name: StringName) -> Variant: + if _runtime == null or not _runtime is Object: + return null + for property: Dictionary in _runtime.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + return _runtime.get(property_name) + return null + + +func _call_array(service: Variant, method_name: StringName) -> Array: + if service == null or not service.has_method(method_name): + return [] + var result: Variant = service.call(method_name) + return result if result is Array else [] + + +func _facility_definition(definition_id: StringName) -> Variant: + var registry: Variant = _service(&"definition_registry") + if registry == null or not registry.has_method(&"get_facility"): + return null + return registry.call(&"get_facility", definition_id) + + +func _construction_facility(instance_id: StringName) -> Dictionary: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_facility"): + return {} + var result: Variant = construction.call(&"get_facility", instance_id) + return result if result is Dictionary else {} + + +func _facility_reasons(reasons_value: Variant) -> String: + if not reasons_value is Array: + return "" + var names: Array[String] = [] + for reason_value: Variant in reasons_value: + var reason := StringName(reason_value) + names.append(String(FACILITY_REASON_NAMES.get(reason, reason))) + return "・".join(names) + + +func _definition_value(source: Variant, property_name: StringName, fallback: Variant) -> Variant: + if source is Dictionary: + return source.get(property_name, source.get(String(property_name), fallback)) + if source == null or not source is Object: + return fallback + for property: Dictionary in source.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + return source.get(property_name) + return fallback + + +func _ensure_guest_observer() -> GuestObservationPanel: + if guest_observer == null: + guest_observer = AcceptDialog.new() + guest_observer.title = "来客の様子" + guest_observer.ok_button_text = "閉じる" + var content := preload("res://scripts/ui/guest_observation_panel.gd").new() + content.configure(_runtime) + var scroll := ScrollContainer.new() + scroll.name = "GuestObservationScroll" + scroll.custom_minimum_size = Vector2(420, 340) + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + content.size_flags_horizontal = Control.SIZE_EXPAND_FILL + guest_observer.add_child(scroll) + scroll.add_child(content) + add_child(guest_observer) + return guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel + + +func _show_guest_observer() -> void: + var panel := _ensure_guest_observer() + guest_observer.popup_centered(Vector2i(460, 410)) + panel.refresh() + + +func _preview_dimension_migration() -> void: + var report: Dictionary = _runtime.preview_facility_dimensions_at_path(manual_save_path) + if not report.get(&"ok",false): + _set_status("旧寸法の修正を検証できませんでした。保存データは変更していません。",false) + return + if report.get(&"changes",[]).is_empty(): + _set_status("この手動セーブに旧寸法の施設はありません。通常の読込を利用してください。",true) + return + _dimension_migration_digest=report[&"source_digest"] + if dimension_migration_dialog==null: + dimension_migration_dialog=ConfirmationDialog.new() + dimension_migration_dialog.name="DimensionMigrationDialog" + dimension_migration_dialog.title="旧寸法を修正して読み込む" + dimension_migration_dialog.ok_button_text="修正して読込" + dimension_migration_dialog.cancel_button_text="戻る" + dimension_migration_dialog.confirmed.connect(_confirm_dimension_migration) + dimension_migration_dialog.canceled.connect(func(): _dimension_migration_digest="") + add_child(dimension_migration_dialog) + var lines: PackedStringArray=[] + for change: Dictionary in report[&"changes"]: + lines.append("%s%s → %d×%d" % [change[&"name"],"(建設中)" if change[&"building"] else "",change[&"size"].x,change[&"size"].y]) + dimension_migration_dialog.dialog_text="保存済みの手動セーブを修正します。\n"+"\n".join(lines)+"\n\n修正前のバックアップを残します。\n現在の未保存の進行は読み込みで置き換わります。" + dimension_migration_dialog.popup_centered(Vector2i(520,280)) + +func _confirm_dimension_migration() -> void: + if _dimension_migration_digest.is_empty(): return + var expected := _dimension_migration_digest + _dimension_migration_digest="" + var report: Dictionary=_runtime.migrate_facility_dimensions_at_path(manual_save_path,&"",expected) + if not report.get(&"ok",false): + _set_status("旧寸法の修正に失敗しました。読込は行っていません。保存データを確認してください。",false) + return + _load_manual(true) + if build_status_label.text=="手動セーブを読み込みました。": + _set_status("旧寸法を修正して読み込みました。修正前のバックアップを保持しています。",true) + + +func _on_defense_notice_input(event: InputEvent) -> void: + if (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT) or event.is_action_pressed(&"ui_accept"): + alert_count_label.accept_event() + _show_defense_manager() + + +func _ensure_defense_manager() -> DefenseManagementPanel: + if defense_manager == null: + defense_manager = AcceptDialog.new() + defense_manager.name = "DefenseManager" + defense_manager.title = "世界樹の防衛" + defense_manager.ok_button_text = "閉じる" + var dialog_theme := Theme.new() + dialog_theme.set_stylebox("panel", "AcceptDialog", _themed_panel_style(&"panel_parchment", Color("f4e3bd"), Color("a96f2f"), 8, 10)) + var window_border := _themed_panel_style(&"panel_outer", Color("49311f"), Color("a96f2f"), 8, 10) + window_border.expand_margin_top = 32 + dialog_theme.set_stylebox("embedded_border", "Window", window_border) + dialog_theme.set_color("title_color", "Window", Color("fff0cf")) + defense_manager.theme = dialog_theme + _apply_button_style(defense_manager.get_ok_button()) + var panel := DefenseManagementPanelScript.new() + panel.configure(_runtime, _apply_button_style) + panel.configure_audio(Callable(_audio_feedback, "submit_command")) + panel.status_requested.connect(_on_staff_management_status) + var scroll := ScrollContainer.new() + scroll.name = "DefenseManagementScroll" + scroll.custom_minimum_size = Vector2(480, 460) + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + defense_manager.add_child(scroll) + scroll.add_child(panel) + add_child(defense_manager) + return defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + + +func _show_defense_manager() -> void: + var panel := _ensure_defense_manager() + panel.refresh() + LayerSkin.apply_tree(defense_manager, GameState.active_layer) + defense_manager.popup_centered(Vector2i(520, 540)) + + +func _restore_defense_interface_selection(manifest: Variant) -> void: + if defense_manager != null: + defense_manager.hide() + var existing := defense_manager.find_child("DefenseManagementPanel", true, false) as DefenseManagementPanel + if existing != null: existing.clear_selection() + if not manifest is Dictionary: return + var selection: Variant = manifest.get("interface_selection", {}) + if not selection is Dictionary or selection.get("version", 0) != 1: return + var id_value: Variant = selection.get("defense_id", "") + if not id_value is String or id_value.is_empty() or id_value.length() > 256: return + var raid_id := StringName(id_value) + if _service(&"defense_service").get_management_detail(raid_id).is_empty(): return + _ensure_defense_manager().select_raid(raid_id) + if selection.get("defense_open", false) is bool and selection.get("defense_open", false): + _show_defense_manager() + + +func _setup_ui_audio() -> void: + _paired_bgm = PairedBGMScript.new() + _paired_bgm.name = "PairedBGM" + _paired_bgm.configure(GameState.active_layer) + add_child(_paired_bgm) + _audio_feedback = UIAudioFeedbackScript.new() + _audio_feedback.name = "UIAudioFeedback" + _audio_feedback.configure(_runtime, audio_settings_path) + add_child(_audio_feedback) + _audio_feedback.begin_loaded_epoch(_current_audio_foreground()) + _audio_settings_dialog = UIAudioSettingsDialogScript.new() + _audio_settings_dialog.configure(_audio_feedback, _apply_button_style) + add_child(_audio_settings_dialog) + + +func _current_audio_foreground() -> Variant: + var events: Variant = _service(&"event_service") + if events != null and events.has_method(&"get_foreground_instance"): + return events.call(&"get_foreground_instance") + return null + + +func _show_audio_settings() -> void: + if _audio_settings_dialog != null: + _audio_settings_dialog.show_settings() + + +func _on_audio_world_selection(_id: StringName) -> void: + if _audio_feedback != null: + _audio_feedback.request_click() + + +func _submit_ui_command( + command_type: StringName, payload: Dictionary = {}, actor_id: StringName = &"player", + target_id: StringName = &"", priority := 0, execute_at := -1, + idempotency_key: StringName = &"" +) -> Variant: + if _audio_feedback != null: + return _audio_feedback.submit_command(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) + return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) + +func _toggle_demolition_mode() -> void: + _set_demolition_mode(not _demolition_mode) + + +func _set_demolition_mode(enabled: bool, announce := true) -> void: + if enabled: + _set_pan_mode(false) + _set_excavation_mode(false, false) + _clear_build_selection(false) + _demolition_mode = enabled + _demolition_target = &"" + if demolition_dialog != null: + demolition_dialog.hide() + if demolition_button != null: + demolition_button.set_pressed_no_signal(enabled) + if world_preview != null: + world_preview.set_demolition_mode(enabled) + if announce: + _set_status("撤去する施設のマスを選択してください。確認画面で取り消せます。" if enabled else "撤去モードを解除しました。", true, true) + + +func _preview_demolition(cell: Vector3i) -> void: + # Selection only; Runtime owns eligibility, occupancy and refunds. + _demolition_target = world_preview.facility_at_canvas(world_preview.cell_to_canvas(cell)) + world_preview.selected_entity_id = _demolition_target + if demolition_dialog == null: + demolition_dialog = ConfirmationDialog.new() + demolition_dialog.name = "DemolitionDialog" + demolition_dialog.title = "施設の撤去" + demolition_dialog.ok_button_text = "撤去する" + demolition_dialog.cancel_button_text = "戻る" + demolition_dialog.confirmed.connect(_confirm_demolition) + demolition_dialog.canceled.connect(_cancel_demolition) + add_child(demolition_dialog) + _apply_button_style(demolition_dialog.get_ok_button()) + _apply_button_style(demolition_dialog.get_cancel_button()) + demolition_dialog.dialog_text = "座標 (%d, %d / %d)\n施設: %s\n\n撤去を指示しますか?" % [cell.x, cell.y, cell.z, String(_demolition_target)] + demolition_dialog.popup_centered(Vector2i(420, 210)) + + +func _cancel_demolition() -> void: + _demolition_target = &"" + _set_status("撤去を取り消しました。", true, true) + + +func _confirm_demolition() -> void: + if not _demolition_mode: + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var target := _demolition_target + _demolition_target = &"" + var result: CommandResult = _runtime.submit_command(&"demolish_facility", {&"instance_id": target}) + # Paused play returns execution synchronously; running play returns acceptance. + if result.is_success() and _runtime.command_queue.get_processed_result(result.command_id) == null: + _pending_demolition_commands[result.command_id] = target + _set_status("撤去を受け付けました", true) + return + _show_demolition_result(result) + + +func _show_demolition_result(result: CommandResult) -> void: + if result.is_success(): + var refund: Dictionary = result.details.get(&"refund", {}) + var keys: Array = refund.keys() + keys.sort() + var amounts: PackedStringArray = [] + for key: Variant in keys: + if int(refund[key]) != 0: + amounts.append("%s %d" % [_resource_title(StringName(key)), int(refund[key])]) + _set_status("撤去しました(返金: %s)" % "、".join(amounts) if not amounts.is_empty() else "撤去しました(返金なし)", true) + else: + _set_status("撤去できません: " + result.message, false) + _refresh_selected_entity() + world_preview.queue_redraw() diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt new file mode 100644 index 0000000..fa32fe0 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/scenes/main/world_preview.gd.source.txt @@ -0,0 +1,2232 @@ +class_name WorldPreview +extends Control + +signal facility_selected(instance_id: StringName) +signal staff_assignment_dropped(data: Dictionary, facility_id: StringName) +signal construction_selected(job_id: StringName) +signal legacy_stair_selected(stair_id: StringName) +signal cell_clicked(cell: Vector3i) +signal hovered_cell_changed(cell: Variant) +signal build_preview_changed(report: Dictionary) +signal excavation_preview_changed(report: Dictionary) + +enum LayerMode { + FRONT, + BACK, +} + +const RoomPresentation := preload("res://scripts/ui/room_presentation.gd") +const People := preload("res://scripts/ui/pixel_people.gd") +const PixelArt := preload("res://scripts/ui/pixel_tower_art.gd") +const InactiveLayerRooms := preload("res://scripts/ui/inactive_layer_rooms.gd") +const LAYER_LENS_SHADER := preload("res://assets/shaders/layer_lens.gdshader") +const LAYER_TRANSITION_SECONDS := 0.55 +const MAP_PADDING := Vector2(76.0, 70.0) +const INVALID_PREVIEW := {&"can_build": false, &"error_codes": [], &"reasons": [], &"cells": []} +const INVALID_EXCAVATION_PREVIEW := { + &"ok": false, + &"cells": [], + &"cost": {}, + &"build_minutes": 0, + &"errors": [&"MISSING_DEPENDENCY"], +} + +var layer_mode := LayerMode.FRONT: + set(value): + var previous := layer_mode + layer_mode = clampi(value, LayerMode.FRONT, LayerMode.BACK) as LayerMode + if previous != layer_mode and is_node_ready(): + _start_layer_transition() + if _hovered_cell != null: + _hovered_cell = Vector3i(_hovered_cell.x, _hovered_cell.y, layer_mode) + queue_redraw() + _update_build_preview() + _update_excavation_preview() + +var selected_entity_id: StringName = &"": + set(value): + selected_entity_id = value + queue_redraw() + +var build_definition: Variant = null +var build_rotation := 0 +var excavation_mode := false +var demolition_mode := false +var visual_theme: VisualThemeProfile = null + +var zoom_level := 1.0 +var pan_offset := Vector2.ZERO +var show_grid := false +var pan_mode := false +var _drawing := false +var _draw_cache: Dictionary = {} + +var _inactive_layer_art: Node2D +var _layer_lens: ColorRect +var _layer_transition_elapsed := LAYER_TRANSITION_SECONDS + +var _animation_time := 0.0 +var _animation_frame := -1 +var _actor_tracks: Dictionary = {} +var _elevator_tracks: Dictionary = {} +var _boarded_actor_ids: Dictionary = {} +var _carried_patients: Dictionary = {} +var _treated_patients: Dictionary = {} +var _resting_staff: Dictionary = {} +var _room_presentations: Dictionary = {} +var _queue_positions: Dictionary = {} +var _queue_hidden: Dictionary = {} +var _landing_badges: Array[Dictionary] = [] +var _transport_report: Dictionary = {} +var _life_observation: Dictionary = {} +var _life_overlay: Node2D +var _life_observation_serial := 0 +var _dragging := false +var _drag_button := MOUSE_BUTTON_NONE + +var _runtime: Variant = null +var _hovered_cell: Variant = null +var _preview_topology_key: Array = [] +var _connection_cache_key: Array = [] +var _connection_cache_report: Dictionary = {} +var _last_preview_report: Dictionary = INVALID_PREVIEW.duplicate(true) +var _last_excavation_preview_report: Dictionary = INVALID_EXCAVATION_PREVIEW.duplicate(true) + + +func _ready() -> void: + clip_contents = true + texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + focus_mode = Control.FOCUS_CLICK + mouse_filter = Control.MOUSE_FILTER_STOP + resized.connect(queue_redraw) + mouse_exited.connect(_on_mouse_exited) + if _runtime == null: + _runtime = get_node_or_null("/root/SimulationRuntime") + _connect_runtime() + _inactive_layer_art = InactiveLayerRooms.new() + _inactive_layer_art.name = "InactiveLayerRooms" + add_child(_inactive_layer_art) + _life_overlay = Node2D.new() + _life_overlay.name = "LifeOverlay" + _life_overlay.draw.connect(_draw_life_overlay) + add_child(_life_overlay) + _layer_lens = ColorRect.new() + _layer_lens.name = "LayerLens" + _layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE + _layer_lens.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + var lens_material := ShaderMaterial.new() + lens_material.shader = LAYER_LENS_SHADER + _layer_lens.material = lens_material + _layer_lens.visible = false + add_child(_layer_lens) + queue_redraw() + + +func set_runtime(value: Variant) -> void: + _connection_cache_key.clear() + _connection_cache_report.clear() + _disconnect_runtime() + _runtime = value + _actor_tracks.clear() + _elevator_tracks.clear() + _boarded_actor_ids.clear() + _connect_runtime() + queue_redraw() + _update_build_preview() + _update_excavation_preview() + + +func set_visual_theme(profile: VisualThemeProfile) -> void: + visual_theme = profile + queue_redraw() + + +func set_build_definition(definition: Variant, rotation := 0) -> void: + if definition != null: + demolition_mode = false + if definition != null and excavation_mode: + set_excavation_mode(false) + build_definition = definition + build_rotation = _normalized_rotation(rotation) + _update_build_preview() + queue_redraw() + + +func clear_build_definition() -> void: + build_definition = null + build_rotation = 0 + _last_preview_report = INVALID_PREVIEW.duplicate(true) + build_preview_changed.emit(_last_preview_report.duplicate(true)) + queue_redraw() + + +func set_build_rotation(value: int) -> void: + build_rotation = _normalized_rotation(value) + _update_build_preview() + queue_redraw() + + +func set_excavation_mode(enabled: bool) -> void: + if enabled: + demolition_mode = false + if excavation_mode == enabled: + _update_excavation_preview() + return + excavation_mode = enabled + if excavation_mode and build_definition != null: + build_definition = null + build_rotation = 0 + _last_preview_report = INVALID_PREVIEW.duplicate(true) + build_preview_changed.emit(_last_preview_report.duplicate(true)) + _update_excavation_preview() + queue_redraw() + + +func get_excavation_preview_report(cell: Variant = null) -> Dictionary: + var target: Variant = _hovered_cell if cell == null else cell + if not target is Vector3i: + return INVALID_EXCAVATION_PREVIEW.duplicate(true) + var excavation: Variant = _service(&"excavation_service") + if excavation != null and excavation.has_method(&"validate_excavation"): + var report: Variant = excavation.call( + &"validate_excavation", + Vector3i(target.x, target.y, layer_mode) + ) + if report is Dictionary: + return report.duplicate(true) + return INVALID_EXCAVATION_PREVIEW.duplicate(true) + + +func get_hovered_cell() -> Variant: + return _hovered_cell + + +func clear_hovered_cell() -> void: + _set_hovered_cell(null) + + +func get_build_preview_report(cell: Variant = null) -> Dictionary: + var target: Variant = _hovered_cell if cell == null else cell + if build_definition == null or not target is Vector3i: + return INVALID_PREVIEW.duplicate(true) + if _runtime != null and _runtime.has_method(&"preview_build_facility"): + var result: Dictionary = _runtime.call(&"preview_build_facility", build_definition.id, Vector3i(target.x, target.y, layer_mode), build_rotation) + result[&"connection"] = _build_connection_report( + _cells_from_serialized(result.get(&"cells", [])), + Vector3i(target.x, target.y, layer_mode)) + return result + var construction: Variant = _service(&"construction_service") + if construction != null and construction.has_method(&"validate_placement"): + var report: Variant = construction.call( + &"validate_placement", + build_definition, + Vector3i(target.x, target.y, layer_mode), + build_rotation + ) + if report != null and report.has_method(&"to_dictionary"): + var result: Dictionary = report.call(&"to_dictionary") + result[&"connection"] = _build_connection_report( + _cells_from_serialized(result.get(&"cells", [])), + Vector3i(target.x, target.y, layer_mode)) + return result + return { + &"can_build": _fallback_cells_are_available(Vector3i(target.x, target.y, layer_mode)), + &"error_codes": [], + &"reasons": [], + &"cells": _serialize_cells(_fallback_build_cells(Vector3i(target.x, target.y, layer_mode))), + } + + +func get_map_rect() -> Rect2: + if _drawing and _draw_cache.has(&"rect"): + return _draw_cache[&"rect"] + var bounds := _world_bounds() + if bounds.is_empty(): + return Rect2() + var dimensions := _cell_dimensions() + var map_size := Vector2( + int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1, + int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1 + ) * dimensions + var result_value = Rect2(((size - map_size) * 0.5 + pan_offset).floor(), map_size) + + if _drawing: + _draw_cache[&"rect"] = result_value + return result_value + + +func _cell_dimensions() -> Vector2: + if _drawing and _draw_cache.has(&"dimensions"): + return _draw_cache[&"dimensions"] + var bounds := _world_bounds() + if bounds.is_empty(): + return Vector2(32, 48) + var columns := maxi(int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1, 1) + var rows := maxi(int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1, 1) + var available := (size - MAP_PADDING * 2.0).max(Vector2.ONE) + var fit := minf(available.x / (columns * 16.0), available.y / (rows * 24.0)) + # Native 16px units use integer magnification. Large worlds can be viewed + # at overview scale, then enlarged with the same invertible camera mapping. + var pixel := maxf(floorf(fit), 1.0) if fit >= 1.0 else maxf(fit, 0.25) + var result_value = (Vector2(16, 24) * pixel * zoom_level).round().max(Vector2(4, 6)) + + if _drawing: + _draw_cache[&"dimensions"] = result_value + return result_value + + +func set_zoom(value: float, anchor := Vector2(-1, -1)) -> void: + var before := get_map_rect() + var at := size * 0.5 if anchor.x < 0 else anchor + var relative := (at - before.position) / before.size if before.has_area() else Vector2(0.5, 0.5) + zoom_level = clampf(value, 0.5, 4.0) + var after := get_map_rect() + pan_offset += at - (after.position + relative * after.size) + _clamp_pan() + _set_hovered_cell(canvas_to_cell(at)) + queue_redraw() + + +func reset_view() -> void: + zoom_level = 1.0 + pan_offset = Vector2.ZERO + _actor_tracks.clear() + queue_redraw() + + +func focus_operating_area() -> void: + # Explicit framing, never continuous tracking: the player's pan stays put. + zoom_level = 1.0 + pan_offset = Vector2.ZERO + var area := Rect2() + var construction: Variant = _service(&"construction_service") + if construction != null: + for item: Dictionary in construction.get_facilities(): + if int(item.get(&"layer", 0)) != layer_mode: + continue + var cells := _cells_from_serialized(item.get(&"occupied_cells", [])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + area = area.merge(rect) if area.has_area() else rect + if area.has_area(): + var relative := (area.get_center()-get_map_rect().position) / get_map_rect().size + zoom_level = clampf(minf(size.x*0.68/area.size.x, size.y*0.65/area.size.y), 0.5, 2.0) + var rect := get_map_rect() + pan_offset += size*Vector2(0.5, 0.58) - (rect.position+rect.size*relative) + else: + zoom_level = 2.0 + pan_offset += size*Vector2(0.5, 0.65) - cell_to_canvas(Vector3i(0, 1, layer_mode)) + _clamp_pan() + clear_hovered_cell() + queue_redraw() + + +func set_pan_mode(enabled: bool) -> void: + pan_mode = enabled + _end_pan_drag() + clear_hovered_cell() + + +func _end_pan_drag() -> void: + _dragging = false + _drag_button = MOUSE_BUTTON_NONE + mouse_default_cursor_shape = Control.CURSOR_DRAG if pan_mode else Control.CURSOR_ARROW + + +func _notification(what: int) -> void: + if what == NOTIFICATION_APPLICATION_FOCUS_OUT: + _end_pan_drag() + + +func _clamp_pan() -> void: + var extent := get_map_rect().size * 0.5 + size * 0.3 + pan_offset = pan_offset.clamp(-extent, extent) + + +func _start_layer_transition() -> void: + if _layer_lens == null: return + _layer_transition_elapsed = 0.0 + var center := size * 0.5 + if _hovered_cell != null: + center = cell_to_canvas(_hovered_cell) + var lens_material := _layer_lens.material as ShaderMaterial + lens_material.set_shader_parameter("center", center / size.max(Vector2.ONE)) + lens_material.set_shader_parameter("extent", size) + lens_material.set_shader_parameter("progress", 0.0) + _layer_lens.visible = true + + +func _process(delta: float) -> void: + # UI transitions complete even in active pause; actor time remains frozen. + if _layer_lens != null and _layer_lens.visible: + _layer_transition_elapsed = minf(_layer_transition_elapsed + maxf(delta, 0.0), LAYER_TRANSITION_SECONDS) + var lens_material := _layer_lens.material as ShaderMaterial + lens_material.set_shader_parameter("extent", size) + lens_material.set_shader_parameter("progress", _layer_transition_elapsed / LAYER_TRANSITION_SECONDS) + _layer_lens.visible = _layer_transition_elapsed < LAYER_TRANSITION_SECONDS + if build_definition != null and _hovered_cell is Vector3i and _current_preview_topology() != _preview_topology_key: + _update_build_preview() + queue_redraw() + var clock := get_node_or_null("/root/GameClock") + if clock != null and bool(clock.get("paused")): + return + var speed := float(clock.get("speed")) if clock != null else 1.0 + _animation_time += delta * maxf(speed, 0.0) + var frame := int(_animation_time * 12.0) + if frame != _animation_frame: + _animation_frame = frame + queue_redraw() + + +func _unhandled_key_input(event: InputEvent) -> void: + if not event is InputEventKey or not event.pressed or event.echo: + return + if event.keycode == KEY_HOME: + reset_view() + get_viewport().set_input_as_handled() + elif event.keycode == KEY_G: + show_grid = not show_grid + queue_redraw() + get_viewport().set_input_as_handled() + + +func cell_to_canvas(cell: Vector3i) -> Vector2: + var bounds := _world_bounds() + if bounds.is_empty(): + return Vector2.ZERO + return get_map_rect().position + Vector2( + float(cell.x - int(bounds[&"min_x"])) + 0.5, + float(int(bounds[&"max_y"]) - cell.y) + 0.5 + ) * _cell_dimensions() + + +func canvas_to_cell(canvas_position: Vector2) -> Variant: + var bounds := _world_bounds() + var map_rect := get_map_rect() + if bounds.is_empty() or not map_rect.has_area() or not map_rect.has_point(canvas_position): + return null + var local_position := (canvas_position - map_rect.position) / _cell_dimensions() + var cell := Vector3i(int(bounds[&"min_x"]) + floori(local_position.x), int(bounds[&"max_y"]) - floori(local_position.y), layer_mode) + var world: Variant = _service(&"world_service") + if world != null and world.has_method(&"is_in_bounds") and not bool(world.call(&"is_in_bounds", cell)): + return null + return cell + + +func get_cell_rect(cell: Vector3i) -> Rect2: + if _world_bounds().is_empty(): + return Rect2() + var dimensions := _cell_dimensions() + return Rect2(cell_to_canvas(cell) - dimensions * 0.5, dimensions) + + +func facility_at_canvas(canvas_position: Vector2) -> StringName: + var cell: Variant = canvas_to_cell(canvas_position) + if not cell is Vector3i: + return &"" + var world: Variant = _service(&"world_service") + if world == null or not world.has_method(&"get_occupant"): + return &"" + var occupant_id := StringName(world.call(&"get_occupant", cell)) + if occupant_id.is_empty(): + return &"" + var kind := &"" + if world.has_method(&"get_occupant_kind"): + kind = StringName(world.call(&"get_occupant_kind", cell)) + return occupant_id if kind == &"facility" else &"" + + +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if not event.pressed and event.button_index == _drag_button: + _end_pan_drag() + accept_event() + return + if event.button_index == MOUSE_BUTTON_MIDDLE or (pan_mode and event.button_index == MOUSE_BUTTON_LEFT): + if event.pressed: + _dragging = true + _drag_button = event.button_index + mouse_default_cursor_shape = Control.CURSOR_DRAG + accept_event() + return + if event.pressed and event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]: + if pan_mode and not event.ctrl_pressed: + var direction := 1.0 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -1.0 + pan_offset.y += direction * _cell_dimensions().y * maxf(event.factor, 0.0) + _clamp_pan() + queue_redraw() + else: + set_zoom(zoom_level + (0.5 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -0.5), event.position) + accept_event() + return + if event is InputEventMouseMotion and _dragging: + var mask := MOUSE_BUTTON_MASK_LEFT if _drag_button == MOUSE_BUTTON_LEFT else MOUSE_BUTTON_MASK_MIDDLE + if not event.button_mask & mask: + _end_pan_drag() + return + pan_offset += event.relative + _clamp_pan() + queue_redraw() + accept_event() + return + if event is InputEventMouseMotion: + if not pan_mode: + _set_hovered_cell(canvas_to_cell(event.position)) + return + if not event is InputEventMouseButton or not event.pressed or event.button_index != MOUSE_BUTTON_LEFT: + return + var cell: Variant = canvas_to_cell(event.position) + if not cell is Vector3i: + return + if build_definition != null or excavation_mode or demolition_mode: + cell_clicked.emit(cell) + accept_event() + return + var stair_id := legacy_stair_at_canvas(event.position) + if not stair_id.is_empty(): + selected_entity_id = stair_id + legacy_stair_selected.emit(stair_id) + accept_event() + return + var world: Variant = _service(&"world_service") + if world != null and world.has_method(&"get_occupant"): + var occupant_id := StringName(world.call(&"get_occupant", cell)) + var kind := &"" + if world.has_method(&"get_occupant_kind"): + kind = StringName(world.call(&"get_occupant_kind", cell)) + if not occupant_id.is_empty() and kind == &"facility": + selected_entity_id = occupant_id + facility_selected.emit(occupant_id) + accept_event() + return + if not occupant_id.is_empty() and kind == &"construction": + selected_entity_id = occupant_id + construction_selected.emit(occupant_id) + accept_event() + return + cell_clicked.emit(cell) + accept_event() + + +func _draw() -> void: + _drawing = true + _draw_cache.clear() + _draw_contents() + _drawing = false + _draw_cache.clear() + + +func _draw_contents() -> void: + _life_observation = {} + if _life_overlay != null: + _life_overlay.queue_redraw() + if _inactive_layer_art != null: + _inactive_layer_art.visible = false + _draw_background() + var bounds := _world_bounds() + if bounds.is_empty(): + _draw_center_message("世界データを待っています") + return + var map_rect := get_map_rect() + if map_rect.size == Vector2.ZERO: + return + _life_observation = _build_life_observation() + _life_observation_serial += 1 + _life_observation[&"observation_serial"] = _life_observation_serial + _life_observation[&"overlay_consumed_serial"] = -1 + _room_presentations = _life_observation[&"rooms"] + _carried_patients = _life_observation[&"carried_patients"] + _treated_patients = _life_observation[&"treated_patients"] + _resting_staff = _life_observation[&"resting_staff"] + _queue_positions = _life_observation[&"queue_positions"] + _queue_hidden = _life_observation[&"queue_hidden"] + _landing_badges.assign(_life_observation[&"landing_badges"]) + _transport_report = _life_observation[&"elevators"] + _boarded_actor_ids = _life_observation[&"boarded"] + _actor_tracks = _life_observation[&"actor_tracks"] + _elevator_tracks = _life_observation[&"elevator_tracks"] + _draw_world_cells(bounds, map_rect) + _draw_world_entity_icons() + _draw_legacy_stairs() + _draw_other_layer() + _draw_construction_labels() + _draw_facility_labels() + _draw_excavation_jobs() + _draw_build_preview() + _draw_excavation_preview() + _draw_hover() + _draw_map_caption(map_rect) + + +## The report describes the last submitted draw, not an independently sampled world. +## Callers cannot mutate either the frame or its interpolation tracks. +func get_life_observation_report() -> Dictionary: + return _life_observation.duplicate(true) + + +## Pure with respect to Runtime and this Control: helpers mutate only fresh local +## dictionaries (including copies of visual tracks). No whole Runtime snapshot +## is copied per frame; whole typed snapshots belong to sparse diagnostics. +func _build_life_observation() -> Dictionary: + var model := _observe_rooms() + model.merge(_observe_queues()) + model[&"layer"] = layer_mode + model[&"zoom"] = zoom_level + model[&"pan"] = pan_offset + model[&"viewport"] = size + model[&"animation_time"] = _animation_time + model[&"actor_tracks"] = _actor_tracks.duplicate(true) + model[&"elevator_tracks"] = _elevator_tracks.duplicate(true) + model[&"boarded"] = {} + var clock := get_node_or_null("/root/GameClock") + model[&"paused"] = clock != null and bool(clock.get("paused")) + model[&"cabins"] = _observe_elevators(model) + model[&"actor_drawings"] = _observe_actors(model) + var guests: Array[Dictionary] = [] + var staff: Array[Dictionary] = [] + var tasks: Array[Dictionary] = [] + var guest_service: Variant = _service(&"guest_service") + if guest_service != null and guest_service.has_method(&"get_guests"): + for guest: GuestRuntime in guest_service.get_guests(): + guests.append({&"id": guest.guest_id, &"state": GuestRuntime.State.keys()[guest.state], + &"cell": guest.current_cell, &"target": guest.target_facility_id, + &"service": guest.current_service_id, &"queue": guest.queue_request_id}) + var staff_service: Variant = _service(&"staff_service") + if staff_service != null: + for member: StaffRuntime in staff_service.get_staff_members(): + staff.append({&"id": member.staff_id, &"state": StaffRuntime.State.keys()[member.state], + &"cell": member.current_cell, &"task": member.current_task_id, + &"facility": member.assigned_facility_id, &"role": member.role_id, + &"carried_inventory_id": member.carried_inventory_id}) + for task: StaffTask in staff_service.get_tasks(): + tasks.append({&"id": task.task_id, &"state": StaffTask.State.keys()[task.state], + &"type": task.task_type, &"staff": task.assigned_staff_id, + &"target": task.target_entity_id, &"rescue_facility": task.rescue_facility_id}) + model[&"guests"] = guests + model[&"staff"] = staff + model[&"tasks"] = tasks + _fit_life_actors(model) + var bridge: Variant = _service(&"supply_logistics") + var goods: Dictionary = bridge.get_report() if bridge != null else {&"requests": [], &"deliveries": []} + model[&"goods"] = goods + var badges: Array[Dictionary] = [] + var facilities: Variant = _service(&"facility_service") + var world: Variant = _service(&"world_service") + if facilities != null and world != null: + for instance: FacilityInstance in facilities.get_instances(): + if instance.origin.z != layer_mode: + continue + var cells: Array[Vector3i] = world.get_entity_cells(instance.instance_id) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + var data: Dictionary = model[&"rooms"][instance.instance_id] + data[&"rect"] = rect + data[&"definition_id"] = instance.definition_id + var count := RoomPresentation.slots(rect, _cell_dimensions().x / 16.0, instance.definition_id).size() + badges.append({&"facility": instance.instance_id, &"kind": &"business", + &"at": rect.position + Vector2(2, 2), + &"text": RoomPresentation.business_label(data) + " " + RoomPresentation.label(data, count)}) + var requests: Array[Dictionary] = [] + for request: Dictionary in goods.get(&"requests", []): + if request[&"source_facility_id"] == instance.instance_id or request[&"destination_facility_id"] == instance.instance_id: + requests.append(request) + if not requests.is_empty(): + badges.append({&"facility": instance.instance_id, &"kind": &"supply", + &"at": rect.position + Vector2(2, 16), &"requests": requests, + &"text": RoomPresentation.supply_label(requests)}) + model[&"badges"] = badges + _resolve_observation_labels(model) + var obstacles: Array[Rect2] = [] + for cabin: Dictionary in model[&"cabins"]: + obstacles.append(cabin[&"rect"]) + obstacles.append(cabin[&"badge_rect"]) + for collection: Array in [badges, model[&"landing_badges"], model.get(&"actor_aggregations", [])]: + for badge: Dictionary in collection: + obstacles.append(_readable_badge_rect(badge[&"at"], badge[&"text"])) + model[&"traffic_obstacles"] = obstacles + var traffic: Array[Dictionary] = [] + var path: Variant = _service(&"path_service") + var bounds := _world_bounds() + if path != null and not bounds.is_empty(): + for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1): + for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1): + var cell := Vector3i(x, y, layer_mode) + var row: Dictionary = path.get_cell_traffic(cell) + if not row.is_empty(): + var cell_rect := get_cell_rect(cell) + var rect := cell_rect.grow(-2.0) + var world_pixel := _cell_dimensions().x / 16.0 + var thickness := clampf(rect.size.y * 0.16, 2.0, 5.0) + var band := Rect2(Vector2(rect.position.x + rect.size.x * 0.12, + cell_rect.end.y - 2 * world_pixel - thickness * 0.5), + Vector2(rect.size.x * 0.76, thickness)) + traffic.append({&"cell": cell, &"traffic": row, &"rect": rect, &"band_rect": band, + &"segment_rects": _traffic_segments(band, rect, obstacles)}) + model[&"traffic"] = traffic + return model + + +## Subtract opaque observation regions, retaining a visible density mark even +## when the ordinary floor band is completely behind a moving cabin. +func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Array[Rect2]: + for y: float in [band.position.y, cell.position.y, cell.end.y - band.size.y]: + var pieces: Array[Rect2] = [Rect2(Vector2(band.position.x, y), band.size)] + for obstacle: Rect2 in obstacles: + var remaining: Array[Rect2] = [] + for piece: Rect2 in pieces: + var cut := piece.intersection(obstacle) + if not cut.has_area(): + remaining.append(piece) + continue + for part: Rect2 in [Rect2(piece.position, Vector2(piece.size.x, cut.position.y-piece.position.y)), + Rect2(Vector2(piece.position.x, cut.end.y), Vector2(piece.size.x, piece.end.y-cut.end.y)), + Rect2(Vector2(piece.position.x, cut.position.y), Vector2(cut.position.x-piece.position.x, cut.size.y)), + Rect2(Vector2(cut.end.x, cut.position.y), Vector2(piece.end.x-cut.end.x, cut.size.y))]: + if part.has_area(): + remaining.append(part) + pieces = remaining + if not pieces.is_empty(): + return pieces + return [] + + +func _resolve_observation_labels(model: Dictionary) -> void: + var placed: Array[Dictionary] = [] + for i: int in model[&"cabins"].size(): + var cabin: Dictionary = model[&"cabins"][i] + placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]}) + placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]}) + for kind: String in ["business", "supply", "landing", "aggregation"]: + var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", []) + for i: int in collection.size(): + var badge: Dictionary = collection[i] + if kind in ["business", "supply"] and badge[&"kind"] != kind: + continue + var anchor: Vector2 = badge[&"at"] + var at := anchor + var displaced_by: Array[String] = [] + # Each collision advances to an existing right edge. Positive widths + # guarantee termination; touching edges are allowed. + while true: + var blocker: Dictionary = {} + for previous: Dictionary in placed: + if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area(): + blocker = previous + break + if blocker.is_empty(): + break + displaced_by.append(blocker[&"id"]) + at.x = (blocker[&"rect"] as Rect2).end.x + badge[&"anchor"] = anchor + badge[&"at"] = at + badge[&"displaced_by"] = displaced_by.duplicate(true) + placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])}) + + +func _draw_life_badges(canvas: CanvasItem) -> void: + for badge: Dictionary in _life_observation.get(&"badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + +func _draw_background() -> void: + var night := false + var clock := get_node_or_null("/root/GameClock") + if clock != null: + var hour := int(clock.call("get_hour")) + night = hour >= 18 or hour < 6 + var sky := Color("b5cbb5") if not night else Color("273c49") + if layer_mode == LayerMode.BACK: + sky = Color("526d68") if not night else Color("233d40") + draw_rect(Rect2(Vector2.ZERO, size), sky) + # Pixel silhouettes make the world frame distinct from the usable floors. + var horizon := size.y * 0.65 + for band in range(3): + var color := [Color("8cae98"), Color("749981"), Color("567e68")][band] as Color + if night: + color = color.darkened(0.48) + for x in range(-24, int(size.x)+64, 48): + var crown_y := horizon + band*46 - posmod(x*17+band*29, 83) + draw_rect(Rect2(x, crown_y, 48, size.y-crown_y), color) + draw_rect(Rect2(x+8, crown_y-12, 32, 12), color) + if night: + for i in range(28): + var at := Vector2(posmod(i*137+31, maxi(int(size.x),1)), posmod(i*47+13,maxi(int(horizon),1))) + draw_rect(Rect2(at,Vector2(2,2)),Color("c6d3b6")) + else: + for i in range(5): + var x := float(posmod(i*263+41, maxi(int(size.x),1))) + var y := 32.0+float(i%3)*27.0 + draw_rect(Rect2(x,y,86,12),Color("d5dec4")) + draw_rect(Rect2(x+17,y-7,46,7),Color("d5dec4")) + + +func _draw_world_cells(bounds: Dictionary, map_rect: Rect2) -> void: + var world: Variant = _service(&"world_service") + if world == null: + return + var p := _cell_dimensions().x / 16.0 + var ground := get_cell_rect(Vector3i(0,0,layer_mode)).end.y + var trunk := Rect2(map_rect.position-Vector2(9*p,22*p), map_rect.size+Vector2(18*p,24*p)) + draw_rect(trunk, Color("574d39")) + draw_rect(trunk.grow(-2*p), Color("776445")) + for x in range(int(trunk.position.x+4*p),int(trunk.end.x),maxi(int(7*p),1)): + var notch := float(posmod(x*11,17))*p + draw_rect(Rect2(x,trunk.position.y+notch,2*p,trunk.size.y-notch),Color("8a7350")) + # Branches, crown and roots belong to the scenery, never facility slots. + for side in [-1,1]: + var edge := trunk.position.x if side < 0 else trunk.end.x + for branch in range(3): + var y := trunk.position.y+(branch*24+22)*p + var x := edge-(18+branch*4)*p if side<0 else edge + draw_rect(Rect2(x,y,(18+branch*4)*p,5*p),Color("69583d")) + _draw_leaf_cluster(Vector2(x+(2 if side<0 else 14)*p,y-5*p),p,branch) + var root_x := edge-28*p if side<0 else edge-2*p + draw_rect(Rect2(root_x,ground-3*p,30*p,6*p),Color("65583e")) + draw_rect(Rect2(root_x+(10*p if side<0 else 0),ground-7*p,20*p,5*p),Color("776445")) + draw_rect(Rect2(trunk.position.x-12*p,trunk.position.y-14*p,trunk.size.x+24*p,17*p),Color("385d45")) + for i in range(17): + var arch := sin(float(i)/16.0*PI)*17.0 + _draw_leaf_cluster(Vector2(trunk.position.x+float(i)*trunk.size.x/16.0,trunk.position.y-4*p-arch*p-float(i%3)*3*p),p,i) + # Unbuilt space stays dark wood; completed rooms cut into it below. + for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"])+1): + for x in range(int(bounds[&"min_x"]),int(bounds[&"max_x"])+1): + var cell := Vector3i(x,y,layer_mode) + var rect := get_cell_rect(cell) + if not rect.intersects(Rect2(Vector2.ZERO,size)): + continue + var excavated := bool(world.call(&"is_excavated",cell)) if world.has_method(&"is_excavated") else true + if not excavated: + draw_rect(rect,Color("47463e")) + if posmod(x+y,3)==0: + draw_rect(Rect2(rect.position+Vector2(5,11)*p,Vector2(5,2)*p),Color("5a594b")) + elif bool(world.call(&"is_walkable",cell)): + draw_rect(rect.grow(-p), Color("aaa083") if layer_mode==LayerMode.FRONT else Color("829a8d")) + draw_rect(Rect2(rect.position.x,rect.end.y-4*p,rect.size.x,3*p),Color("b69465")) + draw_rect(Rect2(rect.position.x,rect.end.y-p,rect.size.x,p),Color("514839")) + if posmod(x,4)==0: + PixelArt.window(self,rect.position+Vector2(5,4)*p,p) + var kind := StringName(world.call(&"get_occupant_kind",cell)) + if kind==&"construction": + draw_rect(rect.grow(-p),Color("8d7951")) + _draw_hatch(rect.grow(-p),Color("e1bd6c"),rect.size.x) + if show_grid or build_definition != null or excavation_mode: + draw_rect(rect,Color("dfd4ad",0.25),false,1.0) + var label_at := Vector2(map_rect.position.x-32*p, get_cell_rect(Vector3i(0,y,layer_mode)).get_center().y) + var floor_name := "%dF" % (y+1) if y>=0 else "B%d" % -y + draw_rect(Rect2(label_at-Vector2(2,8)*p,Vector2(22,12)*p),Color("2f493f")) + draw_string(ThemeDB.fallback_font,label_at+Vector2(0,1)*p,floor_name,HORIZONTAL_ALIGNMENT_CENTER,18*p,clampi(int(7*p),10,18),Color("e4d9b9")) + # A narrow garden frames the entrance without hiding underground floors. + draw_rect(Rect2(map_rect.position.x-35*p,ground,map_rect.size.x+70*p,3*p),Color("58784c")) + var town := visual_theme.pixel_town_tiles if visual_theme != null else null + for side in [-1,1]: + var gx := map_rect.position.x-32*p if side<0 else map_rect.end.x+15*p + PixelArt.tile(self,town,4,Rect2(gx,ground-30*p,16*p,16*p)) + PixelArt.tile(self,town,16,Rect2(gx,ground-14*p,16*p,16*p)) + PixelArt.tile(self,town,2,Rect2(gx-12*p,ground-8*p,12*p,12*p)) + + +func _draw_leaf_cluster(center: Vector2, p: float, variation: int) -> void: + var c := center.round() + var shift := float(variation%3)*2.0 + draw_rect(Rect2(c-Vector2(24,5)*p,Vector2(48,12)*p),Color("385d45")) + draw_rect(Rect2(c-Vector2(20,10)*p,Vector2(40,20)*p),Color("4c7950")) + draw_rect(Rect2(c-Vector2(13+shift,15)*p,Vector2(29,25)*p),Color("4c7950")) + draw_rect(Rect2(c-Vector2(22,5)*p,Vector2(37,10)*p),Color("5e8b57")) + draw_rect(Rect2(c-Vector2(11+shift,13)*p,Vector2(23,7)*p),Color("739957")) + for i in range(9): + var offset := Vector2(posmod(i*11+variation*3,38)-19,posmod(i*7+variation,21)-11)*p + draw_rect(Rect2(c+offset,Vector2(3+i%2,2)*p),Color("95ad69") if i%3==0 else Color("648957")) + + +func _draw_life_overlay() -> void: + if _life_observation.is_empty(): + return + # These are actual child indices and the passes executed by this callback. + _life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()] + _life_observation[&"overlay_passes"] = [] + _life_observation[&"overlay_passes"].append(&"traffic") + _draw_congestion_overlay(_life_overlay) + _life_observation[&"overlay_passes"].append(&"cabins") + _draw_elevators(_life_overlay) + _draw_life_badges(_life_overlay) + _life_observation[&"overlay_passes"].append(&"actors") + for cabin: Dictionary in _life_observation.get(&"cabins", []): + for actor: Dictionary in cabin[&"passengers"]: + _draw_observed_actor(actor, _life_overlay) + _draw_actors(_life_overlay) + for badge: Dictionary in _life_observation.get(&"actor_aggregations", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay) + _life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"] + + +func _draw_congestion_overlay(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"traffic", []): + var traffic: Dictionary = row[&"traffic"] + var density := int(traffic[&"current_density"]) + var blocked := bool(traffic[&"temporary_block"]) + if density <= 0 and not blocked: + continue + var ratio := float(density) / maxi(int(traffic[&"base_capacity"]), 1) + var color := Color("e34332") if blocked or ratio > 1.0 else Color("e3a62f") if ratio >= 0.65 else Color("55c96b") + # At overview zoom a whole cell can be covered by opaque badges. Still + # submit its density band, beneath those later draws, and report no visible + # fragments. No traffic cell or raw density row is dropped. + if row[&"segment_rects"].is_empty(): + canvas.draw_rect(row[&"band_rect"], Color(color, 0.90)) + for segment: Rect2 in row[&"segment_rects"]: + canvas.draw_rect(segment, Color(color, 0.90)) + + +## Visual-only level comes from the existing live FacilityInstance, not the +## construction footprint dictionary. No upgrade or simulation value is written. +func _facility_visual_level(instance_id: StringName) -> int: + var service: Variant = _service(&"facility_service") + if service == null or not service.has_method(&"get_instance"): + return 1 + var instance: Variant = service.get_instance(instance_id) + return int(instance.level) if instance != null else 1 + + +func _completed_facilities_for_draw() -> Array: + if _drawing and _draw_cache.has(&"completed_facilities"): + return _draw_cache[&"completed_facilities"] + var construction: Variant = _service(&"construction_service") + var facilities: Array = construction.get_facilities() if construction != null else [] + if _drawing: + _draw_cache[&"completed_facilities"] = facilities + return facilities + + +func _construction_jobs_for_draw() -> Array: + if _drawing and _draw_cache.has(&"construction_jobs"): + return _draw_cache[&"construction_jobs"] + var construction: Variant = _service(&"construction_service") + var jobs: Array = construction.get_jobs() if construction != null else [] + if _drawing: + _draw_cache[&"construction_jobs"] = jobs + return jobs + + +func _other_layer_rooms() -> Array[Dictionary]: + var rooms: Array[Dictionary] = [] + for facility: Dictionary in _completed_facilities_for_draw(): + if int(facility.get(&"layer", 0)) == layer_mode: continue + var cells := _cells_from_serialized(facility.get(&"occupied_cells", [])) + if cells.is_empty(): continue + var rect := _cell_bounds_rect(cells) + if rect.intersects(Rect2(Vector2.ZERO, size)): + rooms.append({&"id": facility[&"instance_id"], &"rect": rect, &"definition_id": facility[&"definition_id"], &"origin_x": cells[0].x, + &"level": _facility_visual_level(StringName(facility[&"instance_id"]))}) + for job: ConstructionJob in _construction_jobs_for_draw(): + if job.layer == layer_mode or job.state in [ConstructionJob.State.COMPLETED, ConstructionJob.State.CANCELLED]: continue + if job.reserved_cells.is_empty(): continue + var rect := _cell_bounds_rect(job.reserved_cells) + if rect.intersects(Rect2(Vector2.ZERO, size)): + rooms.append({&"id":job.job_id, &"rect":rect, &"definition_id":job.definition_id, + &"origin_x":job.origin.x, &"building":true, &"progress":job.progress}) + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z == layer_mode: continue + var rect := _legacy_stair_rect(upper) + if rect.intersects(Rect2(Vector2.ZERO,size)): + rooms.append({&"id":stair[&"id"], &"rect":rect, &"legacy_stair":true, + &"definition_id":&"service_stairs" if upper.z==LayerMode.BACK else &"guest_stairs", &"origin_x":upper.x}) + return rooms + + +func _draw_other_layer() -> void: + # Opposite-layer footprints are visual context only. Hit testing still uses + # the selected layer's WorldService occupancy, including overlapping rooms. + var rooms := _other_layer_rooms() + if _inactive_layer_art != null: + _inactive_layer_art.visible = layer_mode == LayerMode.FRONT + _inactive_layer_art.set("rooms", rooms) + _inactive_layer_art.set("pixel_scale", _cell_dimensions().x / 16.0) + _inactive_layer_art.set("dungeon", visual_theme.pixel_dungeon_tiles if visual_theme != null else null) + _inactive_layer_art.set("town", visual_theme.pixel_town_tiles if visual_theme != null else null) + _inactive_layer_art.queue_redraw() + for room: Dictionary in rooms: + var rect: Rect2 = room[&"rect"] + if bool(room.get(&"building", false)): + var color := Color(0.95,0.76,0.35,0.45) + draw_rect(rect.grow(-2),color,false,1.0) + _draw_hatch(rect.grow(-3),Color(color,0.24),_cell_dimensions().x) + _draw_readable_badge(rect.position+Vector2(3,3),"%s 工事%d%%" % ["裏" if layer_mode==LayerMode.FRONT else "表",clampi(int(room[&"progress"])/100,0,100)]) + continue + if layer_mode == LayerMode.FRONT: + draw_rect(rect.grow(-2), Color(0.6, 0.87, 0.9, 0.45), false, 1.0) + else: + var color := Color(0.94, 0.83, 0.54, 0.55) + draw_rect(rect.grow(-2), color, false, 1.0) + draw_line(rect.position+Vector2(3,3), rect.end-Vector2(3,3), Color(color,0.2), 1.0) + + +func _draw_world_entity_icons() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_facilities"): + return + var p := _cell_dimensions().x / 16.0 + var dungeon := visual_theme.pixel_dungeon_tiles if visual_theme != null else null + var town := visual_theme.pixel_town_tiles if visual_theme != null else null + for value: Variant in _completed_facilities_for_draw(): + if not value is Dictionary or int(value.get(&"layer",0)) != layer_mode: + continue + var facility: Dictionary = value + var cells := _cells_from_serialized(facility.get(&"occupied_cells",[])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + if not rect.intersects(Rect2(Vector2.ZERO,size)): + continue + var id := StringName(facility.get(&"definition_id",&"")) + PixelArt.room(self,rect,id,p,dungeon,town,layer_mode==LayerMode.BACK,cells[0].x, _room_presentations.get(StringName(facility.get(&"instance_id", &"")), {}), _facility_visual_level(StringName(facility.get(&"instance_id", &"")))) + var instance_id := StringName(facility.get(&"instance_id",&"")) + # Thin status lamp conveys operational state without repainting a room. + draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id)) + if instance_id==selected_entity_id: + draw_rect(rect.grow(-p),Color("f5d582"),false,2*p) + + +func _readable_badge_rect(at: Vector2, text: String) -> Rect2: + # A conservative em-width box makes geometry independently reproducible. + # Text still uses the existing fallback font and size. + return Rect2(at, Vector2(text.length() * 10 + 6, 14)) + + +func _draw_readable_badge(at: Vector2, text: String, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var rect := _readable_badge_rect(at, text) + if not rect.intersects(Rect2(Vector2.ZERO, size)): + return + canvas.draw_rect(rect, Color("34413b")) + canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce")) + + +func _prepare_room_presentations() -> void: + var model := _observe_rooms() + _room_presentations = model[&"rooms"] + _carried_patients = model[&"carried_patients"] + _treated_patients = model[&"treated_patients"] + _resting_staff = model[&"resting_staff"] + + +func _observe_rooms() -> Dictionary: + var _room_presentations: Dictionary = {} + var _carried_patients: Dictionary = {} + var _treated_patients: Dictionary = {} + var _resting_staff: Dictionary = {} + var service: Variant = _service(&"facility_service") + if service == null: + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} + for instance: Variant in service.get_instances(): + if instance.origin.z == layer_mode: + _room_presentations[instance.instance_id] = RoomPresentation.describe(instance) + + var staff: Variant = _service(&"staff_service") + if staff != null: + if _runtime is Object and _runtime.has_method("get_staff_rest_report"): + for member: StaffRuntime in staff.get_staff_members(): + if member.state != StaffRuntime.State.OFF_DUTY: continue + var rest: Dictionary = _runtime.get_staff_rest_report(member.staff_id) + if not rest.get(&"resting", false): continue + _resting_staff[member.staff_id] = rest[&"facility_id"] + var room: Dictionary = _room_presentations.get(rest[&"facility_id"], {}) + if not room.is_empty(): + room[&"people"].append(member.staff_id) + room[&"count"] += 1 + for task: StaffTask in staff.get_tasks(): + if task.task_type != &"rescue_transport" or task.is_terminal() or task.assigned_staff_id.is_empty(): continue + if task.state == StaffTask.State.WORKING: + _treated_patients[task.target_entity_id] = task.rescue_facility_id + var data: Dictionary = _room_presentations.get(task.rescue_facility_id, {}) + if not data.is_empty(): + data[&"people"].append(task.target_entity_id) + data[&"count"] += 1 + else: + _carried_patients[task.target_entity_id] = task.assigned_staff_id + + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} + + +func _prepare_queues() -> void: + var model := _observe_queues() + _queue_positions = model[&"queue_positions"] + _queue_hidden = model[&"queue_hidden"] + _landing_badges.assign(model[&"landing_badges"]) + _transport_report = model[&"elevators"] + + +func _observe_queues() -> Dictionary: + var _queue_positions: Dictionary = {} + var _queue_hidden: Dictionary = {} + var _landing_badges: Array[Dictionary] = [] + var _transport_report: Dictionary = {} + var groups: Dictionary = {} + var facilities: Variant = _service(&"facility_service") + var guests: Variant = _service(&"guest_service") + if facilities != null and guests != null: + for instance: Variant in facilities.get_instances(): + for request_id: StringName in instance.queue_entries: + var request: Variant = facilities.get_request(request_id) + if request == null: + continue + for guest_id: StringName in request.guest_ids: + var guest: Variant = guests.get_guest(guest_id) + if guest != null and guest.state == GuestRuntime.State.QUEUING: + _add_queue_member(groups, "guest/"+String(guest_id), guest.current_cell) + var transport: Variant = _service(&"elevators") + if transport != null: + _transport_report = transport.get_report() + var trips: Array = _transport_report.get(&"trips", []) + trips.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a[&"enqueued_at"]) < int(b[&"enqueued_at"]) or (a[&"enqueued_at"] == b[&"enqueued_at"] and String(a[&"actor_id"]) < String(b[&"actor_id"]))) + for trip: Dictionary in trips: + if trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: + var cell: Variant = trip[&"from"] + if cell is Array: + cell = Vector3i(cell[0], cell[1], cell[2]) + _add_queue_member(groups, _elevator_actor_key(String(trip[&"actor_id"])), cell) + var lift_cells: Dictionary = {} + for shaft: Dictionary in _transport_report.get(&"shafts", []): + for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])): + for offset: int in range(maxi(int(shaft.get(&"width", 2)), 1)): + lift_cells[stop + Vector3i(offset, 0, 0)] = true + var reserved: Dictionary = {} + var cells: Array = groups.keys() + cells.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return a.y < b.y or (a.y == b.y and a.x < b.x)) + for cell: Vector3i in cells: + var members: Array = groups[cell] + var lane: Array[Vector3i] = [] + if not lift_cells.has(cell): + lane.append(cell) + var world: Variant = _service(&"world_service") + for direction: int in [-1, 1]: + for distance: int in range(1, 9): + var next := cell + Vector3i(direction*distance, 0, 0) + if world == null or not world.is_walkable(next): + break + if lift_cells.has(next): + break + var occupant: StringName = world.get_occupant(next) + var facility: Variant = facilities.get_instance(occupant) if facilities != null else null + if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: + break + lane.append(next) + var index := 0 + for target: Vector3i in lane: + if index >= members.size(): + break + if reserved.has(target): + continue + reserved[target] = true + _queue_positions[members[index]] = cell_to_canvas(target) + index += 1 + for hidden: int in range(index, members.size()): + _queue_hidden[members[hidden]] = true + var text := RoomPresentation.queue_label(members.size(), members.size()-index) + _landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text, &"cell": cell, &"members": members.duplicate(), &"visible_count": index, &"overflow": members.size()-index}) + + return {&"queue_positions": _queue_positions, &"queue_hidden": _queue_hidden, &"landing_badges": _landing_badges, &"elevators": _transport_report, &"shaft_footprint": lift_cells.keys()} + + +func _add_queue_member(groups: Dictionary, key: String, cell: Vector3i) -> void: + if cell.z != layer_mode: + return + if not groups.has(cell): + groups[cell] = [] + if not groups[cell].has(key): + groups[cell].append(key) + + +func get_legacy_stair_records() -> Array[Dictionary]: + if _drawing and _draw_cache.has(&"legacy_stair_records"): + return _draw_cache[&"legacy_stair_records"] + var result: Array[Dictionary] = [] + var service: Variant = _service(&"legacy_stairs") + if service == null or not service.has_method(&"create_snapshot"): + return result + var snapshot: Variant = service.call(&"create_snapshot") + if not snapshot is Dictionary: + return result + for row: Variant in snapshot.get(&"stairs", []): + if row is Dictionary and row.get(&"from") is Vector3i and row.get(&"to") is Vector3i: + var record: Dictionary = row.duplicate(true) + var path: Variant = _service(&"path_service") + var connector: Dictionary = path.call(&"get_connector", StringName(row[&"id"])) if path != null else {} + record[&"access_id"] = connector.get(&"access_id", &"") + result.append(record) + if _drawing: + _draw_cache[&"legacy_stair_records"] = result + return result + + +func legacy_stair_at_canvas(canvas_position: Vector2) -> StringName: + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z == layer_mode and _legacy_stair_rect(upper).has_point(canvas_position): + return StringName(stair[&"id"]) + return &"" + + +func _legacy_stair_rect(upper: Vector3i) -> Rect2: + var rect := get_cell_rect(upper) + # A small target leaves the rest of an existing room available for selection. + return Rect2(rect.position+rect.size*Vector2(0.49,0.24), rect.size*Vector2(0.5,0.7)) + + +func _draw_legacy_stairs() -> void: + for stair: Dictionary in get_legacy_stair_records(): + var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"] + if upper.z != layer_mode: + continue + var rect := _legacy_stair_rect(upper) + if not rect.intersects(Rect2(Vector2.ZERO, size)): + continue + var p := rect.size.x/14.0 + draw_rect(rect, Color("3c4e40",0.5)) + PixelArt.stairs(self,rect,p,upper.z == LayerMode.BACK,true) + if StringName(stair[&"id"]) == selected_entity_id: + draw_rect(rect,Color("f5d582"),false,maxf(p,1)) + + +func _cell_bounds_rect(cells: Array[Vector3i]) -> Rect2: + if cells.is_empty(): + return Rect2() + var result := get_cell_rect(cells[0]) + for index: int in range(1, cells.size()): + result = result.merge(get_cell_rect(cells[index])) + return result + + +func _draw_hatch(rect: Rect2, color: Color, cell_size: float) -> void: + if cell_size < 8.0: + return + var offset := -rect.size.y + while offset < rect.size.x: + var start := Vector2(rect.position.x + maxf(offset, 0.0), rect.end.y - maxf(-offset, 0.0)) + var finish := Vector2(rect.position.x + minf(offset + rect.size.y, rect.size.x), rect.end.y - minf(rect.size.y, rect.size.x - offset)) + draw_line(start, finish, color, 1.0) + offset += maxf(6.0, cell_size * 0.45) + + +func _draw_construction_labels() -> void: + var construction: Variant = _service(&"construction_service") + if construction == null or not construction.has_method(&"get_jobs"): + return + var font := ThemeDB.fallback_font + for job: Variant in _construction_jobs_for_draw(): + if job == null or int(job.layer) != layer_mode or int(job.state) in [2, 3]: + continue + var rect := get_cell_rect(job.origin) + if rect.size.x < 12.0: + continue + var percent := clampi(int(job.progress) / 100, 0, 100) + draw_string(font, rect.position + Vector2(3, -3), "%d%%" % percent, HORIZONTAL_ALIGNMENT_LEFT, -1, clampi(int(rect.size.x * 0.45), 9, 13), Color("f5d58a")) + + +func _draw_facility_labels() -> void: + var construction: Variant = _service(&"construction_service") + var registry: Variant = _service(&"definition_registry") + if construction == null or not construction.has_method(&"get_facilities"): + return + for value: Variant in _completed_facilities_for_draw(): + if not value is Dictionary or int(value.get(&"layer",0))!=layer_mode: + continue + var facility: Dictionary = value + var cells := _cells_from_serialized(facility.get(&"occupied_cells",[])) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + var id := StringName(facility.get(&"definition_id",&"")) + var hovered := _hovered_cell is Vector3i and cells.has(_hovered_cell) + var selected := StringName(facility.get(&"instance_id",&""))==selected_entity_id + if not hovered and not selected and rect.size.x<100: + continue + if id in [&"guest_corridor",&"service_corridor",&"staff_door",&"guest_stairs",&"service_stairs"] and not hovered and not selected: + continue + var definition: Variant = registry.call(&"get_facility",id) if registry != null else null + var label := String(definition.display_name) if definition != null else String(id) + var font_size := 12 + var label_width := minf(ThemeDB.fallback_font.get_string_size(label,HORIZONTAL_ALIGNMENT_LEFT,-1,font_size).x+12,rect.size.x) + var at := Vector2(rect.get_center().x-label_width*0.5,rect.position.y-17) + draw_rect(Rect2(at,Vector2(label_width,16)),Color("304b41",0.95)) + draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9")) + + +func _draw_elevators(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"cabins", []): + PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"]) + PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"]) + _draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas) + for badge: Dictionary in _life_observation.get(&"landing_badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + +func _observe_elevators(observation: Dictionary) -> Array[Dictionary]: + var cabins: Array[Dictionary] = [] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _elevator_tracks: Dictionary = observation[&"elevator_tracks"] + var report_value: Dictionary = observation[&"elevators"] + var alive: Dictionary = {} + var p := _cell_dimensions().x/16.0 + for value: Variant in report_value.get(&"shafts", []): + if not value is Dictionary: + continue + var shaft: Dictionary = value + var stops := _cells_from_serialized(shaft.get(&"stops", [])) + if stops.is_empty(): + continue + var shaft_id := StringName(shaft.get(&"shaft_id", &"")) + alive[shaft_id] = true + var passengers: Array = shaft.get(&"boarded", []) + for actor_id: Variant in passengers: + _boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true + var floor_value := float(shaft.get(&"visual_floor", shaft.get(&"current_floor", stops[0].y))) + var visual_floor := _tracked_elevator_floor(shaft_id, floor_value, observation) + if stops[0].z != layer_mode: + continue + var origin := get_cell_rect(stops[0]) + var width := maxi(int(shaft.get(&"width", 2)), 1) + var baseline := origin.end.y-float(visual_floor-stops[0].y)*_cell_dimensions().y-3*p + var cabin := Rect2(Vector2(origin.position.x+4*p,baseline-19*p).round(),Vector2(width*_cell_dimensions().x-8*p,19*p)) + if not cabin.intersects(Rect2(Vector2.ZERO,size)): + continue + var operational := bool(shaft.get(&"operational", true)) + var phase := StringName(shaft.get(&"phase", &"IDLE")) + var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"] + var passengers_drawn: Array[Dictionary] = [] + var seat_columns := maxi(int((cabin.size.x / p - 4.0) / 8.0), 1) + for index: int in range(passengers.size()): + var actor_key := _elevator_actor_key(String(passengers[index])) + var is_staff := actor_key.begins_with("staff/") + var person: Variant = null + var people: Variant = _service(&"staff_service" if is_staff else &"guest_service") + var getter := &"get_staff" if is_staff else &"get_guest" + if people != null and people.has_method(getter): + person = people.call(getter,StringName(actor_key.get_slice("/",1))) + var carrying := is_staff and person != null and not String(person.carried_inventory_id).is_empty() + var state := int(person.state) if person != null else 0 + var x := cabin.position.x + (2.0 + (index % seat_columns + 0.5)*8.0)*p + var feet := cabin.end.y - (2.0 + (int(index / seat_columns) % 2)*9.0)*p + var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5) + passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation)) + cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p, + &"operational": operational, &"doors_open": doors_open, + &"phase": phase, &"passenger_count": passengers.size(), + &"passengers": passengers_drawn, + &"badge_at": Vector2(cabin.position.x, cabin.position.y-14), + &"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)), + &"badge": RoomPresentation.elevator_label(shaft)}) + + for shaft_id: Variant in _elevator_tracks.keys(): + if not alive.has(shaft_id): + _elevator_tracks.erase(shaft_id) + + return cabins + + +func _elevator_actor_key(actor_id: String) -> String: + if actor_id.begins_with("guest:"): + return "guest/" + actor_id.trim_prefix("guest:") + if actor_id.begins_with("staff:"): + return "staff/" + actor_id.trim_prefix("staff:") + return actor_id + + +func _tracked_elevator_floor(shaft_id: StringName, current_floor: float, observation: Dictionary = {}) -> float: + var _elevator_tracks: Dictionary = observation.get(&"elevator_tracks", self._elevator_tracks) + if not _elevator_tracks.has(shaft_id): + _elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time} + var track: Dictionary = _elevator_tracks[shaft_id] + if not is_equal_approx(float(track[&"floor"]),current_floor): + track[&"from"] = float(track[&"floor"]) + track[&"floor"] = current_floor + track[&"at"] = _animation_time + var clock := get_node_or_null("/root/GameClock") + if clock != null and bool(clock.get("paused")): + return current_floor + var t := clampf((_animation_time-float(track[&"at"]))/0.42,0.0,1.0) + return lerpf(float(track[&"from"]),current_floor,t) + + +func _draw_actors(canvas: CanvasItem = null) -> void: + for row: Dictionary in _life_observation.get(&"actor_drawings", []): + _draw_observed_actor(row, canvas) + + +func _observe_actors(observation: Dictionary) -> Array[Dictionary]: + var rows: Array[Dictionary] = [] + var _actor_tracks: Dictionary = observation[&"actor_tracks"] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _queue_hidden: Dictionary = observation[&"queue_hidden"] + var _queue_positions: Dictionary = observation[&"queue_positions"] + var _carried_patients: Dictionary = observation[&"carried_patients"] + var _treated_patients: Dictionary = observation[&"treated_patients"] + var _resting_staff: Dictionary = observation[&"resting_staff"] + var counts: Dictionary = {} + var alive: Dictionary = {} + var service_slots: Dictionary = {} + var guest_service: Variant = _service(&"guest_service") + if guest_service != null and guest_service.has_method(&"get_guests"): + for guest: Variant in guest_service.call(&"get_guests"): + if guest == null: + continue + var key := "guest/"+String(guest.guest_id) + alive[key] = true + if _boarded_actor_ids.has(key) or _queue_hidden.has(key): + _actor_tracks.erase(key) + continue + var position := _tracked_actor_position(key,guest.current_cell,observation) + if guest.current_cell.z==layer_mode: + var receiving := not String(guest.current_service_id).is_empty() + if _queue_positions.has(key): + position = _queue_positions[key] + elif receiving: + position = _service_use_position(guest,position,service_slots,observation) + if not position.is_finite(): + continue + else: + position += _actor_offset(guest.current_cell,counts) + rows.append(_actor_drawing(position,key,false,int(guest.state),receiving,false,_guest_activity(guest),false,observation)) + var staff_service: Variant = _service(&"staff_service") + if staff_service != null and staff_service.has_method(&"get_staff_members"): + for staff: Variant in staff_service.call(&"get_staff_members"): + if staff == null: + continue + var key := "staff/"+String(staff.staff_id) + alive[key] = true + if _boarded_actor_ids.has(key) or _queue_hidden.has(key): + _actor_tracks.erase(key) + continue + var position := _tracked_actor_position(key,staff.current_cell,observation) + if staff.current_cell.z==layer_mode: + if _queue_positions.has(key): + rows.append(_actor_drawing(_queue_positions[key],key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) + continue + if _carried_patients.has(staff.staff_id): + var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id]) + if carrier != null: + position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0) + rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + if _resting_staff.has(staff.staff_id): + position = _resting_staff_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + if _treated_patients.has(staff.staff_id): + position = _rescue_treatment_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) + continue + rows.append(_actor_drawing(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts),observation)),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) + for key: Variant in _actor_tracks.keys(): + if not alive.has(key): + _actor_tracks.erase(key) + + return rows + + +func _staff_work_animation(staff: StaffRuntime) -> StringName: + if staff.role_id != &"cleaner": + return &"work" + # A staffed base is not a cleaning site. Animate only a live job at its + # authoritative destination, including after load and interruption. + var service: Variant = _service(&"staff_service") + var bridge: Variant = _service(&"cleaning_bridge") + var task: StaffTask = service.get_task(staff.current_task_id) if service != null else null + if staff.state == StaffRuntime.State.WORKING and task != null and task.task_type == FacilityCleaningBridge.TASK_TYPE and task.state == StaffTask.State.WORKING and bridge != null and bridge.task_can_work(task, staff): + return &"clean" + return &"idle" + + +func _staff_work_position(staff: Variant, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]: + return fallback + var service: Variant = _service(&"facility_service") + var facility: Variant = service.get_instance(staff.assigned_facility_id) if service != null else null + if facility == null: + return fallback + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(staff.assigned_facility_id) if world != null else [] + if not cells.has(staff.current_cell): + return fallback + var data: Dictionary = _room_presentations.get(staff.assigned_facility_id, {}) + var workers: Array = data.get(&"staff", []) + var slot := workers.find(staff.staff_id) + if slot < 0: + return fallback + var rect := _cell_bounds_rect(cells) + var p := _cell_dimensions().x / 16.0 + if rect.size.y < 40*p: + return fallback + var columns := maxi(int(rect.size.x / (16*p)), 1) + if slot >= columns: + return fallback + return Vector2(rect.position.x+(slot+0.5)*rect.size.x/columns, rect.position.y+26*p-_cell_dimensions().y*0.5) + + +func _tracked_actor_position(key: String, cell: Vector3i, observation: Dictionary = {}) -> Vector2: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) + if not _actor_tracks.has(key): + _actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0} + var track: Dictionary = _actor_tracks[key] + var previous: Vector3i = track[&"cell"] + if previous != cell: + track[&"from"] = previous if previous.z==cell.z and absi(previous.x-cell.x)+absi(previous.y-cell.y)==1 else cell + track[&"cell"] = cell + track[&"at"] = _animation_time + if previous.x!=cell.x: + track[&"direction"] = 1.0 if cell.x>previous.x else -1.0 + var clock := get_node_or_null("/root/GameClock") + var t := clampf((_animation_time-float(track[&"at"]))/0.42,0.0,1.0) + # Pausing displays the authoritative cell, never an ambiguous half-step. + if clock != null and bool(clock.get("paused")): + t = 1.0 + return cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(cell),t) + + +func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + # Guest paths stop at an access cell. While a service is active, project the + # occupant into that facility's use position; authoritative paths stay intact. + var id := StringName(guest.target_facility_id) + var world: Variant = _service(&"world_service") + if world==null or id.is_empty(): + return fallback + var cells := _cells_from_serialized(world.call(&"get_entity_cells",id)) + if cells.is_empty() or cells[0].z!=layer_mode: + return fallback + var rect := _cell_bounds_rect(cells) + var data: Dictionary = _room_presentations.get(id, {}) + var slot := (data.get(&"people", []) as Array).find(guest.guest_id) + if slot < 0: + return fallback + var p := _cell_dimensions().x/16.0 + var anchors := RoomPresentation.slots(rect, p, _guest_activity(guest)) + if slot >= anchors.size(): + return Vector2(INF, INF) + var at := anchors[slot] + if _guest_activity(guest) in [&"standard_room", &"premium_room"]: + at.y -= 5*p + return at + Vector2(0, 3*p-_cell_dimensions().y*0.5) + + + +func _guest_activity(guest: Variant) -> StringName: + if String(guest.target_facility_id).is_empty(): + return &"" + var service: Variant = _service(&"facility_service") + var facility: Variant = service.call(&"get_instance",guest.target_facility_id) if service != null else null + return StringName(facility.definition_id) if facility != null else &"" + + +func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false) -> void: + _draw_observed_actor(_actor_drawing(center, key, staff, state, receiving, carrying, activity, riding)) + + +func _draw_observed_actor(row: Dictionary, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var at: Vector2 = row[&"at"] + var p: float = row[&"scale"] + canvas.draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2)) + People.draw(canvas, at, p, row[&"mode"], row[&"frame"], row[&"variant"], row[&"staff"], row[&"flip"]) + + +func _actor_drawing(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false, observation: Dictionary = {}) -> Dictionary: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) + var _queue_positions: Dictionary = observation.get(&"queue_positions", self._queue_positions) + var world_pixel := _cell_dimensions().x / 16.0 + var scale := 0.5 if riding else 0.75 + var p := maxf(floorf(world_pixel * scale), 1.0) if world_pixel >= 2.0 else world_pixel * scale + var track: Dictionary = _actor_tracks.get(key, {}) + var moving: bool = not receiving and not _queue_positions.has(key) and track.get(&"cell", Vector3i.ZERO) != track.get(&"from", Vector3i.ZERO) and _animation_time - float(track.get(&"at", 0.0)) < 0.42 + var clock := get_node_or_null("/root/GameClock") + var paused := clock != null and bool(clock.get("paused")) + moving = moving and not paused and not riding + var variant := int(absi(key.hash())) % 15 + var frame := int(_animation_time * 12.0) + variant + var mode: StringName = &"idle" + if staff and state == StaffRuntime.State.INCAPACITATED: + mode = &"collapsed" + elif receiving and activity in [&"standard_room", &"premium_room"]: + mode = &"standard_room_sleep" if activity == &"standard_room" else &"sleep" + elif moving: + mode = &"carry_walk" if carrying else &"walk" + elif carrying: + mode = &"carry" + elif staff and state == StaffRuntime.State.WORKING and not riding: + mode = activity if activity in [&"clean", &"idle", &"work"] else &"work" + elif receiving and activity in [&"cafeteria", &"tavern"]: + mode = &"eat" + elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING): + mode = &"wait" + var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round() + return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode, + &"frame": frame, &"variant": variant, &"staff": staff, &"state": state, + &"receiving": receiving, &"carrying": carrying, &"activity": activity, + &"riding": riding, &"flip": float(track.get(&"direction", 1.0)) < 0.0} + + +## Identity cores are tied to the unchanged PixelPeople poses, not sprite boxes. +func _life_core(row: Dictionary) -> Rect2: + var phase := int(row[&"frame"]) % 8 + var core := Rect2(6, 1, 6, 12) + if row[&"mode"] in [&"sleep", &"collapsed", &"standard_room_sleep"]: + core = Rect2(2, 11, 13, 5) + if row[&"mode"] == &"standard_room_sleep" and phase in [2, 3, 4]: + core.position.y -= 1 + core.size.y += 1 + elif row[&"mode"] in [&"walk", &"carry_walk"] and phase in [2, 6]: + core.position.y -= 1 + if row[&"flip"]: + core.position.x = 16-core.end.x + return Rect2(row[&"at"]+core.position*row[&"scale"], core.size*row[&"scale"]) + + +func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dictionary: + var track: Dictionary = model[&"actor_tracks"].get(key, {}) + var transit: bool = not model[&"paused"] and track.get(&"from", cell) != cell and _animation_time-float(track.get(&"at", 0.0)) < 0.42 + var rect := get_cell_rect(cell) + if transit: + rect = rect.merge(get_cell_rect(track[&"from"])) + return {&"bounds": rect, &"transit": transit} + + +## Keep non-passengers on a real corridor landing when a cabin crosses their pose. +func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary: + var blocked := false + for cabin: Dictionary in model[&"cabins"]: + if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]): + blocked = true + break + if not blocked: + return region + var world: Variant = _service(&"world_service") + var facilities: Variant = _service(&"facility_service") + var footprint: Array = model[&"shaft_footprint"] + var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z) + origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + var lane: Array[Vector3i] = [] + for origin: Vector3i in origins: + for direction: int in [-1, 1]: + for distance: int in range(1, 9): + var target := origin + Vector3i(direction*distance, 0, 0) + if world == null or footprint.has(target) or not world.is_walkable(target): + break + var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null + if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: + break + if not lane.has(target): + lane.append(target) + lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + if lane.is_empty(): + var anchor := get_cell_rect(cell).position+Vector2(-80, -16) + var nearest := INF + for badge: Dictionary in model[&"landing_badges"]: + var landing: Vector3i = badge[&"cell"] + if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest: + nearest = absf(landing.x-cell.x) + anchor = badge[&"at"] + return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor} + var selected: Vector3i = lane[0] + for target: Vector3i in lane: + if not model[&"queue_positions"].values().has(cell_to_canvas(target)): + selected = target + break + return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)} + + +func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary: + var key: String = row[&"key"] + var cell: Vector3i = person[&"cell"] + var result := _life_track_region(key, cell, model) + var p := _cell_dimensions().x/16.0 + var world: Variant = _service(&"world_service") + var facility_id: StringName = &"" + if model[&"queue_positions"].has(key): + result = {&"bounds": Rect2(model[&"queue_positions"][key]-_cell_dimensions()*0.5, _cell_dimensions()), &"transit": false} + elif not row[&"staff"]: + if not String(person[&"service"]).is_empty(): + var room: Dictionary = model[&"rooms"].get(person[&"target"], {}) + if (room.get(&"people", []) as Array).has(person[&"id"]): + facility_id = person[&"target"] + else: + var id: StringName = person[&"id"] + if model[&"carried_patients"].has(id): + for carrier: Dictionary in model[&"staff"]: + if carrier[&"id"] == model[&"carried_patients"][id]: + result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model) + var rect: Rect2 = result[&"bounds"] + rect = rect.grow(p) + rect.position.y -= 12*p + result[&"bounds"] = rect + if result.has(&"center"): + result[&"center"] -= Vector2(0, 12*p) + elif model[&"resting_staff"].has(id): + facility_id = model[&"resting_staff"][id] + elif model[&"treated_patients"].has(id): + facility_id = model[&"treated_patients"][id] + elif person[&"state"] in ["WORKING", "AVAILABLE"] and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(person[&"facility"]) + var room: Dictionary = model[&"rooms"].get(person[&"facility"], {}) + var slot := (room.get(&"staff", []) as Array).find(id) + var rect := _cell_bounds_rect(cells) + var columns := maxi(int(rect.size.x/(16*p)), 1) + if cells.has(cell) and slot >= 0 and slot < columns and rect.size.y >= 40*p: + result = {&"bounds": Rect2(rect.position+Vector2(slot*rect.size.x/columns, 0), Vector2(rect.size.x/columns, rect.size.y)), &"transit": false} + if not facility_id.is_empty() and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) + if not cells.is_empty(): + result = {&"bounds": _cell_bounds_rect(cells), &"transit": false} + if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false): + result = _life_landing_region(result, cell, model) + result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p) + return result + + +func _life_scaled(row: Dictionary, scale: float) -> Dictionary: + var result := row.duplicate(true) + result[&"scale"] = scale + result[&"at"] = (row[&"center"]+Vector2(-8*scale, _cell_dimensions().y*0.5-3*_cell_dimensions().x/16.0-17*scale)).round() + return result + + +## Forward/backward packing keeps each member's own interval. No relocation. +func _life_pack(group: Array, scale: float) -> Array: + var trial: Array = [] + var right := -INF + for member: Dictionary in group: + var row := _life_scaled(member[&"row"], minf(scale, member[&"row"][&"scale"])) + var core := _life_core(row) + var bounds: Rect2 = member[&"bounds"] + var x := maxf(maxf(core.position.x, bounds.position.x), right) + row[&"at"].x += x-core.position.x + right = x+core.size.x + trial.append(row) + var left := INF + for i: int in range(trial.size()-1, -1, -1): + var row: Dictionary = trial[i] + var core := _life_core(row) + var bounds: Rect2 = group[i][&"bounds"] + var x := minf(core.position.x, minf(bounds.end.x, left)-core.size.x) + row[&"at"].x += x-core.position.x + left = x + if x < bounds.position.x or core.position.y < bounds.position.y or core.end.y > bounds.end.y: + return [] + return trial + + +## Rebuild vertical components at every fitted scale. Ceilings only decrease; +## once none decreases, the feet/cores and therefore the partition are fixed. +func _life_fitted_groups(members: Array) -> Array: + var groups: Array = [] + while true: + for member: Dictionary in members: + member[&"row"].merge(_life_scaled(member[&"row"], member[&"row"][&"scale"]), true) + members.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: + var ay := _life_core(a[&"row"]).position.y + var by := _life_core(b[&"row"]).position.y + return ay < by or (ay == by and a[&"row"][&"key"] < b[&"row"][&"key"])) + groups = [] + var bottom := -INF + for member: Dictionary in members: + var core := _life_core(member[&"row"]) + if groups.is_empty() or core.position.y >= bottom: + groups.append([]) + bottom = core.end.y + else: + bottom = maxf(bottom, core.end.y) + groups.back().append(member) + var reduced := false + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var ceiling := 1.0 + for member: Dictionary in group: + ceiling = maxf(ceiling, member[&"row"][&"scale"]) + if _life_pack(group, ceiling).is_empty() and ceiling > 1: + for member: Dictionary in group: + var scale := minf(member[&"row"][&"scale"], maxf(1, int(ceiling)-1)) + member[&"row"].merge(_life_scaled(member[&"row"], scale), true) + reduced = true + if not reduced: + return groups + return groups + + +func _fit_life_actors(model: Dictionary) -> void: + var people: Dictionary = {} + for person: Dictionary in model[&"guests"]: + people["guest/"+String(person[&"id"])] = person + for person: Dictionary in model[&"staff"]: + people["staff/"+String(person[&"id"])] = person + var members: Array = [] + var stranded: Array = [] + var all_rows: Array = model[&"actor_drawings"].duplicate() + var passenger_bounds: Dictionary = {} + for cabin: Dictionary in model[&"cabins"]: + for row: Dictionary in cabin[&"passengers"]: + all_rows.append(row) + passenger_bounds[row[&"key"]] = (cabin[&"rect"] as Rect2).grow(-_cell_dimensions().x/16.0) + for row: Dictionary in all_rows: + var person: Dictionary = people[row[&"key"]] + row[&"cell"] = person[&"cell"] + var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false} + if region.has(&"center"): + var jitter := Vector2.ZERO + if not model[&"carried_patients"].has(person[&"id"]): + var track: Dictionary = model[&"actor_tracks"][row[&"key"]] + var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1) + jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t) + row[&"center"] = region[&"center"]+jitter + row.merge(_life_scaled(row, row[&"scale"]), true) + region[&"row"] = row + if region.get(&"no_lane", false): + stranded.append(region) + elif not region[&"transit"]: + members.append(region) + var groups := _life_fitted_groups(members) + model[&"actor_aggregations"] = [] + var hidden: Dictionary = {} + for member: Dictionary in stranded: + var row: Dictionary = member[&"row"] + hidden[row[&"key"]] = true + var aggregate: Dictionary = {} + for existing: Dictionary in model[&"actor_aggregations"]: + if existing[&"at"] == member[&"badge_at"]: + aggregate = existing + break + if aggregate.is_empty(): + aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""} + model[&"actor_aggregations"].append(aggregate) + aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + aggregate[&"text"] = "+%d" % aggregate[&"members"].size() + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var base := 1.0 + for member: Dictionary in group: + base = maxf(base, member[&"row"][&"scale"]) + var fitted := _life_pack(group, base) + var retained: Array = group + if fitted.is_empty(): + retained = [] + var identities: Array = [] + var drawn: Array = [] + for member: Dictionary in group: + var row: Dictionary = member[&"row"] + identities.append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + var attempt := retained.duplicate() + attempt.append(member) + var packed := _life_pack(attempt, 1) + if not packed.is_empty(): + retained = attempt + fitted = packed + drawn.append(row[&"key"]) + else: + hidden[row[&"key"]] = true + model[&"actor_aggregations"].append({&"members": identities, &"drawn_keys": drawn, &"at": (group[0][&"bounds"] as Rect2).position, &"text": "+%d" % (group.size()-retained.size())}) + for i: int in fitted.size(): + retained[i][&"row"].merge(fitted[i], true) + model[&"actor_drawings"] = model[&"actor_drawings"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + for cabin: Dictionary in model[&"cabins"]: + cabin[&"passengers"] = cabin[&"passengers"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + + +func _draw_excavation_jobs() -> void: + var excavation: Variant = _service(&"excavation_service") + if excavation == null or not excavation.has_method(&"get_jobs"): + return + var font := ThemeDB.fallback_font + for job_value: Variant in excavation.call(&"get_jobs"): + if not job_value is Dictionary: + continue + var job: Dictionary = job_value + var state := StringName(job.get(&"state", &"")) + if state == &"COMPLETED": + continue + var position: Variant = job.get(&"position", null) + if not position is Vector2i: + continue + var cell := Vector3i(position.x, position.y, layer_mode) + var rect := get_cell_rect(cell).grow(-1.0) + if rect.size == Vector2.ZERO: + continue + var paused := state == &"PAUSED" + draw_rect(rect, Color("d69a56", 0.26) if paused else Color("4ca5c7", 0.28)) + draw_rect(rect, Color("efbd72") if paused else Color("79d3e7"), false, 2.0) + if rect.size.x >= 12.0: + var percent := clampi(int(job.get(&"progress", 0)) / 100, 0, 100) + draw_string( + font, + rect.position + Vector2(3.0, rect.size.y - 3.0), + "掘 %d%%" % percent, + HORIZONTAL_ALIGNMENT_LEFT, + -1, + clampi(int(rect.size.x * 0.38), 8, 12), + Color("e5f8fa") + ) + + +func _draw_build_preview() -> void: + if build_definition == null or not _hovered_cell is Vector3i: + return + var preview_cells := _cells_from_serialized(_last_preview_report.get(&"cells", [])) + if preview_cells.is_empty(): + preview_cells = _fallback_build_cells(_hovered_cell) + var valid := bool(_last_preview_report.get(&"can_build", false)) + var fill := Color("64d48c", 0.34) if valid else Color("e16262", 0.38) + var edge := Color("8ff0ab") if valid else Color("ff8a80") + for cell: Vector3i in preview_cells: + if cell.z != layer_mode: + continue + var rect := get_cell_rect(cell).grow(-1.0) + draw_rect(rect, fill) + draw_rect(rect, edge, false, 2.0) + + _draw_build_connection(_last_preview_report.get(&"connection", {})) + +func _draw_excavation_preview() -> void: + if not excavation_mode or not _hovered_cell is Vector3i: + return + var preview_cells := _cells_from_serialized( + _last_excavation_preview_report.get(&"cells", []) + ) + if preview_cells.is_empty(): + preview_cells = [Vector3i(_hovered_cell.x, _hovered_cell.y, layer_mode)] + var valid := bool(_last_excavation_preview_report.get(&"ok", false)) + var fill := Color("52bdd0", 0.35) if valid else Color("e16262", 0.38) + var edge := Color("8ce6ee") if valid else Color("ff8a80") + for cell: Vector3i in preview_cells: + if cell.z != layer_mode: + continue + var rect := get_cell_rect(cell).grow(-1.0) + draw_rect(rect, fill) + draw_rect(rect, edge, false, 2.0) + + +func _draw_hover() -> void: + if not _hovered_cell is Vector3i or build_definition != null or excavation_mode: + return + var rect := get_cell_rect(_hovered_cell).grow(-1.0) + draw_rect(rect, Color("d7ebef", 0.75), false, 1.5) + + +func _draw_map_caption(_map_rect: Rect2) -> void: + var font := ThemeDB.fallback_font + var caption := "世界樹の宿 / お客様のフロア" if layer_mode==LayerMode.FRONT else "世界樹の宿 / 従業員と物流" + draw_rect(Rect2(14,12,244,26),Color("29483d",0.93)) + draw_string(font,Vector2(24,30),caption,HORIZONTAL_ALIGNMENT_LEFT,-1,13,Color("f0e2bd")) + var status := "表示 %d%% · %s · G マス目%s" % [int(zoom_level*100), "裏層:半透明" if layer_mode == LayerMode.FRONT else "表層:輪郭", " ON" if show_grid else ""] + var status_width := font.get_string_size(status,HORIZONTAL_ALIGNMENT_LEFT,-1,12).x + 20.0 + draw_rect(Rect2(size.x-status_width-14,12,status_width,26),Color("29483d",0.93)) + draw_string(font,Vector2(size.x-status_width-4,30),status,HORIZONTAL_ALIGNMENT_LEFT,-1,12,Color("f0e2bd")) + + +func _draw_center_message(message: String) -> void: + var font := ThemeDB.fallback_font + draw_string(font, Vector2(0, size.y * 0.5), message, HORIZONTAL_ALIGNMENT_CENTER, size.x, 16, Color("8ca4af")) + + +func _actor_offset(cell: Vector3i, counts: Dictionary) -> Vector2: + var count := int(counts.get(cell,0)) + counts[cell] = count+1 + var p := _cell_dimensions().x/16.0 + var slots := [0.0,4.0,-4.0,2.0,-2.0,6.0,-6.0] + return Vector2(float(slots[count%slots.size()])*p,-float(count%2)*p) + + +func _facility_color(instance_id: StringName) -> Color: + var facility_service: Variant = _service(&"facility_service") + if facility_service == null or not facility_service.has_method(&"get_instance"): + return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582") + var instance: Variant = facility_service.call(&"get_instance", instance_id) + if instance == null: + return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582") + match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)): + FacilityInstance.State.BROKEN: + return Color("a24b45") + FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF: + return Color("a77d3f") + FacilityInstance.State.CLOSED: + return Color("6b6258") + FacilityInstance.State.DEGRADED: + return Color("89914d") + _: + return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e") + + +func _fallback_build_cells(origin: Vector3i) -> Array[Vector3i]: + var result: Array[Vector3i] = [] + if build_definition == null: + return result + var footprint: Variant = build_definition.get(&"footprint") if build_definition is Object else null + if build_definition is Dictionary: + footprint = build_definition.get(&"footprint", Vector2i.ONE) + if not footprint is Vector2i: + footprint = Vector2i.ONE + var dimensions: Vector2i = footprint + if build_rotation in [90, 270]: + dimensions = Vector2i(footprint.y, footprint.x) + for y_offset in range(dimensions.y): + for x_offset in range(dimensions.x): + result.append(origin + Vector3i(x_offset, y_offset, 0)) + return result + + +func _fallback_cells_are_available(origin: Vector3i) -> bool: + var world: Variant = _service(&"world_service") + if world == null: + return false + for cell: Vector3i in _fallback_build_cells(origin): + if world.has_method(&"is_in_bounds") and not bool(world.call(&"is_in_bounds", cell)): + return false + if world.has_method(&"get_occupant") and not StringName(world.call(&"get_occupant", cell)).is_empty(): + return false + return true + + +func _set_hovered_cell(value: Variant) -> void: + if value == _hovered_cell: + return + _hovered_cell = value + hovered_cell_changed.emit(_hovered_cell) + _update_build_preview() + _update_excavation_preview() + queue_redraw() + + +func _update_build_preview() -> void: + _preview_topology_key = _current_preview_topology() + _last_preview_report = get_build_preview_report() + build_preview_changed.emit(_last_preview_report.duplicate(true)) + + +func _update_excavation_preview() -> void: + _last_excavation_preview_report = ( + get_excavation_preview_report() + if excavation_mode else INVALID_EXCAVATION_PREVIEW.duplicate(true) + ) + excavation_preview_changed.emit(_last_excavation_preview_report.duplicate(true)) + + +func _on_mouse_exited() -> void: + _set_hovered_cell(null) + + +func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void: + _connection_cache_key.clear() + _connection_cache_report.clear() + queue_redraw() + _update_build_preview() + _update_excavation_preview() + + +func _connect_runtime() -> void: + if _runtime == null or not _runtime is Object or not _runtime.has_signal(&"snapshot_published"): + return + var notification: StringName = &"state_updated" if _runtime.has_signal(&"state_updated") else &"snapshot_published" + var callback := Callable(self, "_on_snapshot_published") + if not _runtime.is_connected(notification, callback): + _runtime.connect(notification, callback) + + +func _disconnect_runtime() -> void: + if _runtime == null or not _runtime is Object or not _runtime.has_signal(&"snapshot_published"): + return + var notification: StringName = &"state_updated" if _runtime.has_signal(&"state_updated") else &"snapshot_published" + var callback := Callable(self, "_on_snapshot_published") + if _runtime.is_connected(notification, callback): + _runtime.disconnect(notification, callback) + + +func _service(property_name: StringName) -> Variant: + if _runtime == null or not _runtime is Object: + return null + if _drawing and _draw_cache.has(property_name): + return _draw_cache[property_name] + for property: Dictionary in _runtime.get_property_list(): + if StringName(property.get(&"name", &"")) == property_name: + var value = _runtime.get(property_name) + if _drawing: + _draw_cache[property_name] = value + return value + return null + + +func _world_bounds() -> Dictionary: + if _drawing and _draw_cache.has(&"bounds"): + return _draw_cache[&"bounds"] + var world: Variant = _service(&"world_service") + if world == null or not world.has_method(&"get_bounds"): + return {} + var result: Variant = world.call(&"get_bounds") + if not result is Dictionary: + return {} + for key: StringName in [&"min_x", &"max_x", &"min_y", &"max_y"]: + if not result.has(key) and not result.has(String(key)): + return {} + var result_value = { + &"min_x": int(result.get(&"min_x", result.get("min_x", 0))), + &"max_x": int(result.get(&"max_x", result.get("max_x", -1))), + &"min_y": int(result.get(&"min_y", result.get("min_y", 0))), + &"max_y": int(result.get(&"max_y", result.get("max_y", -1))), + } + + if _drawing: + _draw_cache[&"bounds"] = result_value + return result_value + + +func _cells_from_serialized(value: Variant) -> Array[Vector3i]: + var result: Array[Vector3i] = [] + if not value is Array: + return result + for raw_cell: Variant in value: + if raw_cell is Vector3i: + result.append(raw_cell) + elif raw_cell is Array and raw_cell.size() == 3: + result.append(Vector3i(int(raw_cell[0]), int(raw_cell[1]), int(raw_cell[2]))) + return result + + +func _serialize_cells(cells: Array[Vector3i]) -> Array[Array]: + var result: Array[Array] = [] + for cell: Vector3i in cells: + result.append([cell.x, cell.y, cell.z]) + return result + + +func _normalized_rotation(value: int) -> int: + return posmod(value, 360) / 90 * 90 + + +func _rescue_treatment_position(patient_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _treated_patients: Dictionary = observation.get(&"treated_patients", self._treated_patients) + var facility_id: StringName = _treated_patients[patient_id] + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] + if cells.is_empty(): return fallback + var data: Dictionary = _room_presentations.get(facility_id, {}) + var slot := (data.get(&"people", []) as Array).find(patient_id) + var p := _cell_dimensions().x/16.0 + var anchors := RoomPresentation.slots(_cell_bounds_rect(cells), p, &"infirmary") + if slot < 0 or slot >= anchors.size(): return Vector2(INF,INF) + return anchors[slot]+Vector2(0,-2*p-_cell_dimensions().y*0.5) + + +func _resting_staff_position(staff_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _resting_staff: Dictionary = observation.get(&"resting_staff", self._resting_staff) + var facility_id: StringName = _resting_staff[staff_id] + var world: Variant = _service(&"world_service") + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] + if cells.is_empty(): return fallback + var data: Dictionary = _room_presentations.get(facility_id, {}) + var slot := (data.get(&"people", []) as Array).find(staff_id) + var anchors := RoomPresentation.slots(_cell_bounds_rect(cells), _cell_dimensions().x/16.0, &"staff_waiting_room") + if slot < 0 or slot >= anchors.size(): return Vector2(INF,INF) + return anchors[slot]+Vector2(0,-_cell_dimensions().y*0.5) + + +func _can_drop_data(_position: Vector2, data: Variant) -> bool: + return data is Dictionary and data.get(&"kind", &"") == &"staff_assignment" and data.get(&"staff_id", &"") is StringName + + +func _drop_data(position: Vector2, data: Variant) -> void: + if _can_drop_data(position, data): + staff_assignment_dropped.emit(data, facility_at_canvas(position)) + + +func _current_preview_topology() -> Array: + var world: Variant = _service(&"world_service") + var path: Variant = _service(&"path_service") + return [world.revision if world != null else -1, + path.graph_revision if path != null else -1, + path.traffic_revision if path != null else -1] + + +## Read-only connection preview. Shares ordinary facility access candidates and +## accessible graph edges, but does not issue live path queries or change routing. +## The displayed path proves connectivity; it is not a cheapest-route promise. +func _build_connection_report(cells: Array[Vector3i], origin: Vector3i) -> Dictionary: + var path: Variant = _service(&"path_service") + if path == null or _runtime == null or cells.is_empty(): + return {&"status": &"UNAVAILABLE", &"reachable": false, &"path": []} + var entrance: Variant = _runtime.call(&"_operations_entrance_cell") + var key: Array = [origin, cells, _current_preview_topology(), entrance] + if key == _connection_cache_key: + return _connection_cache_report.duplicate(true) + var result := {&"status": &"UNAVAILABLE", &"reachable": false, + &"entrance": entrance, &"origin": origin, &"path": [], &"access_points": []} + if entrance is Vector3i: + var future := FacilityInstance.new() + future.origin = origin + future.occupied_cells.assign(cells) + var candidates: Array = _runtime.call(&"_facility_access_candidates", future) + result[&"access_points"] = candidates + var targets: Dictionary = {} + for cell: Vector3i in candidates: targets[cell] = true + var queue: Array[Vector3i] = [entrance] + var parents: Dictionary = {} + parents[entrance] = entrance + var cursor := 0 + var target: Variant = null + while cursor < queue.size(): + var current := queue[cursor] + cursor += 1 + if targets.has(current): + target = current + break + for edge: Dictionary in path.get_neighbors(current): + var next: Vector3i = edge[&"to"] + if parents.has(next) or path.get_edge_report(current, next).is_empty(): + continue + parents[next] = current + queue.append(next) + var route: Array[Vector3i] = [] + if target is Vector3i: + var current: Vector3i = target + while current != entrance: + route.push_front(current) + current = parents[current] + route.push_front(entrance) + result[&"reachable"] = not route.is_empty() + result[&"status"] = &"CONNECTED" if result[&"reachable"] else &"DISCONNECTED" + result[&"path"] = route + _connection_cache_key = key.duplicate(true) + _connection_cache_report = result.duplicate(true) + return result + + +func _draw_build_connection(report: Dictionary) -> void: + if report.is_empty() or not _hovered_cell is Vector3i: return + var status: StringName = report.get(&"status", &"UNAVAILABLE") + var color := Color("8ff0ab") if status == &"CONNECTED" else Color("ffd080") + var target := cell_to_canvas(_hovered_cell) + var route: Array = report.get(&"path", []) + if status == &"CONNECTED": + for i in range(1, route.size()): + var from: Vector3i = route[i-1] + var to: Vector3i = route[i] + if from.z == layer_mode and to.z == layer_mode: + draw_line(cell_to_canvas(from), cell_to_canvas(to), color, 2.0) + if not route.is_empty() and (route.back() as Vector3i).z == layer_mode: + draw_line(cell_to_canvas(route.back()), target, color, 2.0) + elif report.get(&"entrance") is Vector3i: + # Dashed straight guide is a warning, never a traversable-path claim. + draw_dashed_line(cell_to_canvas(report[&"entrance"]), target, color, 2.0, 8.0) + draw_line(target-Vector2(6,6),target+Vector2(6,6),color,2.0) + draw_line(target+Vector2(-6,6),target+Vector2(6,-6),color,2.0) + var message := "入口への経路接続済" if status == &"CONNECTED" else "入口への経路未接続" if status == &"DISCONNECTED" else "入口・経路を確認できません" + draw_string(ThemeDB.fallback_font, target+Vector2(8,-12), message, + HORIZONTAL_ALIGNMENT_LEFT, -1, 13, color) + +func set_demolition_mode(enabled: bool) -> void: + demolition_mode = enabled + if enabled: + clear_build_definition() + set_excavation_mode(false) + queue_redraw() diff --git a/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json new file mode 100644 index 0000000..ad194ae --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p24-02/root-current-main-reference-20261017/main-source/tools/regression/suite-policy.json @@ -0,0 +1,466 @@ +{ + "schema_version": 1, + "suite_policies": { + "tests/s4/test_original_relationship_model.gd": { + "source_sha256": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/s4/test_original_relationship_runtime.gd": { + "source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_departure.gd": { + "source_sha256": "3137f36f547514f25ce6ac775fa7a5141d12f83ca4b95da0be45fecfafe9d9b4", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": ["single"], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_anri_recruitment.gd": { + "source_sha256": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_event_log_ui.gd": { + "source_sha256": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_amelia_donations.gd": { + "source_sha256": "1bd622a3d88374f01e3d8885a84a816572ed296928f6ce7e3f8134ab15f60793", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_fine_cleaning_effect.gd": { + "source_sha256": "7c7df0bac1c072ed4423140e8afe431952ec2a47f76df7dbc24cab62283cae06", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_normal_auto_purchase_chain.gd": { + "source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_normal_supply_chain.gd": { + "source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_approved_free_unique_arrears_ui.gd": { + "source_sha256": "ff684e2f227896c1b5df6026f130d45f4289fc6cae40989be51ddc95b2cf135b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_business_management.gd": { + "source_sha256": "2c76697f9a911e9dded63ebbbc2b82ee9e616777c50a637eebd91dc4932f85a5", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_chapel_gameplay.gd": { + "source_sha256": "e1996d6bd6caf46bae7becf1de2514e91a6168a686e83563b00803ac71109735", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_cleaning_gameplay.gd": { + "source_sha256": "38593cd17829611dd34561ec46a0d746492a6cffebc35a659d49fe69de4234ac", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_dashboard_coalescing.gd": { + "source_sha256": "2f4bd8e9d43911067a7e82864454f6779ae92acc48e0b8d4932e5d963e0430ec", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_defense_management.gd": { + "source_sha256": "e7debf5746e1ad5976b1afb1d2471c1c2317bcc406ed2716a2368bd4e06e0235", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_dimension_migration_ui.gd": { + "source_sha256": "0f0cec88ea8bf5fccdcd5ade02fab70b7dc71135132048cd10db17ad9d964c0d", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_elevator_management.gd": { + "source_sha256": "dff2d3f2a1cea84224aee8f70c4ad53d731df173bc7d16d7ec012c0d16c62e11", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_first_operating_day.gd": { + "source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_guest_decision_log.gd": { + "source_sha256": "8532defab87c075ad3196b44af030cdab785abed365e25d5a56d2d42f115cf71", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_guest_observation.gd": { + "source_sha256": "8f5af3d6ef367512b34d0cf35fd8c1d2848d020419dddfa234120206dbe4f6e4", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_layer_skin.gd": { + "source_sha256": "aab6f0561e222f88681169a82b47631320495df4c359bcf79d2fb228c60ad83b", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "front-owner", + "back-owner" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_main_audio_feedback.gd": { + "source_sha256": "9bc9e13ef3498941b6e8566c0b7666c42e294b63e18ec0da2cfa1cda3e4617bd", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_main_dashboard.gd": { + "source_sha256": "fa9ee0c4b9aee00a2be6d05a9dab7c0c3e8b0beb2b944257de1dc308bfc3cc20", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_original_build_pointer.gd": { + "source_sha256": "a04a79c5e766659d10c4564d1f11c6554a27cb8af71431295e10bc4967012913", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_original_facility_controls.gd": { + "source_sha256": "294b39808d3454f96555b981c8f822f17e1248afa803f7c0da74c35799ae55a1", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_rank_pointer.gd": { + "source_sha256": "2c51510f80fb57068c6c06aa56566936685e679720b0fd853d8f814566e730f2", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_autosave.gd": { + "source_sha256": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_selection_lifecycle.gd": { + "source_sha256": "ed68b120f422e1b37b8cf0654b6d6605458fe01c7e5d84decc3ba6f4e9366c47", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "transition", + "finish" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_management.gd": { + "source_sha256": "7e37dc47583325a564ef48de31596adeeccfb7151ac5fce952efc6873c8ab704", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "restored-transition", + "finish" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_rescue.gd": { + "source_sha256": "928179383bda38adf9211065b644a37bed3f8128be42b7e7a0fba9a505a40b9e", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_staff_rest.gd": { + "source_sha256": "712f2c30ec89da20835e54f2e83b22a358cc6fc05555147ce635ec2592ef75b5", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_stair_transport_details.gd": { + "source_sha256": "3abd3655eecbefe1b7cd49e1b16c42f8161c86f20380c298ec6367b42d31555f", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_supply_auto_purchase_ui.gd": { + "source_sha256": "ad3788ae4b3f17918be67cf06c4162635da1ecba1ee0a0465d10acc019f3e3ae", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_supply_management.gd": { + "source_sha256": "ee96be8b31eb71e84ab813e427a28bcfb76de4a057bd964584a6a497382ea716", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": true, + "new_audio_QA": false + }, + "tests/ui/test_ui_audio_feedback.gd": { + "source_sha256": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": true + }, + "tests/ui/test_ui_presentation_audio_contract.gd": { + "source_sha256": "9769b89b61630ddf2ef13a927f329ddaa30e04da77987e7a868d0490244d6a25", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "before-remount", + "final" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_upkeep_closed_ui.gd": { + "source_sha256": "915db12e539d98bfcb2cfc3712693ca7fca3ff459aaacba419faff04ac712868", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_upkeep_inspector.gd": { + "source_sha256": "99895a24bcddc4c2e01d8960a2fd7eba05be237a64700d78879c68c1a0660f34", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_world_tree_rank.gd": { + "source_sha256": "8e108e3ae63d1ee3971c7b236f50a673c407835f094de0d38a736c38261767aa", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_runtime_upkeep.gd": { + "source_sha256": "eae48823d58d78c419d29728e33a5559fbe5131b49e33d04ca4e6fa3a721c29b", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/transport/test_runtime_transport_comparison.gd": { + "source_sha256": "ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/transport/test_transport_comparison.gd": { + "source_sha256": "7e80e9ef80500278c56f0d1ada5c911f43e3c0a9bc370045fdbebd33c2f02fd5", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/supply/test_mimi_purchase_quote.gd": { + "source_sha256": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false, + "standalone_ownership": { + "Main_instantiated": false, + "configure_audio_called": false, + "UIAudio_owner_registered": false, + "actual_parent": "SceneTree.root", + "owned_supply_panel": "configure(runtime) then add_child; queue_free + await process_frame + is_instance_valid rejection", + "autoload_Runtime_Economy_lifetime": "native strict diagnostics and whole profile capture remain mandatory; leak outcome unmeasured", + "normal_human_viewport_or_OS": false + }, + "source_only_ownership_review": true + }, + "tests/transport/test_runtime_transport.gd": { + "source_sha256": "ec6f8fa8e8f2a76a6f6214b4ee84861aba1bb13845a342c5126905b01f428a15", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_actor_selection.gd": { + "source_sha256": "f37a0f4805adce4b538fccde1404c1a417f20639fddf36947f076d88409ba0a3", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "before-remount", + "final" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/mvp/test_pending_service_pricing_report.gd": { + "source_sha256": "50095c41fe76bac7bd9346ce97651d40610fa69b48c3b24f7acc5b371578e753", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_p08_normal_placement_ops.gd": { + "source_sha256": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + } + }, + "outside_routes": { + "tests/supply/test_normal_auto_purchase_chain.gd": { + "env": "THE_TREE_SUPPLY_EVIDENCE_DIR", + "filename": "normal-play-report.json", + "stdout_prefix": "NORMAL_AUTO_PURCHASE_REPORT ", + "stdout_projection": "subset", + "report_owner_source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256" + }, + "tests/supply/test_normal_supply_chain.gd": { + "env": "THE_TREE_SUPPLY_EVIDENCE_DIR", + "filename": "normal-play-report.json", + "stdout_prefix": "NORMAL_SUPPLY_REPORT ", + "stdout_projection": "subset", + "report_owner_source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d" + }, + "tests/ui/test_first_operating_day.gd": { + "env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR", + "filename": "operating-report.json", + "stdout_prefix": "FIRST_OPERATING_DAY_REPORT ", + "stdout_projection": "full", + "report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226" + }, + "tests/ui/test_selection_autosave.gd": { + "env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR", + "filename": "operating-report.json", + "stdout_prefix": "FIRST_OPERATING_DAY_REPORT ", + "stdout_projection": "full", + "inherited_source": "tests/ui/test_first_operating_day.gd", + "report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226" + } + }, + "fixed_generated_report_routes": { + "tests/mvp/test_runtime_upkeep.gd": [ + "artifacts/upkeep-2026-09-29/preclose-snapshot.txt", + "artifacts/upkeep-2026-09-29/closed-snapshot.txt" + ], + "tests/transport/test_runtime_transport_comparison.gd": [ + "artifacts/transport/runtime-comparison.json", + "artifacts/transport/runtime-comparison-1-trace.json", + "artifacts/transport/runtime-comparison-2-trace.json" + ], + "tests/transport/test_transport_comparison.gd": [ + "artifacts/transport/path-comparison.json", + "artifacts/transport/comparison-demand.json" + ] + }, + "scope": "observed source/proof/IO contracts; no new gameplay assertions" +} diff --git a/scenes/main/main.gd b/scenes/main/main.gd index 2adc769..a3bba86 100644 --- a/scenes/main/main.gd +++ b/scenes/main/main.gd @@ -152,6 +152,11 @@ var important_warning_cutin: OperationFeedback.ImportantWarningCutinDialog var front_button: Button var back_button: Button var excavation_button: Button +var demolition_button: Button +var demolition_dialog: ConfirmationDialog +var _demolition_mode := false +var _demolition_target: StringName = &"" +var _pending_demolition_commands: Dictionary = {} var pan_button: Button var pause_button: Button var speed_buttons: Dictionary = {} @@ -812,12 +817,20 @@ func _create_bottom_bar() -> Control: palette_scroll.custom_minimum_size.y = 62 palette_scroll.visible = false column.add_child(palette_scroll) + var palette_row := HBoxContainer.new() + palette_row.name = "BuildPaletteRow" + palette_row.add_theme_constant_override("separation", 4) + palette_scroll.add_child(palette_row) var grid := GridContainer.new() grid.name = "BuildPalette" grid.columns = MVP_FACILITY_IDS.size() grid.add_theme_constant_override("h_separation", 4) grid.add_theme_constant_override("v_separation", 0) - palette_scroll.add_child(grid) + palette_row.add_child(grid) + demolition_button = _button("撤去", _toggle_demolition_mode, 72) + demolition_button.name = "DemolitionButton" + demolition_button.toggle_mode = true + palette_row.add_child(demolition_button) build_status_label = Label.new() build_status_label.name = "BuildStatusLabel" @@ -1267,6 +1280,7 @@ func _select_build_definition(definition_id: StringName) -> void: if definition == null: _set_status("選択した施設定義が見つかりません。", false) return + _set_demolition_mode(false, false) _set_excavation_mode(false, false) _set_pan_mode(false) _selected_definition_id = definition_id @@ -1293,6 +1307,9 @@ func _clear_build_selection(announce := true) -> void: func _clear_active_tool() -> void: + if _demolition_mode: + _set_demolition_mode(false) + return if world_preview.pan_mode: _set_pan_mode(false) _set_status("画面移動を解除しました。施設をクリックすると情報を表示します。", true, true) @@ -1325,6 +1342,7 @@ func _toggle_pan_mode() -> void: func _set_pan_mode(enabled: bool) -> void: if enabled: + _set_demolition_mode(false, false) _clear_build_selection(false) _set_excavation_mode(false, false) _set_build_palette_open(false) @@ -1335,6 +1353,7 @@ func _set_pan_mode(enabled: bool) -> void: func _set_excavation_mode(enabled: bool, announce := true) -> void: if enabled: + _set_demolition_mode(false, false) _set_pan_mode(false) _excavation_mode = enabled if excavation_button != null: @@ -1380,6 +1399,9 @@ func _rotate_build_preview() -> void: func _on_map_cell_clicked(cell: Vector3i) -> void: if _audio_feedback != null: _audio_feedback.request_click() + if _demolition_mode: + _preview_demolition(cell) + return if _excavation_mode: _start_excavation(cell) return @@ -2128,7 +2150,19 @@ func _preview_failure_message(report: Dictionary) -> String: var codes_value: Variant = report.get(&"error_codes", []) if codes_value is Array and not codes_value.is_empty(): var code := String(codes_value[0]) - return "配置不可: %s" % PREVIEW_REASON_NAMES.get(code, code) + if PREVIEW_REASON_NAMES.has(code): + return "配置不可: %s" % PREVIEW_REASON_NAMES[code] + var code_reasons: Variant = report.get(&"reasons", []) + if code_reasons is Array and not code_reasons.is_empty(): + var reason: Variant = code_reasons[0] + for candidate: Variant in code_reasons: + if candidate is Dictionary and String(candidate.get(&"code", "")) == code: + reason = candidate + break + if reason is Dictionary: + var message := String(reason.get(&"message", "")) + if not message.is_empty(): + return "配置不可: %s" % message var reasons_value: Variant = report.get(&"reasons", []) if reasons_value is Array and not reasons_value.is_empty() and reasons_value[0] is Dictionary: return "配置不可: %s" % String(reasons_value[0].get(&"message", "条件を満たしていません")) @@ -2358,6 +2392,8 @@ func _apply_loaded_interface(result: Dictionary) -> void: if _audio_feedback != null: _audio_feedback.begin_loaded_epoch(_current_audio_foreground()) _pending_build_commands.clear() + _pending_demolition_commands.clear() + _set_demolition_mode(false, false) _pending_excavation_commands.clear() _pending_hire_commands.clear() _pending_elevator_commands.clear() @@ -2598,6 +2634,10 @@ func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> func _on_command_completed(result: Variant) -> void: if result == null: return + if _pending_demolition_commands.has(int(result.command_id)): + _pending_demolition_commands.erase(int(result.command_id)) + _show_demolition_result(result) + return if _pending_related_commands.has(int(result.command_id)): _pending_related_commands.erase(int(result.command_id)) _set_status("関連施設の設定を反映しました。" if result.is_success() else result.message, result.is_success()) @@ -2781,17 +2821,17 @@ func _phase_name(phase: GameClock.Phase) -> String: return "夜" +const RESOURCE_TITLES := {&"gold": "所持金", &"mana": "マナ", &"materials": "資材", &"faith": "信仰"} + +func _resource_title(resource_name: StringName) -> String: + return String(RESOURCE_TITLES.get(resource_name, String(resource_name))) + + func _on_resource_changed(resource_name: StringName, value: int) -> void: if not resource_labels.has(resource_name): return - var titles := { - &"gold": "所持金", - &"mana": "マナ", - &"materials": "資材", - &"faith": "信仰", - } var label := resource_labels[resource_name] as Label - label.text = "%s %d" % [titles[resource_name], value] + label.text = "%s %d" % [_resource_title(resource_name), value] if resource_name == &"mana": label.mouse_filter = Control.MOUSE_FILTER_STOP label.tooltip_text = "残量0で全館停止。接客・調理・搬入・エレベーターと新規入館が停止します。\n残量と配電が回復すると再開します。破壊・摘発による休止は解除されません。" @@ -3114,6 +3154,83 @@ func _submit_ui_command( return _audio_feedback.submit_command(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key) +func _toggle_demolition_mode() -> void: + _set_demolition_mode(not _demolition_mode) + + +func _set_demolition_mode(enabled: bool, announce := true) -> void: + if enabled: + _set_pan_mode(false) + _set_excavation_mode(false, false) + _clear_build_selection(false) + _demolition_mode = enabled + _demolition_target = &"" + if demolition_dialog != null: + demolition_dialog.hide() + if demolition_button != null: + demolition_button.set_pressed_no_signal(enabled) + if world_preview != null: + world_preview.set_demolition_mode(enabled) + if announce: + _set_status("撤去する施設のマスを選択してください。確認画面で取り消せます。" if enabled else "撤去モードを解除しました。", true, true) + + +func _preview_demolition(cell: Vector3i) -> void: + # Selection only; Runtime owns eligibility, occupancy and refunds. + _demolition_target = world_preview.facility_at_canvas(world_preview.cell_to_canvas(cell)) + world_preview.selected_entity_id = _demolition_target + if demolition_dialog == null: + demolition_dialog = ConfirmationDialog.new() + demolition_dialog.name = "DemolitionDialog" + demolition_dialog.title = "施設の撤去" + demolition_dialog.ok_button_text = "撤去する" + demolition_dialog.cancel_button_text = "戻る" + demolition_dialog.confirmed.connect(_confirm_demolition) + demolition_dialog.canceled.connect(_cancel_demolition) + add_child(demolition_dialog) + _apply_button_style(demolition_dialog.get_ok_button()) + _apply_button_style(demolition_dialog.get_cancel_button()) + demolition_dialog.dialog_text = "座標 (%d, %d / %d)\n施設: %s\n\n撤去を指示しますか?" % [cell.x, cell.y, cell.z, String(_demolition_target)] + demolition_dialog.popup_centered(Vector2i(420, 210)) + + +func _cancel_demolition() -> void: + _demolition_target = &"" + _set_status("撤去を取り消しました。", true, true) + + +func _confirm_demolition() -> void: + if not _demolition_mode: + return + if _runtime == null or not _runtime.has_method(&"submit_command"): + _set_status("Simulation Runtimeへ接続できません。", false) + return + var target := _demolition_target + _demolition_target = &"" + var result: CommandResult = _runtime.submit_command(&"demolish_facility", {&"instance_id": target}) + # Paused play returns execution synchronously; running play returns acceptance. + if result.is_success() and _runtime.command_queue.get_processed_result(result.command_id) == null: + _pending_demolition_commands[result.command_id] = target + _set_status("撤去を受け付けました", true) + return + _show_demolition_result(result) + + +func _show_demolition_result(result: CommandResult) -> void: + if result.is_success(): + var refund: Dictionary = result.details.get(&"refund", {}) + var keys: Array = refund.keys() + keys.sort() + var amounts: PackedStringArray = [] + for key: Variant in keys: + if int(refund[key]) != 0: + amounts.append("%s %d" % [_resource_title(StringName(key)), int(refund[key])]) + _set_status("撤去しました(返金: %s)" % "、".join(amounts) if not amounts.is_empty() else "撤去しました(返金なし)", true) + else: + _set_status("撤去できません: " + result.message, false) + _refresh_selected_entity() + world_preview.queue_redraw() + func assign_staff_by_click(facility_id: StringName, staff_id: StringName = &"") -> void: if staff_management_panel == null: diff --git a/scenes/main/world_preview.gd b/scenes/main/world_preview.gd index 5ebf3bd..26dd8d7 100644 --- a/scenes/main/world_preview.gd +++ b/scenes/main/world_preview.gd @@ -53,6 +53,7 @@ var selected_entity_id: StringName = &"": var build_definition: Variant = null var build_rotation := 0 var excavation_mode := false +var demolition_mode := false var visual_theme: VisualThemeProfile = null var zoom_level := 1.0 @@ -84,6 +85,9 @@ var _queue_positions: Dictionary = {} var _queue_hidden: Dictionary = {} var _landing_badges: Array[Dictionary] = [] var _transport_report: Dictionary = {} +var _life_observation: Dictionary = {} +var _life_overlay: Node2D +var _life_observation_serial := 0 var _dragging := false var _drag_button := MOUSE_BUTTON_NONE @@ -109,6 +113,10 @@ func _ready() -> void: _inactive_layer_art = InactiveLayerRooms.new() _inactive_layer_art.name = "InactiveLayerRooms" add_child(_inactive_layer_art) + _life_overlay = Node2D.new() + _life_overlay.name = "LifeOverlay" + _life_overlay.draw.connect(_draw_life_overlay) + add_child(_life_overlay) _layer_lens = ColorRect.new() _layer_lens.name = "LayerLens" _layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -141,6 +149,8 @@ func set_visual_theme(profile: VisualThemeProfile) -> void: func set_build_definition(definition: Variant, rotation := 0) -> void: + if definition != null: + demolition_mode = false if definition != null and excavation_mode: set_excavation_mode(false) build_definition = definition @@ -164,6 +174,8 @@ func set_build_rotation(value: int) -> void: func set_excavation_mode(enabled: bool) -> void: + if enabled: + demolition_mode = false if excavation_mode == enabled: _update_excavation_preview() return @@ -204,6 +216,12 @@ func get_build_preview_report(cell: Variant = null) -> Dictionary: var target: Variant = _hovered_cell if cell == null else cell if build_definition == null or not target is Vector3i: return INVALID_PREVIEW.duplicate(true) + if _runtime != null and _runtime.has_method(&"preview_build_facility"): + var result: Dictionary = _runtime.call(&"preview_build_facility", build_definition.id, Vector3i(target.x, target.y, layer_mode), build_rotation) + result[&"connection"] = _build_connection_report( + _cells_from_serialized(result.get(&"cells", [])), + Vector3i(target.x, target.y, layer_mode)) + return result var construction: Variant = _service(&"construction_service") if construction != null and construction.has_method(&"validate_placement"): var report: Variant = construction.call( @@ -471,7 +489,7 @@ func _gui_input(event: InputEvent) -> void: var cell: Variant = canvas_to_cell(event.position) if not cell is Vector3i: return - if build_definition != null or excavation_mode: + if build_definition != null or excavation_mode or demolition_mode: cell_clicked.emit(cell) accept_event() return @@ -510,6 +528,9 @@ func _draw() -> void: func _draw_contents() -> void: + _life_observation = {} + if _life_overlay != null: + _life_overlay.queue_redraw() if _inactive_layer_art != null: _inactive_layer_art.visible = false _draw_background() @@ -520,18 +541,27 @@ func _draw_contents() -> void: var map_rect := get_map_rect() if map_rect.size == Vector2.ZERO: return + _life_observation = _build_life_observation() + _life_observation_serial += 1 + _life_observation[&"observation_serial"] = _life_observation_serial + _life_observation[&"overlay_consumed_serial"] = -1 + _room_presentations = _life_observation[&"rooms"] + _carried_patients = _life_observation[&"carried_patients"] + _treated_patients = _life_observation[&"treated_patients"] + _resting_staff = _life_observation[&"resting_staff"] + _queue_positions = _life_observation[&"queue_positions"] + _queue_hidden = _life_observation[&"queue_hidden"] + _landing_badges.assign(_life_observation[&"landing_badges"]) + _transport_report = _life_observation[&"elevators"] + _boarded_actor_ids = _life_observation[&"boarded"] + _actor_tracks = _life_observation[&"actor_tracks"] + _elevator_tracks = _life_observation[&"elevator_tracks"] _draw_world_cells(bounds, map_rect) - if show_grid: - _draw_congestion_overlay(bounds) - _prepare_room_presentations() - _prepare_queues() _draw_world_entity_icons() _draw_legacy_stairs() - _draw_elevators() _draw_other_layer() _draw_construction_labels() _draw_facility_labels() - _draw_actors() _draw_excavation_jobs() _draw_build_preview() _draw_excavation_preview() @@ -539,6 +569,177 @@ func _draw_contents() -> void: _draw_map_caption(map_rect) +## The report describes the last submitted draw, not an independently sampled world. +## Callers cannot mutate either the frame or its interpolation tracks. +func get_life_observation_report() -> Dictionary: + return _life_observation.duplicate(true) + + +## Pure with respect to Runtime and this Control: helpers mutate only fresh local +## dictionaries (including copies of visual tracks). No whole Runtime snapshot +## is copied per frame; whole typed snapshots belong to sparse diagnostics. +func _build_life_observation() -> Dictionary: + var model := _observe_rooms() + model.merge(_observe_queues()) + model[&"layer"] = layer_mode + model[&"zoom"] = zoom_level + model[&"pan"] = pan_offset + model[&"viewport"] = size + model[&"animation_time"] = _animation_time + model[&"actor_tracks"] = _actor_tracks.duplicate(true) + model[&"elevator_tracks"] = _elevator_tracks.duplicate(true) + model[&"boarded"] = {} + var clock := get_node_or_null("/root/GameClock") + model[&"paused"] = clock != null and bool(clock.get("paused")) + model[&"cabins"] = _observe_elevators(model) + model[&"actor_drawings"] = _observe_actors(model) + var guests: Array[Dictionary] = [] + var staff: Array[Dictionary] = [] + var tasks: Array[Dictionary] = [] + var guest_service: Variant = _service(&"guest_service") + if guest_service != null and guest_service.has_method(&"get_guests"): + for guest: GuestRuntime in guest_service.get_guests(): + guests.append({&"id": guest.guest_id, &"state": GuestRuntime.State.keys()[guest.state], + &"cell": guest.current_cell, &"target": guest.target_facility_id, + &"service": guest.current_service_id, &"queue": guest.queue_request_id}) + var staff_service: Variant = _service(&"staff_service") + if staff_service != null: + for member: StaffRuntime in staff_service.get_staff_members(): + staff.append({&"id": member.staff_id, &"state": StaffRuntime.State.keys()[member.state], + &"cell": member.current_cell, &"task": member.current_task_id, + &"facility": member.assigned_facility_id, &"role": member.role_id, + &"carried_inventory_id": member.carried_inventory_id}) + for task: StaffTask in staff_service.get_tasks(): + tasks.append({&"id": task.task_id, &"state": StaffTask.State.keys()[task.state], + &"type": task.task_type, &"staff": task.assigned_staff_id, + &"target": task.target_entity_id, &"rescue_facility": task.rescue_facility_id}) + model[&"guests"] = guests + model[&"staff"] = staff + model[&"tasks"] = tasks + _fit_life_actors(model) + var bridge: Variant = _service(&"supply_logistics") + var goods: Dictionary = bridge.get_report() if bridge != null else {&"requests": [], &"deliveries": []} + model[&"goods"] = goods + var badges: Array[Dictionary] = [] + var facilities: Variant = _service(&"facility_service") + var world: Variant = _service(&"world_service") + if facilities != null and world != null: + for instance: FacilityInstance in facilities.get_instances(): + if instance.origin.z != layer_mode: + continue + var cells: Array[Vector3i] = world.get_entity_cells(instance.instance_id) + if cells.is_empty(): + continue + var rect := _cell_bounds_rect(cells) + var data: Dictionary = model[&"rooms"][instance.instance_id] + data[&"rect"] = rect + data[&"definition_id"] = instance.definition_id + var count := RoomPresentation.slots(rect, _cell_dimensions().x / 16.0, instance.definition_id).size() + badges.append({&"facility": instance.instance_id, &"kind": &"business", + &"at": rect.position + Vector2(2, 2), + &"text": RoomPresentation.business_label(data) + " " + RoomPresentation.label(data, count)}) + var requests: Array[Dictionary] = [] + for request: Dictionary in goods.get(&"requests", []): + if request[&"source_facility_id"] == instance.instance_id or request[&"destination_facility_id"] == instance.instance_id: + requests.append(request) + if not requests.is_empty(): + badges.append({&"facility": instance.instance_id, &"kind": &"supply", + &"at": rect.position + Vector2(2, 16), &"requests": requests, + &"text": RoomPresentation.supply_label(requests)}) + model[&"badges"] = badges + _resolve_observation_labels(model) + var obstacles: Array[Rect2] = [] + for cabin: Dictionary in model[&"cabins"]: + obstacles.append(cabin[&"rect"]) + obstacles.append(cabin[&"badge_rect"]) + for collection: Array in [badges, model[&"landing_badges"], model.get(&"actor_aggregations", [])]: + for badge: Dictionary in collection: + obstacles.append(_readable_badge_rect(badge[&"at"], badge[&"text"])) + model[&"traffic_obstacles"] = obstacles + var traffic: Array[Dictionary] = [] + var path: Variant = _service(&"path_service") + var bounds := _world_bounds() + if path != null and not bounds.is_empty(): + for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1): + for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1): + var cell := Vector3i(x, y, layer_mode) + var row: Dictionary = path.get_cell_traffic(cell) + if not row.is_empty(): + var cell_rect := get_cell_rect(cell) + var rect := cell_rect.grow(-2.0) + var world_pixel := _cell_dimensions().x / 16.0 + var thickness := clampf(rect.size.y * 0.16, 2.0, 5.0) + var band := Rect2(Vector2(rect.position.x + rect.size.x * 0.12, + cell_rect.end.y - 2 * world_pixel - thickness * 0.5), + Vector2(rect.size.x * 0.76, thickness)) + traffic.append({&"cell": cell, &"traffic": row, &"rect": rect, &"band_rect": band, + &"segment_rects": _traffic_segments(band, rect, obstacles)}) + model[&"traffic"] = traffic + return model + + +## Subtract opaque observation regions, retaining a visible density mark even +## when the ordinary floor band is completely behind a moving cabin. +func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Array[Rect2]: + for y: float in [band.position.y, cell.position.y, cell.end.y - band.size.y]: + var pieces: Array[Rect2] = [Rect2(Vector2(band.position.x, y), band.size)] + for obstacle: Rect2 in obstacles: + var remaining: Array[Rect2] = [] + for piece: Rect2 in pieces: + var cut := piece.intersection(obstacle) + if not cut.has_area(): + remaining.append(piece) + continue + for part: Rect2 in [Rect2(piece.position, Vector2(piece.size.x, cut.position.y-piece.position.y)), + Rect2(Vector2(piece.position.x, cut.end.y), Vector2(piece.size.x, piece.end.y-cut.end.y)), + Rect2(Vector2(piece.position.x, cut.position.y), Vector2(cut.position.x-piece.position.x, cut.size.y)), + Rect2(Vector2(cut.end.x, cut.position.y), Vector2(piece.end.x-cut.end.x, cut.size.y))]: + if part.has_area(): + remaining.append(part) + pieces = remaining + if not pieces.is_empty(): + return pieces + return [] + + +func _resolve_observation_labels(model: Dictionary) -> void: + var placed: Array[Dictionary] = [] + for i: int in model[&"cabins"].size(): + var cabin: Dictionary = model[&"cabins"][i] + placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]}) + placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]}) + for kind: String in ["business", "supply", "landing", "aggregation"]: + var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", []) + for i: int in collection.size(): + var badge: Dictionary = collection[i] + if kind in ["business", "supply"] and badge[&"kind"] != kind: + continue + var anchor: Vector2 = badge[&"at"] + var at := anchor + var displaced_by: Array[String] = [] + # Each collision advances to an existing right edge. Positive widths + # guarantee termination; touching edges are allowed. + while true: + var blocker: Dictionary = {} + for previous: Dictionary in placed: + if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area(): + blocker = previous + break + if blocker.is_empty(): + break + displaced_by.append(blocker[&"id"]) + at.x = (blocker[&"rect"] as Rect2).end.x + badge[&"anchor"] = anchor + badge[&"at"] = at + badge[&"displaced_by"] = displaced_by.duplicate(true) + placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])}) + + +func _draw_life_badges(canvas: CanvasItem) -> void: + for badge: Dictionary in _life_observation.get(&"badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + func _draw_background() -> void: var night := false var clock := get_node_or_null("/root/GameClock") @@ -649,40 +850,43 @@ func _draw_leaf_cluster(center: Vector2, p: float, variation: int) -> void: draw_rect(Rect2(c+offset,Vector2(3+i%2,2)*p),Color("95ad69") if i%3==0 else Color("648957")) -func _draw_congestion_overlay(bounds: Dictionary) -> void: - var path_service: Variant = _service(&"path_service") - if path_service == null or not path_service.has_method(&"get_cell_traffic"): +func _draw_life_overlay() -> void: + if _life_observation.is_empty(): return - for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1): - for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1): - var cell := Vector3i(x, y, layer_mode) - var traffic_value: Variant = path_service.call(&"get_cell_traffic", cell) - if not traffic_value is Dictionary or traffic_value.is_empty(): - continue - var traffic: Dictionary = traffic_value - var density := int(traffic.get(&"current_density", 0)) - var capacity := maxi(int(traffic.get(&"base_capacity", 1)), 1) - var blocked := bool(traffic.get(&"temporary_block", false)) - if density <= 0 and not blocked: - continue - var ratio := float(density) / float(capacity) - var color := Color("55c96b") - if blocked or ratio > 1.0: - color = Color("e34332") - elif ratio >= 0.65: - color = Color("e3a62f") - var rect := get_cell_rect(cell).grow(-2.0) - var center := rect.get_center() - var route_width := clampf(rect.size.y * 0.16, 2.0, 5.0) - draw_line( - Vector2(rect.position.x + rect.size.x * 0.12, center.y), - Vector2(rect.end.x - rect.size.x * 0.12, center.y), - Color(color, 0.90), - route_width - ) - if blocked: - draw_line(rect.position, rect.end, color, 2.0) - draw_line(Vector2(rect.end.x, rect.position.y), Vector2(rect.position.x, rect.end.y), color, 2.0) + # These are actual child indices and the passes executed by this callback. + _life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()] + _life_observation[&"overlay_passes"] = [] + _life_observation[&"overlay_passes"].append(&"traffic") + _draw_congestion_overlay(_life_overlay) + _life_observation[&"overlay_passes"].append(&"cabins") + _draw_elevators(_life_overlay) + _draw_life_badges(_life_overlay) + _life_observation[&"overlay_passes"].append(&"actors") + for cabin: Dictionary in _life_observation.get(&"cabins", []): + for actor: Dictionary in cabin[&"passengers"]: + _draw_observed_actor(actor, _life_overlay) + _draw_actors(_life_overlay) + for badge: Dictionary in _life_observation.get(&"actor_aggregations", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay) + _life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"] + + +func _draw_congestion_overlay(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"traffic", []): + var traffic: Dictionary = row[&"traffic"] + var density := int(traffic[&"current_density"]) + var blocked := bool(traffic[&"temporary_block"]) + if density <= 0 and not blocked: + continue + var ratio := float(density) / maxi(int(traffic[&"base_capacity"]), 1) + var color := Color("e34332") if blocked or ratio > 1.0 else Color("e3a62f") if ratio >= 0.65 else Color("55c96b") + # At overview zoom a whole cell can be covered by opaque badges. Still + # submit its density band, beneath those later draws, and report no visible + # fragments. No traffic cell or raw density row is dropped. + if row[&"segment_rects"].is_empty(): + canvas.draw_rect(row[&"band_rect"], Color(color, 0.90)) + for segment: Rect2 in row[&"segment_rects"]: + canvas.draw_rect(segment, Color(color, 0.90)) ## Visual-only level comes from the existing live FacilityInstance, not the @@ -793,29 +997,40 @@ func _draw_world_entity_icons() -> void: draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id)) if instance_id==selected_entity_id: draw_rect(rect.grow(-p),Color("f5d582"),false,2*p) - var data: Dictionary = _room_presentations.get(instance_id, {}) - if not data.is_empty() and (rect.size.x >= 80 or instance_id == selected_entity_id) and id not in [&"guest_corridor", &"service_corridor", &"guest_stairs", &"service_stairs", &"guest_elevator", &"service_elevator"]: - var status := RoomPresentation.label(data, RoomPresentation.slots(rect, p, id).size()) - _draw_readable_badge(rect.position+Vector2(2, 2), status.replace("利用", "治療") if id == &"infirmary" else status.replace("利用", "休憩") if id in [&"staff_waiting_room", &"staff_dormitory"] else status) -func _draw_readable_badge(at: Vector2, text: String) -> void: - var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x+6 - var rect := Rect2(at, Vector2(width, 14)) +func _readable_badge_rect(at: Vector2, text: String) -> Rect2: + # A conservative em-width box makes geometry independently reproducible. + # Text still uses the existing fallback font and size. + return Rect2(at, Vector2(text.length() * 10 + 6, 14)) + + +func _draw_readable_badge(at: Vector2, text: String, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var rect := _readable_badge_rect(at, text) if not rect.intersects(Rect2(Vector2.ZERO, size)): return - draw_rect(rect, Color("34413b", 0.94)) - draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce")) + canvas.draw_rect(rect, Color("34413b")) + canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce")) func _prepare_room_presentations() -> void: - _room_presentations.clear() - _carried_patients.clear() - _treated_patients.clear() - _resting_staff.clear() + var model := _observe_rooms() + _room_presentations = model[&"rooms"] + _carried_patients = model[&"carried_patients"] + _treated_patients = model[&"treated_patients"] + _resting_staff = model[&"resting_staff"] + + +func _observe_rooms() -> Dictionary: + var _room_presentations: Dictionary = {} + var _carried_patients: Dictionary = {} + var _treated_patients: Dictionary = {} + var _resting_staff: Dictionary = {} var service: Variant = _service(&"facility_service") if service == null: - return + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} for instance: Variant in service.get_instances(): if instance.origin.z == layer_mode: _room_presentations[instance.instance_id] = RoomPresentation.describe(instance) @@ -843,12 +1058,22 @@ func _prepare_room_presentations() -> void: else: _carried_patients[task.target_entity_id] = task.assigned_staff_id + return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff} + func _prepare_queues() -> void: - _queue_positions.clear() - _queue_hidden.clear() - _landing_badges.clear() - _transport_report.clear() + var model := _observe_queues() + _queue_positions = model[&"queue_positions"] + _queue_hidden = model[&"queue_hidden"] + _landing_badges.assign(model[&"landing_badges"]) + _transport_report = model[&"elevators"] + + +func _observe_queues() -> Dictionary: + var _queue_positions: Dictionary = {} + var _queue_hidden: Dictionary = {} + var _landing_badges: Array[Dictionary] = [] + var _transport_report: Dictionary = {} var groups: Dictionary = {} var facilities: Variant = _service(&"facility_service") var guests: Variant = _service(&"guest_service") @@ -876,7 +1101,8 @@ func _prepare_queues() -> void: var lift_cells: Dictionary = {} for shaft: Dictionary in _transport_report.get(&"shafts", []): for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])): - lift_cells[stop] = true + for offset: int in range(maxi(int(shaft.get(&"width", 2)), 1)): + lift_cells[stop + Vector3i(offset, 0, 0)] = true var reserved: Dictionary = {} var cells: Array = groups.keys() cells.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return a.y < b.y or (a.y == b.y and a.x < b.x)) @@ -891,6 +1117,8 @@ func _prepare_queues() -> void: var next := cell + Vector3i(direction*distance, 0, 0) if world == null or not world.is_walkable(next): break + if lift_cells.has(next): + break var occupant: StringName = world.get_occupant(next) var facility: Variant = facilities.get_instance(occupant) if facilities != null else null if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: @@ -907,10 +1135,10 @@ func _prepare_queues() -> void: index += 1 for hidden: int in range(index, members.size()): _queue_hidden[members[hidden]] = true - var text := "待ち%d人" % members.size() - if index < members.size(): - text += "(他%d人)" % (members.size()-index) - _landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text}) + var text := RoomPresentation.queue_label(members.size(), members.size()-index) + _landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text, &"cell": cell, &"members": members.duplicate(), &"visible_count": index, &"overflow": members.size()-index}) + + return {&"queue_positions": _queue_positions, &"queue_hidden": _queue_hidden, &"landing_badges": _landing_badges, &"elevators": _transport_report, &"shaft_footprint": lift_cells.keys()} func _add_queue_member(groups: Dictionary, key: String, cell: Vector3i) -> void: @@ -1037,15 +1265,20 @@ func _draw_facility_labels() -> void: draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9")) -func _draw_elevators() -> void: - _boarded_actor_ids.clear() - var transport: Variant = _service(&"elevators") - if transport == null or not transport.has_method(&"get_report"): - _elevator_tracks.clear() - return - var report_value: Variant = _transport_report - if not report_value is Dictionary: - return +func _draw_elevators(canvas: CanvasItem) -> void: + for row: Dictionary in _life_observation.get(&"cabins", []): + PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"]) + PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"]) + _draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas) + for badge: Dictionary in _life_observation.get(&"landing_badges", []): + _draw_readable_badge(badge[&"at"], badge[&"text"], canvas) + + +func _observe_elevators(observation: Dictionary) -> Array[Dictionary]: + var cabins: Array[Dictionary] = [] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _elevator_tracks: Dictionary = observation[&"elevator_tracks"] + var report_value: Dictionary = observation[&"elevators"] var alive: Dictionary = {} var p := _cell_dimensions().x/16.0 for value: Variant in report_value.get(&"shafts", []): @@ -1061,7 +1294,7 @@ func _draw_elevators() -> void: for actor_id: Variant in passengers: _boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true var floor_value := float(shaft.get(&"visual_floor", shaft.get(&"current_floor", stops[0].y))) - var visual_floor := _tracked_elevator_floor(shaft_id, floor_value) + var visual_floor := _tracked_elevator_floor(shaft_id, floor_value, observation) if stops[0].z != layer_mode: continue var origin := get_cell_rect(stops[0]) @@ -1073,10 +1306,9 @@ func _draw_elevators() -> void: var operational := bool(shaft.get(&"operational", true)) var phase := StringName(shaft.get(&"phase", &"IDLE")) var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"] - PixelArt.elevator_cabin(self,cabin,p,layer_mode==LayerMode.BACK,operational) + var passengers_drawn: Array[Dictionary] = [] var seat_columns := maxi(int((cabin.size.x / p - 4.0) / 8.0), 1) - var visible_count := mini(passengers.size(), seat_columns*2) - for index: int in range(visible_count): + for index: int in range(passengers.size()): var actor_key := _elevator_actor_key(String(passengers[index])) var is_staff := actor_key.begins_with("staff/") var person: Variant = null @@ -1087,24 +1319,23 @@ func _draw_elevators() -> void: var carrying := is_staff and person != null and not String(person.carried_inventory_id).is_empty() var state := int(person.state) if person != null else 0 var x := cabin.position.x + (2.0 + (index % seat_columns + 0.5)*8.0)*p - var feet := cabin.end.y - (2.0 + int(index / seat_columns)*9.0)*p + var feet := cabin.end.y - (2.0 + (int(index / seat_columns) % 2)*9.0)*p var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5) - _draw_pixel_actor(center,actor_key,is_staff,state,false,carrying,&"",true) - PixelArt.elevator_gate(self,cabin,p,doors_open) - var capacity := int(shaft.get(&"capacity", 0)) - var load_text := "%d/%d人" % [passengers.size(), capacity] - if capacity > 0 and passengers.size() >= capacity: - load_text = "満員 " + load_text - if not operational: - load_text = "停止 " + load_text - _draw_readable_badge(Vector2(cabin.position.x, cabin.position.y-14), load_text) - for badge: Dictionary in _landing_badges: - _draw_readable_badge(badge[&"at"], badge[&"text"]) + passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation)) + cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p, + &"operational": operational, &"doors_open": doors_open, + &"phase": phase, &"passenger_count": passengers.size(), + &"passengers": passengers_drawn, + &"badge_at": Vector2(cabin.position.x, cabin.position.y-14), + &"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)), + &"badge": RoomPresentation.elevator_label(shaft)}) for shaft_id: Variant in _elevator_tracks.keys(): if not alive.has(shaft_id): _elevator_tracks.erase(shaft_id) + return cabins + func _elevator_actor_key(actor_id: String) -> String: if actor_id.begins_with("guest:"): @@ -1114,7 +1345,8 @@ func _elevator_actor_key(actor_id: String) -> String: return actor_id -func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> float: +func _tracked_elevator_floor(shaft_id: StringName, current_floor: float, observation: Dictionary = {}) -> float: + var _elevator_tracks: Dictionary = observation.get(&"elevator_tracks", self._elevator_tracks) if not _elevator_tracks.has(shaft_id): _elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time} var track: Dictionary = _elevator_tracks[shaft_id] @@ -1129,7 +1361,21 @@ func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> floa return lerpf(float(track[&"from"]),current_floor,t) -func _draw_actors() -> void: +func _draw_actors(canvas: CanvasItem = null) -> void: + for row: Dictionary in _life_observation.get(&"actor_drawings", []): + _draw_observed_actor(row, canvas) + _draw_actor_bubble(row, canvas) + + +func _observe_actors(observation: Dictionary) -> Array[Dictionary]: + var rows: Array[Dictionary] = [] + var _actor_tracks: Dictionary = observation[&"actor_tracks"] + var _boarded_actor_ids: Dictionary = observation[&"boarded"] + var _queue_hidden: Dictionary = observation[&"queue_hidden"] + var _queue_positions: Dictionary = observation[&"queue_positions"] + var _carried_patients: Dictionary = observation[&"carried_patients"] + var _treated_patients: Dictionary = observation[&"treated_patients"] + var _resting_staff: Dictionary = observation[&"resting_staff"] var counts: Dictionary = {} var alive: Dictionary = {} var service_slots: Dictionary = {} @@ -1143,22 +1389,18 @@ func _draw_actors() -> void: if _boarded_actor_ids.has(key) or _queue_hidden.has(key): _actor_tracks.erase(key) continue - var position := _tracked_actor_position(key,guest.current_cell) + var position := _tracked_actor_position(key,guest.current_cell,observation) if guest.current_cell.z==layer_mode: var receiving := not String(guest.current_service_id).is_empty() if _queue_positions.has(key): position = _queue_positions[key] elif receiving: - position = _service_use_position(guest,position,service_slots) + position = _service_use_position(guest,position,service_slots,observation) if not position.is_finite(): continue else: position += _actor_offset(guest.current_cell,counts) - _draw_pixel_actor(position,key,false,int(guest.state),receiving,false,_guest_activity(guest)) - var rel_guest: Variant = _guest_relationship(guest.guest_id) - var guest_bubble := OperationFeedbackScript.resolve_guest_bubble(guest, rel_guest) - if guest_bubble != OperationFeedbackScript.BUBBLE_NONE: - _draw_unit_bubble(position, guest_bubble) + rows.append(_actor_drawing(position,key,false,int(guest.state),receiving,false,_guest_activity(guest),false,observation)) var staff_service: Variant = _service(&"staff_service") if staff_service != null and staff_service.has_method(&"get_staff_members"): for staff: Variant in staff_service.call(&"get_staff_members"): @@ -1169,32 +1411,32 @@ func _draw_actors() -> void: if _boarded_actor_ids.has(key) or _queue_hidden.has(key): _actor_tracks.erase(key) continue - var position := _tracked_actor_position(key,staff.current_cell) + var position := _tracked_actor_position(key,staff.current_cell,observation) if staff.current_cell.z==layer_mode: + if _queue_positions.has(key): + rows.append(_actor_drawing(_queue_positions[key],key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) + continue if _carried_patients.has(staff.staff_id): var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id]) if carrier != null: - position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell) - Vector2(0, 12*_cell_dimensions().x/16.0) - _draw_pixel_actor(position,key,true,int(staff.state),false,false) + position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0) + rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) continue if _resting_staff.has(staff.staff_id): - position = _resting_staff_position(staff.staff_id, position) - if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false) + position = _resting_staff_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) continue if _treated_patients.has(staff.staff_id): - position = _rescue_treatment_position(staff.staff_id, position) - if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false) + position = _rescue_treatment_position(staff.staff_id, position,observation) + if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation)) continue - var staff_draw_pos: Vector2 = _queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts))) - _draw_pixel_actor(staff_draw_pos,key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff)) - var rel_staff: Variant = _staff_relationship(staff.staff_id) - var staff_bubble := OperationFeedbackScript.resolve_staff_bubble(staff, rel_staff) - if staff_bubble != OperationFeedbackScript.BUBBLE_NONE: - _draw_unit_bubble(staff_draw_pos, staff_bubble) + rows.append(_actor_drawing(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts),observation)),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation)) for key: Variant in _actor_tracks.keys(): if not alive.has(key): _actor_tracks.erase(key) + return rows + func _staff_work_animation(staff: StaffRuntime) -> StringName: if staff.role_id != &"cleaner": @@ -1209,7 +1451,8 @@ func _staff_work_animation(staff: StaffRuntime) -> StringName: return &"idle" -func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2: +func _staff_work_position(staff: Variant, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]: return fallback var service: Variant = _service(&"facility_service") @@ -1235,7 +1478,8 @@ func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2: return Vector2(rect.position.x+(slot+0.5)*rect.size.x/columns, rect.position.y+26*p-_cell_dimensions().y*0.5) -func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2: +func _tracked_actor_position(key: String, cell: Vector3i, observation: Dictionary = {}) -> Vector2: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) if not _actor_tracks.has(key): _actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0} var track: Dictionary = _actor_tracks[key] @@ -1254,7 +1498,8 @@ func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2: return cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(cell),t) -func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary) -> Vector2: +func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) # Guest paths stop at an access cell. While a service is active, project the # occupant into that facility's use position; authoritative paths stay intact. var id := StringName(guest.target_facility_id) @@ -1289,6 +1534,21 @@ func _guest_activity(guest: Variant) -> StringName: func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false) -> void: + _draw_observed_actor(_actor_drawing(center, key, staff, state, receiving, carrying, activity, riding)) + + +func _draw_observed_actor(row: Dictionary, canvas: CanvasItem = null) -> void: + if canvas == null: + canvas = self + var at: Vector2 = row[&"at"] + var p: float = row[&"scale"] + canvas.draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2)) + People.draw(canvas, at, p, row[&"mode"], row[&"frame"], row[&"variant"], row[&"staff"], row[&"flip"]) + + +func _actor_drawing(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false, observation: Dictionary = {}) -> Dictionary: + var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks) + var _queue_positions: Dictionary = observation.get(&"queue_positions", self._queue_positions) var world_pixel := _cell_dimensions().x / 16.0 var scale := 0.5 if riding else 0.75 var p := maxf(floorf(world_pixel * scale), 1.0) if world_pixel >= 2.0 else world_pixel * scale @@ -1315,34 +1575,330 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING): mode = &"wait" var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round() - draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2)) - People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0) + return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode, + &"frame": frame, &"variant": variant, &"staff": staff, &"state": state, + &"receiving": receiving, &"carrying": carrying, &"activity": activity, + &"riding": riding, &"flip": float(track.get(&"direction", 1.0)) < 0.0} -func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName) -> void: +## Identity cores are tied to the unchanged PixelPeople poses, not sprite boxes. +func _life_core(row: Dictionary) -> Rect2: + var phase := int(row[&"frame"]) % 8 + var core := Rect2(6, 1, 6, 12) + if row[&"mode"] in [&"sleep", &"collapsed", &"standard_room_sleep"]: + core = Rect2(2, 11, 13, 5) + if row[&"mode"] == &"standard_room_sleep" and phase in [2, 3, 4]: + core.position.y -= 1 + core.size.y += 1 + elif row[&"mode"] in [&"walk", &"carry_walk"] and phase in [2, 6]: + core.position.y -= 1 + if row[&"flip"]: + core.position.x = 16-core.end.x + return Rect2(row[&"at"]+core.position*row[&"scale"], core.size*row[&"scale"]) + + +func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dictionary: + var track: Dictionary = model[&"actor_tracks"].get(key, {}) + var transit: bool = not model[&"paused"] and track.get(&"from", cell) != cell and _animation_time-float(track.get(&"at", 0.0)) < 0.42 + var rect := get_cell_rect(cell) + if transit: + rect = rect.merge(get_cell_rect(track[&"from"])) + return {&"bounds": rect, &"transit": transit} + + +## Keep non-passengers on a real corridor landing when a cabin crosses their pose. +func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary: + var blocked := false + for cabin: Dictionary in model[&"cabins"]: + if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]): + blocked = true + break + if not blocked: + return region + var world: Variant = _service(&"world_service") + var facilities: Variant = _service(&"facility_service") + var footprint: Array = model[&"shaft_footprint"] + var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z) + origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + var lane: Array[Vector3i] = [] + for origin: Vector3i in origins: + for direction: int in [-1, 1]: + for distance: int in range(1, 9): + var target := origin + Vector3i(direction*distance, 0, 0) + if world == null or footprint.has(target) or not world.is_walkable(target): + break + var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null + if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]: + break + if not lane.has(target): + lane.append(target) + lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x)) + if lane.is_empty(): + var anchor := get_cell_rect(cell).position+Vector2(-80, -16) + var nearest := INF + for badge: Dictionary in model[&"landing_badges"]: + var landing: Vector3i = badge[&"cell"] + if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest: + nearest = absf(landing.x-cell.x) + anchor = badge[&"at"] + return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor} + var selected: Vector3i = lane[0] + for target: Vector3i in lane: + if not model[&"queue_positions"].values().has(cell_to_canvas(target)): + selected = target + break + return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)} + + +func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary: + var key: String = row[&"key"] + var cell: Vector3i = person[&"cell"] + var result := _life_track_region(key, cell, model) + var p := _cell_dimensions().x/16.0 + var world: Variant = _service(&"world_service") + var facility_id: StringName = &"" + if model[&"queue_positions"].has(key): + result = {&"bounds": Rect2(model[&"queue_positions"][key]-_cell_dimensions()*0.5, _cell_dimensions()), &"transit": false} + elif not row[&"staff"]: + if not String(person[&"service"]).is_empty(): + var room: Dictionary = model[&"rooms"].get(person[&"target"], {}) + if (room.get(&"people", []) as Array).has(person[&"id"]): + facility_id = person[&"target"] + else: + var id: StringName = person[&"id"] + if model[&"carried_patients"].has(id): + for carrier: Dictionary in model[&"staff"]: + if carrier[&"id"] == model[&"carried_patients"][id]: + result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model) + var rect: Rect2 = result[&"bounds"] + rect = rect.grow(p) + rect.position.y -= 12*p + result[&"bounds"] = rect + if result.has(&"center"): + result[&"center"] -= Vector2(0, 12*p) + elif model[&"resting_staff"].has(id): + facility_id = model[&"resting_staff"][id] + elif model[&"treated_patients"].has(id): + facility_id = model[&"treated_patients"][id] + elif person[&"state"] in ["WORKING", "AVAILABLE"] and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(person[&"facility"]) + var room: Dictionary = model[&"rooms"].get(person[&"facility"], {}) + var slot := (room.get(&"staff", []) as Array).find(id) + var rect := _cell_bounds_rect(cells) + var columns := maxi(int(rect.size.x/(16*p)), 1) + if cells.has(cell) and slot >= 0 and slot < columns and rect.size.y >= 40*p: + result = {&"bounds": Rect2(rect.position+Vector2(slot*rect.size.x/columns, 0), Vector2(rect.size.x/columns, rect.size.y)), &"transit": false} + if not facility_id.is_empty() and world != null: + var cells: Array[Vector3i] = world.get_entity_cells(facility_id) + if not cells.is_empty(): + result = {&"bounds": _cell_bounds_rect(cells), &"transit": false} + if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false): + result = _life_landing_region(result, cell, model) + result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p) + return result + + +func _life_scaled(row: Dictionary, scale: float) -> Dictionary: + var result := row.duplicate(true) + result[&"scale"] = scale + result[&"at"] = (row[&"center"]+Vector2(-8*scale, _cell_dimensions().y*0.5-3*_cell_dimensions().x/16.0-17*scale)).round() + return result + + +## Forward/backward packing keeps each member's own interval. No relocation. +func _life_pack(group: Array, scale: float) -> Array: + var trial: Array = [] + var right := -INF + for member: Dictionary in group: + var row := _life_scaled(member[&"row"], minf(scale, member[&"row"][&"scale"])) + var core := _life_core(row) + var bounds: Rect2 = member[&"bounds"] + var x := maxf(maxf(core.position.x, bounds.position.x), right) + row[&"at"].x += x-core.position.x + right = x+core.size.x + trial.append(row) + var left := INF + for i: int in range(trial.size()-1, -1, -1): + var row: Dictionary = trial[i] + var core := _life_core(row) + var bounds: Rect2 = group[i][&"bounds"] + var x := minf(core.position.x, minf(bounds.end.x, left)-core.size.x) + row[&"at"].x += x-core.position.x + left = x + if x < bounds.position.x or core.position.y < bounds.position.y or core.end.y > bounds.end.y: + return [] + return trial + + +## Rebuild vertical components at every fitted scale. Ceilings only decrease; +## once none decreases, the feet/cores and therefore the partition are fixed. +func _life_fitted_groups(members: Array) -> Array: + var groups: Array = [] + while true: + for member: Dictionary in members: + member[&"row"].merge(_life_scaled(member[&"row"], member[&"row"][&"scale"]), true) + members.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: + var ay := _life_core(a[&"row"]).position.y + var by := _life_core(b[&"row"]).position.y + return ay < by or (ay == by and a[&"row"][&"key"] < b[&"row"][&"key"])) + groups = [] + var bottom := -INF + for member: Dictionary in members: + var core := _life_core(member[&"row"]) + if groups.is_empty() or core.position.y >= bottom: + groups.append([]) + bottom = core.end.y + else: + bottom = maxf(bottom, core.end.y) + groups.back().append(member) + var reduced := false + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var ceiling := 1.0 + for member: Dictionary in group: + ceiling = maxf(ceiling, member[&"row"][&"scale"]) + if _life_pack(group, ceiling).is_empty() and ceiling > 1: + for member: Dictionary in group: + var scale := minf(member[&"row"][&"scale"], maxf(1, int(ceiling)-1)) + member[&"row"].merge(_life_scaled(member[&"row"], scale), true) + reduced = true + if not reduced: + return groups + return groups + + +func _fit_life_actors(model: Dictionary) -> void: + var people: Dictionary = {} + for person: Dictionary in model[&"guests"]: + people["guest/"+String(person[&"id"])] = person + for person: Dictionary in model[&"staff"]: + people["staff/"+String(person[&"id"])] = person + var members: Array = [] + var stranded: Array = [] + var all_rows: Array = model[&"actor_drawings"].duplicate() + var passenger_bounds: Dictionary = {} + for cabin: Dictionary in model[&"cabins"]: + for row: Dictionary in cabin[&"passengers"]: + all_rows.append(row) + passenger_bounds[row[&"key"]] = (cabin[&"rect"] as Rect2).grow(-_cell_dimensions().x/16.0) + for row: Dictionary in all_rows: + var person: Dictionary = people[row[&"key"]] + row[&"cell"] = person[&"cell"] + var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false} + if region.has(&"center"): + var jitter := Vector2.ZERO + if not model[&"carried_patients"].has(person[&"id"]): + var track: Dictionary = model[&"actor_tracks"][row[&"key"]] + var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1) + jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t) + row[&"center"] = region[&"center"]+jitter + row.merge(_life_scaled(row, row[&"scale"]), true) + region[&"row"] = row + if region.get(&"no_lane", false): + stranded.append(region) + elif not region[&"transit"]: + members.append(region) + var groups := _life_fitted_groups(members) + model[&"actor_aggregations"] = [] + var hidden: Dictionary = {} + for member: Dictionary in stranded: + var row: Dictionary = member[&"row"] + hidden[row[&"key"]] = true + var aggregate: Dictionary = {} + for existing: Dictionary in model[&"actor_aggregations"]: + if existing[&"at"] == member[&"badge_at"]: + aggregate = existing + break + if aggregate.is_empty(): + aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""} + model[&"actor_aggregations"].append(aggregate) + aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + aggregate[&"text"] = "+%d" % aggregate[&"members"].size() + for group: Array in groups: + group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"])) + var base := 1.0 + for member: Dictionary in group: + base = maxf(base, member[&"row"][&"scale"]) + var fitted := _life_pack(group, base) + var retained: Array = group + if fitted.is_empty(): + retained = [] + var identities: Array = [] + var drawn: Array = [] + for member: Dictionary in group: + var row: Dictionary = member[&"row"] + identities.append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]}) + var attempt := retained.duplicate() + attempt.append(member) + var packed := _life_pack(attempt, 1) + if not packed.is_empty(): + retained = attempt + fitted = packed + drawn.append(row[&"key"]) + else: + hidden[row[&"key"]] = true + model[&"actor_aggregations"].append({&"members": identities, &"drawn_keys": drawn, &"at": (group[0][&"bounds"] as Rect2).position, &"text": "+%d" % (group.size()-retained.size())}) + for i: int in fitted.size(): + retained[i][&"row"].merge(fitted[i], true) + model[&"actor_drawings"] = model[&"actor_drawings"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + for cabin: Dictionary in model[&"cabins"]: + cabin[&"passengers"] = cabin[&"passengers"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"])) + + +## P24 overhead bubble for one already observed and fitted actor row. Presentation +## only: reads authoritative Staff/Guest state and RelationshipState at draw time, +## never writes the life observation report or its rows, so the P23 frame report +## stays exactly what was observed. Riding passengers and aggregated (hidden) +## actors have no row here and therefore never keep a stale symbol. +func _draw_actor_bubble(row: Dictionary, canvas: CanvasItem = null) -> void: + if bool(row.get(&"riding", false)): + return + var key := String(row.get(&"key", "")) + var bubble: StringName = OperationFeedbackScript.BUBBLE_NONE + if key.begins_with("staff/"): + var staff_id := StringName(key.trim_prefix("staff/")) + var staff_service: Variant = _service(&"staff_service") + var staff: Variant = staff_service.get_staff(staff_id) if staff_service != null and staff_service.has_method(&"get_staff") else null + bubble = OperationFeedbackScript.resolve_staff_bubble(staff, _staff_relationship(staff_id)) + elif key.begins_with("guest/"): + var guest_id := StringName(key.trim_prefix("guest/")) + var guest_service: Variant = _service(&"guest_service") + var guest: Variant = guest_service.get_guest(guest_id) if guest_service != null and guest_service.has_method(&"get_guest") else null + bubble = OperationFeedbackScript.resolve_guest_bubble(guest, _guest_relationship(guest_id)) + if bubble != OperationFeedbackScript.BUBBLE_NONE: + _draw_unit_bubble(row[&"center"], bubble, canvas, row.get(&"at", Vector2.INF), float(row.get(&"scale", 0.0))) + + +func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName, canvas: CanvasItem = null, actor_at := Vector2.INF, actor_scale := 0.0) -> void: + if canvas == null: + canvas = self var world_pixel := _cell_dimensions().x / 16.0 var p := maxf(floorf(world_pixel * 0.75), 1.0) if world_pixel >= 2.0 else world_pixel * 0.75 var bubble_pos := actor_center + Vector2(-9 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 32 * p) + if actor_scale > 0.0 and actor_at.is_finite(): + # Fitted P23 rows carry their own sprite origin and scale; follow them. + p = actor_scale + bubble_pos = actor_at + Vector2(-1 * p, -15 * p) var bubble_size := Vector2(18 * p, 13 * p) var rect := Rect2(bubble_pos.round(), bubble_size.round()) - draw_rect(rect, Color("ffffff", 0.94)) - draw_rect(rect, Color("303c40", 0.85), false, 1.0) + canvas.draw_rect(rect, Color("ffffff", 0.94)) + canvas.draw_rect(rect, Color("303c40", 0.85), false, 1.0) var tri_p1 := Vector2(rect.get_center().x - 2.5 * p, rect.end.y) var tri_p2 := Vector2(rect.get_center().x + 2.5 * p, rect.end.y) var tri_p3 := Vector2(rect.get_center().x, rect.end.y + 3.5 * p) - draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color("ffffff", 0.94)) - draw_line(tri_p1, tri_p3, Color("303c40", 0.85), 1.0) - draw_line(tri_p2, tri_p3, Color("303c40", 0.85), 1.0) + canvas.draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color("ffffff", 0.94)) + canvas.draw_line(tri_p1, tri_p3, Color("303c40", 0.85), 1.0) + canvas.draw_line(tri_p2, tri_p3, Color("303c40", 0.85), 1.0) var font := ThemeDB.fallback_font var font_size := clampi(int(9 * p), 9, 14) match bubble_type: OperationFeedbackScript.BUBBLE_ZZZ: - draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "Zzz", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("2d68c4")) + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "Zzz", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("2d68c4")) OperationFeedbackScript.BUBBLE_ANGER: - draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "💢", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("d63031")) + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "💢", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("d63031")) OperationFeedbackScript.BUBBLE_HEART: - draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "♥", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("e84393")) + canvas.draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "♥", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("e84393")) func _staff_relationship(staff_id: StringName) -> Variant: @@ -1477,14 +2033,14 @@ func _facility_color(instance_id: StringName) -> Color: var instance: Variant = facility_service.call(&"get_instance", instance_id) if instance == null: return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582") - match int(instance.state): - 0: + match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)): + FacilityInstance.State.BROKEN: return Color("a24b45") - 1, 2, 3: + FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF: return Color("a77d3f") - 4: + FacilityInstance.State.CLOSED: return Color("6b6258") - 5: + FacilityInstance.State.DEGRADED: return Color("89914d") _: return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e") @@ -1635,7 +2191,9 @@ func _normalized_rotation(value: int) -> int: return posmod(value, 360) / 90 * 90 -func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Vector2: +func _rescue_treatment_position(patient_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _treated_patients: Dictionary = observation.get(&"treated_patients", self._treated_patients) var facility_id: StringName = _treated_patients[patient_id] var world: Variant = _service(&"world_service") var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] @@ -1648,7 +2206,9 @@ func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Ve return anchors[slot]+Vector2(0,-2*p-_cell_dimensions().y*0.5) -func _resting_staff_position(staff_id: StringName, fallback: Vector2) -> Vector2: +func _resting_staff_position(staff_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2: + var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations) + var _resting_staff: Dictionary = observation.get(&"resting_staff", self._resting_staff) var facility_id: StringName = _resting_staff[staff_id] var world: Variant = _service(&"world_service") var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else [] @@ -1754,3 +2314,10 @@ func _draw_build_connection(report: Dictionary) -> void: var message := "入口への経路接続済" if status == &"CONNECTED" else "入口への経路未接続" if status == &"DISCONNECTED" else "入口・経路を確認できません" draw_string(ThemeDB.fallback_font, target+Vector2(8,-12), message, HORIZONTAL_ALIGNMENT_LEFT, -1, 13, color) + +func set_demolition_mode(enabled: bool) -> void: + demolition_mode = enabled + if enabled: + clear_build_definition() + set_excavation_mode(false) + queue_redraw() diff --git a/tools/check_p24_operations_evidence.py b/tools/check_p24_operations_evidence.py index 37726fb..56d2fc0 100644 --- a/tools/check_p24_operations_evidence.py +++ b/tools/check_p24_operations_evidence.py @@ -65,18 +65,18 @@ def compute_sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() -def get_actual_git_head() -> str: - env_head = os.environ.get("CANDIDATE_COMMIT", os.environ.get("TARGET_HEAD")) - if env_head: - cleaned = env_head.strip().lower() - if HEX_COMMIT_PATTERN.match(cleaned) and not (set(cleaned) <= {"0"} or set(cleaned) <= {"f"}): - return cleaned +def get_actual_git_head(root: Path = None) -> str: + root = root if root is not None else Path(__file__).resolve().parents[1] try: import subprocess - res = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=5) + res = subprocess.run(["git", "rev-parse", "HEAD"], cwd=root, capture_output=True, text=True, timeout=5) if res.returncode == 0: h = res.stdout.strip().lower() if HEX_COMMIT_PATTERN.match(h) and not (set(h) <= {"0"} or set(h) <= {"f"}): + for name in ("CANDIDATE_COMMIT", "TARGET_HEAD"): + if name in os.environ and os.environ[name].strip().lower() != h: + print(f"ERROR: {name} mismatch with real git HEAD {h}", file=sys.stderr) + return "" return h except Exception: pass @@ -84,15 +84,11 @@ def get_actual_git_head() -> str: def get_required_suite_roster(root: Path) -> list[str]: - tests_dir = root / "tests" - if not tests_dir.is_dir(): - return [] - roster = ["tests/run_tests.gd"] - for p in sorted(tests_dir.rglob("test*.gd")): - rel = p.relative_to(root).as_posix() - if "export" not in p.relative_to(root).parts and rel != "tests/s0/test_kernel_and_save.gd": - roster.append(rel) - return sorted(list(set(roster))) + import importlib.util + spec = importlib.util.spec_from_file_location("p24_required_roster", root / "tools/run_all_tests.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module.roster(root) def find_index_file(custom_path: str = None) -> Path: @@ -376,53 +372,57 @@ def _validate_condition_artifact_content( required_suites = get_required_suite_roster(repo_root) total_required = len(required_suites) - reported_suites = full_roster.get("total_suites", full_roster.get("suites_count", full_roster.get("suites"))) - if isinstance(reported_suites, int): - if reported_suites < total_required: - return False, f"Full roster reports only {reported_suites} suites, but repository requires at least {total_required} suites" + for field in ("total_suites", "suites_count"): + if field in full_roster and (type(full_roster[field]) is not int or full_roster[field] != total_required): + return False, f"Full roster {field} must equal {total_required}" roster_failures = full_roster.get("failures") - if roster_failures is not None and (isinstance(roster_failures, bool) or not isinstance(roster_failures, int) or roster_failures != 0): + if "failures" in full_roster and (type(roster_failures) is not int or roster_failures != 0): return False, f"Full roster regression reported failures: {roster_failures!r}" # Check suite entries if provided in full_roster or referenced all-tests artifact suite_entries = full_roster.get("results", full_roster.get("rows", full_roster.get("all_tests"))) - if not suite_entries and "rows" in full_roster and full_roster["rows"] in validated_artifacts: - rows_art = validated_artifacts[full_roster["rows"]] + if isinstance(suite_entries, str) and suite_entries in validated_artifacts: + rows_art = validated_artifacts[suite_entries] try: suite_entries = json.loads(rows_art["file"].read_text(encoding="utf-8")) except Exception: suite_entries = None + if not isinstance(suite_entries, list) or not suite_entries: + return False, "Full roster requires a non-empty suite list" if isinstance(suite_entries, list) and suite_entries: seen_suites = set() for entry in suite_entries: if not isinstance(entry, dict): - continue + return False, "Full roster row must be a dict" s_path = entry.get("test", entry.get("suite", entry.get("path"))) + if not isinstance(s_path, str) or not s_path: + return False, "Full roster row missing suite path" + if s_path not in required_suites: + return False, f"Unknown full roster suite: {s_path}" + if s_path in seen_suites: + return False, f"Duplicate full roster suite: {s_path}" if s_path: seen_suites.add(s_path) p = repo_root / s_path if not p.is_file(): return False, f"Full roster suite does not exist on disk: {s_path}" expected_sha = entry.get("source_sha256") - if expected_sha and HEX64_PATTERN.match(expected_sha): + if not isinstance(expected_sha, str) or not HEX64_PATTERN.fullmatch(expected_sha): + return False, f"Full roster missing or invalid source_sha256: {s_path}" + if expected_sha: actual_sha = compute_sha256(p) if actual_sha != expected_sha: return False, f"Full roster source SHA256 mismatch for {s_path}: expected {expected_sha}, got {actual_sha}" - e_fails = entry.get("failures", entry.get("reported_failures", 0)) - if isinstance(e_fails, int) and e_fails != 0: + e_fails = entry.get("failures") + if type(e_fails) is not int or e_fails != 0: return False, f"Full roster suite {s_path} failed with {e_fails} failures" - e_passed = entry.get("passed", entry.get("completion", {}).get("passed") if isinstance(entry.get("completion"), dict) else None) - if e_passed is False: - return False, f"Full roster suite {s_path} marked passed: false" + if entry.get("passed") is not True: + return False, f"Full roster suite {s_path} passed must be True" missing_from_roster = set(required_suites) - seen_suites - if missing_from_roster and len(seen_suites) >= total_required: - pass - elif missing_from_roster and len(suite_entries) >= total_required: - pass - elif missing_from_roster: + if seen_suites != set(required_suites): return False, f"Full roster execution missing required suites: {sorted(list(missing_from_roster))[:5]}" return True, "" @@ -474,7 +474,7 @@ def verify_evidence(index_path: Path) -> bool: actual_head = get_actual_git_head() if not actual_head: - print("ERROR: Unable to resolve verified actual candidate git HEAD from environment (CANDIDATE_COMMIT/TARGET_HEAD) or git repository", file=sys.stderr) + print("ERROR: Unable to verify real repository git HEAD; git failed or CANDIDATE_COMMIT/TARGET_HEAD mismatched", file=sys.stderr) return False if candidate_commit_norm != actual_head: diff --git a/tools/regression/suite-policy.json b/tools/regression/suite-policy.json index b233e06..de0ab07 100644 --- a/tools/regression/suite-policy.json +++ b/tools/regression/suite-policy.json @@ -1,10 +1,17 @@ { "schema_version": 1, "suite_policies": { - "tests/ui/test_operations_notification.gd": { - "source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", - "ordinary_cleanup_prepared": true, - "ordinary_control_labels": ["single"], + "tests/s4/test_original_relationship_model.gd": { + "source_sha256": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/s4/test_original_relationship_runtime.gd": { + "source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9", + "ordinary_cleanup_prepared": false, + "ordinary_control_labels": [], "old_bounded_teardown": false, "new_audio_QA": false }, @@ -398,6 +405,22 @@ ], "old_bounded_teardown": false, "new_audio_QA": false + }, + "tests/ui/test_p08_normal_placement_ops.gd": { + "source_sha256": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": [ + "single" + ], + "old_bounded_teardown": false, + "new_audio_QA": false + }, + "tests/ui/test_operations_notification.gd": { + "source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147", + "ordinary_cleanup_prepared": true, + "ordinary_control_labels": ["single"], + "old_bounded_teardown": false, + "new_audio_QA": false } }, "outside_routes": {