P08-03: 配置の通常操作受入

This commit is contained in:
Agent Control Runner
2026-10-14 16:08:32 +09:00
parent 7e2368f5c4
commit 47750010b8
9 changed files with 498 additions and 56 deletions
@@ -457,3 +457,46 @@ Actual R09 --verify-only: integrity pass, acceptance pending.
- tests/fixtures/p08-03/repair13_images.py: 248a8d73ece3a60e601421fdd2ad53f8f8e971184fb1328c7155a71b8ed4ca53
- repair13-local-validation.json: f249c76c6d4c044c1a3c33ea09abc99f09a8c0ea6b0ade470d2af205eb4d89af
- repair13-local-source-manifest.json: 7d5a62155b7053398a5bc55879c9a92a07dcb3c5a0603e92d6f38eefba122ca1
## Repair17 revision 2 — submitted, no acceptance claimed
History above and all R09 evidence/pins remain historical and unrewritten.
(1) support_connection is NOT satisfied: blocked pending Root reconciliation and application of runtime-preview-proposal/runtime_preview.patch. See proposal for the additional Main display premise gap.
(2)–(5) are not verified. The earlier reviewer raw-proof read refusal remains historical. The reviewer now has the permitted task build via --add-dir (while --restricted, Read/Grep/Glob and outer sandbox remain); future independent checking with raw-proof access is required.
Native/OS proof for changed demolition status and support display is invalidated. Root must recollect after all product fixes, including the reconciled Runtime proposal, are combined. No applicability or acceptance is claimed.
Limits for repair, inference and material remain null. No 500k compression; low findings (empty cell, audio, idempotency, English Runtime messages) remain deferred. Denominator remains one existing unit among 30 packages / 88 units, no partial credit.
New HEAD: to be bound by Runner. No worker commit or branch change.
Validation: 128 Python tests passed (85.953s, exit0) after one targeted-test correction; py_compile passed with an in-scope cache directory after default-cache PermissionError; JSON load and git apply --check passed. placement.gd and proposed GDScript test are not run. Placement fixture requires Root's complete applied fix.
Pending:
- Root reconciliation and application of Runtime preview patch (including Main display premise gap)
- Root native/OS recollection for changed demolition and support UI after all fixes are combined
- Root final full175
- Independent Opus review of all 5 conditions with raw-proof access
- Integration / current final verification / ledger
- Windows same-distribution/device (no Mac substitution)
Net committed diff, from git diff --name-status --no-renames 31aa7706d28a92e313c432d5a62e35d19503dcc3 HEAD:
```text
M docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md
A docs/plans/original-completion-2026-10-02/execution/p08-03/REPAIR12_CONTEXT_GAP.md
A docs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_NATIVE_R09_COLLECTION_PINS.json
A docs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_R09_COLLECTION_AND_PUBLICATION_DIAGNOSIS.json
A docs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_R09_READONLY_IMAGE_DIMENSIONS.json
M docs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json
A docs/plans/original-completion-2026-10-02/execution/p08-03/repair13-local-source-manifest.json
A docs/plans/original-completion-2026-10-02/execution/p08-03/repair13-local-validation.json
M tools/check_p08_normal_placement_evidence.py
M tools/test_p08_normal_placement_evidence.py
```
Working changes (plus new proposal files):
```text
M docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md
M docs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json
M scenes/main/main.gd
M tests/fixtures/p08-03/checker_fixture.py
M tests/fixtures/p08-03/placement.gd
M tools/check_p08_normal_placement_evidence.py
M tools/test_p08_normal_placement_evidence.py
A docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/RUNTIME_PREVIEW_PROPOSAL.md
A docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/runtime_preview.patch
```
Exact validation commands and initial/retry results are recorded in implementation.json repair17.validation. No Godot/native/full execution or recollection was performed.
@@ -64,73 +64,58 @@
"tests/fixtures/p08-03/r05-reader-checkpoint.json.gz": "27fd3b41834106ccd6a0980dfa2bc91cc19806dd3f552ba04246efb400763233"
},
"actual_local_validation": {
"command": "python3 tools/test_p08_normal_placement_evidence.py",
"tests": 116,
"duration_seconds": 80.606,
"command": "python3 -B tools/test_p08_normal_placement_evidence.py",
"tests": 128,
"duration_seconds": 85.953,
"exit_code": 0,
"failures": 0,
"errors": 0,
"skipped": 0,
"focused_command": "python3 tools/test_p08_normal_placement_evidence.py R09ImageTests R09ApplicabilityTests",
"focused_tests": 18,
"focused_exit_code": 0,
"python_ast_checks": 2,
"diff_check": "passed",
"native_OS_full_regression": "not executed by worker",
"actual_R09_verify_only": "not executed by worker",
"original_capture_hashes": "actual original 138 images checked inside pure Python test; Root re-check pending",
"earlier_attempts": [
"101 tests: helper naming errors, corrected",
"18 focused tests: helper naming errors, corrected",
"18 focused tests: passed with one Root-positive skip before actual bytes were used",
"18 focused tests: passed without skips after actual original hashes were used"
]
"scope": "pure Python only; no acceptance",
"all_commands": "repair17.validation"
},
"conditions": {
"support_connection": {
"implementation": "prepared",
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"local_checker_controls": "128 pure Python tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "historical only; current native recollection pending",
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending",
"accepted": false,
"state": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending"
"state": "blocked pending Root reconciliation and application of runtime_preview.patch; Main display premise gap recorded"
},
"invalid_position_reason": {
"implementation": "prepared",
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"local_checker_controls": "128 pure Python tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "historical only; current native recollection pending",
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending",
"accepted": false,
"state": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending"
"state": "not verified; historical raw-proof read refusal retained. Reviewer permitted task build through --add-dir while --restricted, Read/Grep/Glob and outer sandbox remain. Future independent raw-proof check required."
},
"demolish_rebuild": {
"implementation": "prepared",
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"local_checker_controls": "128 pure Python tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "historical only; current native recollection pending",
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending",
"accepted": false,
"state": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending"
"state": "not verified; historical raw-proof read refusal retained. Reviewer permitted task build through --add-dir while --restricted, Read/Grep/Glob and outer sandbox remain. Future independent raw-proof check required."
},
"pan_zoom_world_identity": {
"implementation": "prepared",
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"local_checker_controls": "128 pure Python tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "historical only; current native recollection pending",
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending",
"accepted": false,
"state": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending"
"state": "not verified; historical raw-proof read refusal retained. Reviewer permitted task build through --add-dir while --restricted, Read/Grep/Glob and outer sandbox remain. Future independent raw-proof check required."
},
"separate_pid_resume": {
"implementation": "prepared",
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
"local_checker_controls": "128 pure Python tests, exit0; no gameplay acceptance",
"actual_Godot_handler": "historical only; current native recollection pending",
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending",
"accepted": false,
"state": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending"
"state": "not verified; historical raw-proof read refusal retained. Reviewer permitted task build through --add-dir while --restricted, Read/Grep/Glob and outer sandbox remain. Future independent raw-proof check required."
}
},
"pending": [
@@ -1812,5 +1797,106 @@
"preview/runtime stair difference": "open",
"coordinator runtime acceptance=false": "open",
"Windows same-distribution": "open"
},
"repair17": {
"revision": 2,
"direction": "repair17 following review 3124b341",
"status": "submitted; no acceptance claimed",
"head": "to be bound by Runner",
"support_connection": "blocked pending Root reconciliation and application of runtime_preview.patch; Main display premise gap recorded",
"conditions_2_to_5": "not verified; historical raw-proof read refusal retained. Reviewer permitted task build through --add-dir while --restricted, Read/Grep/Glob and outer sandbox remain. Future independent raw-proof check required.",
"R09": "historical pins/evidence unrewritten; current R09 missing_connection must fail preview/command disagreement",
"native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"pending": [
"Root reconciliation and application of Runtime preview patch (including Main display premise gap)",
"Root native/OS recollection for changed demolition and support UI after all fixes are combined",
"Root final full175",
"Independent Opus review of all 5 conditions with raw-proof access",
"Integration / current final verification / ledger",
"Windows same-distribution/device (no Mac substitution)"
],
"repair_inference_material_limits": null,
"validation": [
{
"command": "python3 -B tools/test_p08_normal_placement_evidence.py",
"initial_exit_code": 1,
"initial_tests": 128,
"initial_duration_seconds": 92.811,
"initial_failure": "test_preview_partial_state_change reached snapshot-envelope validation; fixed mutation to existing resource field",
"exit_code": 0,
"tests": 128,
"duration_seconds": 85.953
},
{
"command": "python3 -m py_compile tools/check_p08_normal_placement_evidence.py tools/test_p08_normal_placement_evidence.py tests/fixtures/p08-03/checker_fixture.py",
"initial_exit_code": 1,
"initial_failure": "PermissionError on default external PYTHONPYCACHEPREFIX cache directory",
"retry_environment": {
"PYTHONPYCACHEPREFIX": "tests/fixtures/p08-03/.scratch/repair17-pycache"
},
"retry_exact_command": "PYTHONPYCACHEPREFIX=tests/fixtures/p08-03/.scratch/repair17-pycache python3 -m py_compile tools/check_p08_normal_placement_evidence.py tools/test_p08_normal_placement_evidence.py tests/fixtures/p08-03/checker_fixture.py",
"exit_code": 0,
"cache_cleanup": "removed only worker-generated cache"
},
{
"command": "python3 -c \"import json,sys;json.load(open(sys.argv[1]))\" docs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json",
"exit_code": 0
},
{
"command": "git apply --check docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/runtime_preview.patch",
"exit_code": 0,
"applied": false
}
],
"net_diff_command": "git diff --name-status --no-renames 31aa7706d28a92e313c432d5a62e35d19503dcc3 HEAD",
"net_diff_committed": "M\tdocs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/REPAIR12_CONTEXT_GAP.md\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_NATIVE_R09_COLLECTION_PINS.json\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_R09_COLLECTION_AND_PUBLICATION_DIAGNOSIS.json\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_R09_READONLY_IMAGE_DIMENSIONS.json\nM\tdocs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/repair13-local-source-manifest.json\nA\tdocs/plans/original-completion-2026-10-02/execution/p08-03/repair13-local-validation.json\nM\ttools/check_p08_normal_placement_evidence.py\nM\ttools/test_p08_normal_placement_evidence.py\n",
"working_changes": "M\tdocs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md\nM\tdocs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json\nM\tscenes/main/main.gd\nM\ttests/fixtures/p08-03/checker_fixture.py\nM\ttests/fixtures/p08-03/placement.gd\nM\ttools/check_p08_normal_placement_evidence.py\nM\ttools/test_p08_normal_placement_evidence.py\n",
"new_files": [
"runtime-preview-proposal/RUNTIME_PREVIEW_PROPOSAL.md",
"runtime-preview-proposal/runtime_preview.patch"
],
"applied_files": [
"scenes/main/main.gd",
"tools/check_p08_normal_placement_evidence.py",
"tools/test_p08_normal_placement_evidence.py",
"tests/fixtures/p08-03/checker_fixture.py",
"tests/fixtures/p08-03/placement.gd",
"docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md",
"docs/plans/original-completion-2026-10-02/execution/p08-03/implementation.json"
],
"recorded_direction_context_gap": "main.gd:2123-2131 error-code-first rendering does not display the Runtime message; Root reconciliation required; build renderer not changed"
},
"repair17_local_source_sha256": {
"scenes/main/main.gd": "3b0e9d736a26d848700854e7162b3de9b92ac26fc7e26998e65fe3e95f71d8d6",
"tools/check_p08_normal_placement_evidence.py": "f1e090fdb6f6e505b76e0853a22b6b6f7464f1dbf01b4b831bec361fa8141150",
"tools/test_p08_normal_placement_evidence.py": "1b42cb89ae6d00cb6fe8cb978e95ca6bc0e392912ad3b9e904dc34e29495eb63",
"tests/fixtures/p08-03/checker_fixture.py": "576d5dbeeda669dfda7866fb7eb7a7b3011c00f08e070ca12540fd875ab77acd",
"tests/fixtures/p08-03/placement.gd": "0b50217d9d1ddd0549a428135617a310d95e81a9c4a8a029212019b1dfb19ecb",
"docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/runtime_preview.patch": "415360b1f22868c2bf862e4cedd5ce4690104054a43874aa983665089c33575b",
"docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md": "0fade567e5a25c3a2373bf1995ffb84f7eaae218532dabccde52bf257f85564e",
"docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/RUNTIME_PREVIEW_PROPOSAL.md": "fca5c0ded91ff8a17f6b2c98183d7e2991a80b57fcacecf1a5688f61d959892a"
},
"historical_pre_repair17_local_validation": {
"command": "python3 tools/test_p08_normal_placement_evidence.py",
"tests": 116,
"duration_seconds": 80.606,
"exit_code": 0,
"failures": 0,
"errors": 0,
"skipped": 0,
"focused_command": "python3 tools/test_p08_normal_placement_evidence.py R09ImageTests R09ApplicabilityTests",
"focused_tests": 18,
"focused_exit_code": 0,
"python_ast_checks": 2,
"diff_check": "passed",
"native_OS_full_regression": "not executed by worker",
"actual_R09_verify_only": "not executed by worker",
"original_capture_hashes": "actual original 138 images checked inside pure Python test; Root re-check pending",
"earlier_attempts": [
"101 tests: helper naming errors, corrected",
"18 focused tests: helper naming errors, corrected",
"18 focused tests: passed with one Root-positive skip before actual bytes were used",
"18 focused tests: passed without skips after actual original hashes were used"
]
}
}
@@ -0,0 +1,39 @@
# OUTSIDE CURRENT TASK SCOPE — NOT APPLIED — Root reconciles against standing full-game implementation authorization after concrete review; no approval inferred.
## Problem and concrete proposal
At HEAD 7e2368f5c453a3900ba1bb038d7138d25f7eb4fb, guest_stairs at (-8,2,0), without a path at (-8,1,0), has preview can_build=true but submit_command(build_facility) returns VALIDATION_FAILED / STAIR_LANDING_MISSING / 「先に階段の一つ下の階に通路を作ってください。」.
WorldPreview uses ConstructionService alone (scenes/main/world_preview.gd:201-218); Runtime performs additional checks (autoload/simulation_runtime.gd:7576-7615).
runtime_preview.patch is a concrete proposal against this HEAD. It is NOT applied. It changes exactly:
- autoload/simulation_runtime.gd
- scenes/main/world_preview.gd
- tests/transport/test_runtime_build_preview.gd (new)
- tests/transport/test_runtime_build_preview.gd.uid (new)
The private _build_facility_precheck(command_id, payload) returns CommandResult or null. Taking the raw payload rather than eagerly parsed parameters preserves the exact original order: operations gate, definition lookup/NOT_FOUND, unsupported vertical, origin parsing/INVALID_PAYLOAD, stair landing, busy elevator, rotation/INVALID_PAYLOAD. The existing prefix is moved unchanged; execution calls it, then reads the already-validated payload and calls start_construction unchanged. There is no copied domain rule in Main, WorldPreview or the checker.
The public read-only preview_build_facility(definition_id: StringName, origin: Vector3i, rotation: int) runs that helper and merges validate_placement(...).to_dictionary(). A failure forces can_build=false and prepends details.reason (or Code key) plus the exact message. Reasons retain PlacementReport's dictionary shape (code/message/cells/details), not strings. WorldPreview uses the guarded Runtime API, otherwise retains its old fallback, and keeps connection-report augmentation.
## Required reconciliation: recorded Main premise is false
The recorded direction says Main needs no change. Direct source inspection contradicts it: scenes/main/main.gd:2123-2131 selects the first error code and returns 「配置不可: <mapped code>」 before inspecting reason messages. STAIR_LANDING_MISSING is not mapped to the Runtime message. Thus the proposed patch fixes can_build but, by itself, cannot satisfy the specified exact displayed-message equality. No build-status hunk is applied or smuggled into this proposal. Root must reconcile this concrete source gap before applying a complete fix. Condition (1) remains blocked. The green UI is not hidden, recoloured or reworded.
## Purity check (current source; not a native execution)
- autoload/simulation_runtime.gd:7988-8002 _operations_construction_investment_gate reads gate/arrears and constructs a new result only; scripts/operations/operations_coordinator.gd:546-562 reads terminal/arrears/quarantine and returns a fresh dictionary.
- autoload/simulation_runtime.gd:1890-1891 and :4020-4027 sum payroll/upkeep; scripts/staff/staff_service.gd:198 builds a fresh obligations projection; scripts/economy/facility_upkeep_settlement.gd:94-98 sums existing debts.
- autoload/simulation_runtime.gd:6687-6688 _staff_assignment_failure only constructs CommandResult. No command queue, journal, ID allocation or event path is invoked.
- autoload/simulation_runtime.gd:12744-12759 _elevator_shaft_busy/_elevator_touches_module read dictionaries and definition fields. scripts/transport/elevator_service.gd:345-360 get_report duplicates shafts and computes local projection rows.
- scripts/world/path_service.gd:1415-1416 has_cell is a dictionary lookup.
- scripts/construction/construction_service.gd:56-111 validate_placement creates a local PlacementReport; :862-973 fill only that report using read-only world calls; :975-993 checks resource sufficiency. :1136-1144 _can_afford invokes the configured resource query. autoload/game_state.gd:44-48 and scripts/economy/ledger_backed_resource_state.gd:42-48 only read resources.
- scripts/world/world_service.gd:305 onward validate_support builds local structural sets/reports; :122 cells_for_footprint and :194 get_occupant are read queries. scripts/construction/placement_report.gd:36-61 modifies only the new report; :73 to_dictionary returns its projection.
These production dependencies contain no event emission, counters, command-log writes, RNG advance or model mutation in the preview call graph. Mutation begins in ConstructionService.start_construction (:114 onward), which the preview never calls. The helper may return a freshly allocated CommandResult without allocating a command ID; preview never calls submit_command. Purity must also be checked natively through byte-identical typed snapshots after Root applies the proposal; source review is not execution proof.
## Test impact and pending targeted checks
Expected unchanged outcomes: tests/transport/test_runtime_stairs.gd; tests/s1/test_construction_service.gd; tests/mvp/test_original_room_zones.gd; tests/ui/test_main_dashboard.gd; tests/ui/test_pixel_tower.gd. Related fixtures tests/fixtures/p08-03/placement.gd and passive_placement.gd require fresh native evidence after the combined product correction.
The new proposed test asserts:
- failing-cell preview false, STAIR_LANDING_MISSING and exact Runtime message;
- var_to_bytes(create_snapshot()) byte identity before/after preview;
- command reason/message equality at the same cell;
- with diagnostic path at (-8,1,0), preview true and command OK (existing initial reception supplies support);
- operations gate and unsupported-vertical precedence over landing (explicit test doubles isolate ordering, existing domain tests retain real values);
- WorldPreview surfaces the Runtime code.
No GDScript test was run. The patch is checked only with git apply --check. It is a proposal, not acceptance or an applied Runtime change.
@@ -0,0 +1,169 @@
diff --git a/autoload/simulation_runtime.gd b/autoload/simulation_runtime.gd
--- a/autoload/simulation_runtime.gd
+++ b/autoload/simulation_runtime.gd
@@ -7573,18 +7573,33 @@
return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, failure_message)
return CommandResult.succeeded(command.command_id, {&"request_id": request_id, &"reason": &"GUEST_LEFT"})
-func _execute_build_facility(command: SimulationCommand) -> CommandResult:
- var operations_gate := _operations_construction_investment_gate(command.command_id)
+func preview_build_facility(definition_id: StringName, origin: Vector3i, rotation: int) -> Dictionary:
+ var failure := _build_facility_precheck(0, {&"definition_id": definition_id, &"origin": origin, &"rotation": rotation})
+ var definition := definition_registry.get_facility(definition_id)
+ var report: Dictionary = construction_service.validate_placement(definition, origin, rotation).to_dictionary()
+ if failure != null:
+ report[&"can_build"] = false
+ var code := String(failure.details.get(&"reason", CommandResult.Code.keys()[failure.code]))
+ var codes: Array = Array(report.get(&"error_codes", []))
+ var reasons: Array = Array(report.get(&"reasons", []))
+ codes.push_front(code)
+ reasons.push_front({&"code": code, &"message": failure.message, &"cells": [], &"details": failure.details.duplicate(true)})
+ report[&"error_codes"] = codes
+ report[&"reasons"] = reasons
+ return report
+
+func _build_facility_precheck(command_id: int, payload: Dictionary) -> CommandResult:
+ var operations_gate := _operations_construction_investment_gate(command_id)
if operations_gate != null:
return operations_gate
- var definition_id := StringName(command.payload.get(&"definition_id", command.payload.get("definition_id", "")))
+ var definition_id := StringName(payload.get(&"definition_id", payload.get("definition_id", "")))
var definition := definition_registry.get_facility(definition_id)
if definition == null:
- return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Facility definition was not found.")
+ return CommandResult.failed(CommandResult.Code.NOT_FOUND, command_id, "Facility definition was not found.")
if _facility_definition_is_unsupported_vertical_proxy(definition):
return CommandResult.failed(
CommandResult.Code.VALIDATION_FAILED,
- command.command_id,
+ command_id,
"Vertical infrastructure topology is not supported.",
{
&"reason": FacilityInfrastructureProjector.REASON_UNSUPPORTED_VERTICAL_TOPOLOGY,
@@ -7592,18 +7607,28 @@
&"unchanged": true,
}
)
- var origin_value: Variant = _parse_world_cell(command.payload.get(&"origin", command.payload.get("origin", null)))
+ var origin_value: Variant = _parse_world_cell(payload.get(&"origin", payload.get("origin", null)))
if origin_value == null:
- return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "origin must be Vector3i or [x, y, layer].")
+ return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command_id, "origin must be Vector3i or [x, y, layer].")
var origin: Vector3i = origin_value
if definition.id in [&"guest_stairs", &"service_stairs"] and not path_service.has_cell(origin + Vector3i(0, -1, 0)):
- return _staff_assignment_failure(command.command_id, &"STAIR_LANDING_MISSING", "先に階段の一つ下の階に通路を作ってください。")
+ return _staff_assignment_failure(command_id, &"STAIR_LANDING_MISSING", "先に階段の一つ下の階に通路を作ってください。")
for shaft: Dictionary in elevators.get_report()[&"shafts"]:
if _elevator_touches_module(shaft, definition, origin) and _elevator_shaft_busy(shaft):
- return _staff_assignment_failure(command.command_id, &"ELEVATOR_BUSY", "Wait until the shaft is idle before extending it.")
+ return _staff_assignment_failure(command_id, &"ELEVATOR_BUSY", "Wait until the shaft is idle before extending it.")
+ var rotation := int(payload.get(&"rotation", payload.get("rotation", 0)))
+ if rotation not in definition.allowed_rotations:
+ return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command_id, "The selected rotation is not allowed.")
+ return null
+
+func _execute_build_facility(command: SimulationCommand) -> CommandResult:
+ var failure := _build_facility_precheck(command.command_id, command.payload)
+ if failure != null:
+ return failure
+ var definition_id := StringName(command.payload.get(&"definition_id", command.payload.get("definition_id", "")))
+ var definition := definition_registry.get_facility(definition_id)
+ var origin: Vector3i = _parse_world_cell(command.payload.get(&"origin", command.payload.get("origin", null)))
var rotation := int(command.payload.get(&"rotation", command.payload.get("rotation", 0)))
- if rotation not in definition.allowed_rotations:
- return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "The selected rotation is not allowed.")
var result := construction_service.start_construction(
definition,
origin,
diff --git a/scenes/main/world_preview.gd b/scenes/main/world_preview.gd
--- a/scenes/main/world_preview.gd
+++ b/scenes/main/world_preview.gd
@@ -202,6 +202,13 @@
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(
diff --git a/tests/transport/test_runtime_build_preview.gd b/tests/transport/test_runtime_build_preview.gd
new file mode 100644
--- /dev/null
+++ b/tests/transport/test_runtime_build_preview.gd
@@ -0,0 +1,64 @@
+extends SceneTree
+
+var failures := 0
+
+class GatedRuntime extends SimulationRuntimeService:
+ var block_operations := false
+ var block_vertical := false
+ func _operations_construction_investment_gate(command_id: int) -> CommandResult:
+ if block_operations:
+ return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command_id,
+ "Operations currently block construction and investment.", {&"reason": &"ARREARS_REMAIN"})
+ return super._operations_construction_investment_gate(command_id)
+ func _facility_definition_is_unsupported_vertical_proxy(definition: Variant) -> bool:
+ return block_vertical or super._facility_definition_is_unsupported_vertical_proxy(definition)
+
+func _initialize() -> void:
+ call_deferred("_run")
+
+func check(ok: bool, message: String) -> void:
+ if not ok:
+ failures += 1
+ push_error(message)
+
+func _run() -> void:
+ var runtime := GatedRuntime.new()
+ var clock := load("res://autoload/game_clock.gd").new()
+ var state := load("res://autoload/game_state.gd").new()
+ clock.reset(1, 6, 0)
+ state.reset()
+ check(runtime.initialize(clock, state, 90416)[&"valid"], "initialize")
+ runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
+ clock.set_paused(true)
+ var cell := Vector3i(-8, 2, 0)
+ var message := "先に階段の一つ下の階に通路を作ってください。"
+ var before := var_to_bytes(runtime.create_snapshot())
+ var report := runtime.preview_build_facility(&"guest_stairs", cell, 0)
+ check(not report[&"can_build"] and "STAIR_LANDING_MISSING" in report[&"error_codes"], "missing landing preview")
+ check(report[&"reasons"][0][&"message"] == message, "Runtime message")
+ check(before == var_to_bytes(runtime.create_snapshot()), "typed save snapshot byte identity")
+ var view := load("res://scenes/main/world_preview.gd").new()
+ view.set_runtime(runtime)
+ view.set_build_definition(runtime.definition_registry.get_facility(&"guest_stairs"))
+ check("STAIR_LANDING_MISSING" in view.get_build_preview_report(cell)[&"error_codes"], "WorldPreview surfaces Runtime")
+ var refused := runtime.submit_command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": cell})
+ check(refused.details.get(&"reason") == &"STAIR_LANDING_MISSING" and refused.message == message, "command agrees")
+ # Diagnostic path setup; existing reception supplies structural support.
+ var walkable: Array[Vector3i] = runtime.world_service.get_walkable_cells()
+ walkable.append(Vector3i(-8, 1, 0))
+ check(runtime.path_service.rebuild_graph(walkable), "diagnostic landing path")
+ report = runtime.preview_build_facility(&"guest_stairs", cell, 0)
+ var accepted := runtime.submit_command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": cell})
+ check(report[&"can_build"] and accepted.is_success(), "landing positive agreement")
+ runtime.block_operations = true
+ runtime.block_vertical = true
+ report = runtime.preview_build_facility(&"guest_stairs", Vector3i(-8, 4, 0), 0)
+ check(report[&"error_codes"][0] == "ARREARS_REMAIN", "operations precedence")
+ runtime.block_operations = false
+ report = runtime.preview_build_facility(&"guest_stairs", Vector3i(-8, 4, 0), 0)
+ check(report[&"error_codes"][0] == String(FacilityInfrastructureProjector.REASON_UNSUPPORTED_VERTICAL_TOPOLOGY), "vertical precedence")
+ view.free()
+ runtime.free()
+ clock.free()
+ state.free()
+ quit(1 if failures else 0)
diff --git a/tests/transport/test_runtime_build_preview.gd.uid b/tests/transport/test_runtime_build_preview.gd.uid
new file mode 100644
--- /dev/null
+++ b/tests/transport/test_runtime_build_preview.gd.uid
@@ -0,0 +1 @@
+uid://cpxbldpreview