P08-03: 配置の通常操作受入
This commit is contained in:
@@ -457,3 +457,46 @@ Actual R09 --verify-only: integrity pass, acceptance pending.
|
|||||||
- tests/fixtures/p08-03/repair13_images.py: 248a8d73ece3a60e601421fdd2ad53f8f8e971184fb1328c7155a71b8ed4ca53
|
- tests/fixtures/p08-03/repair13_images.py: 248a8d73ece3a60e601421fdd2ad53f8f8e971184fb1328c7155a71b8ed4ca53
|
||||||
- repair13-local-validation.json: f249c76c6d4c044c1a3c33ea09abc99f09a8c0ea6b0ade470d2af205eb4d89af
|
- repair13-local-validation.json: f249c76c6d4c044c1a3c33ea09abc99f09a8c0ea6b0ade470d2af205eb4d89af
|
||||||
- repair13-local-source-manifest.json: 7d5a62155b7053398a5bc55879c9a92a07dcb3c5a0603e92d6f38eefba122ca1
|
- 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"
|
"tests/fixtures/p08-03/r05-reader-checkpoint.json.gz": "27fd3b41834106ccd6a0980dfa2bc91cc19806dd3f552ba04246efb400763233"
|
||||||
},
|
},
|
||||||
"actual_local_validation": {
|
"actual_local_validation": {
|
||||||
"command": "python3 tools/test_p08_normal_placement_evidence.py",
|
"command": "python3 -B tools/test_p08_normal_placement_evidence.py",
|
||||||
"tests": 116,
|
"tests": 128,
|
||||||
"duration_seconds": 80.606,
|
"duration_seconds": 85.953,
|
||||||
"exit_code": 0,
|
"exit_code": 0,
|
||||||
"failures": 0,
|
"scope": "pure Python only; no acceptance",
|
||||||
"errors": 0,
|
"all_commands": "repair17.validation"
|
||||||
"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"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"support_connection": {
|
"support_connection": {
|
||||||
"implementation": "prepared",
|
"implementation": "prepared",
|
||||||
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
|
"local_checker_controls": "128 pure Python 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_Godot_handler": "historical only; current native recollection pending",
|
||||||
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
|
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
|
||||||
"independent_review": "pending",
|
"independent_review": "pending",
|
||||||
"accepted": false,
|
"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": {
|
"invalid_position_reason": {
|
||||||
"implementation": "prepared",
|
"implementation": "prepared",
|
||||||
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
|
"local_checker_controls": "128 pure Python 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_Godot_handler": "historical only; current native recollection pending",
|
||||||
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
|
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
|
||||||
"independent_review": "pending",
|
"independent_review": "pending",
|
||||||
"accepted": false,
|
"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": {
|
"demolish_rebuild": {
|
||||||
"implementation": "prepared",
|
"implementation": "prepared",
|
||||||
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
|
"local_checker_controls": "128 pure Python 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_Godot_handler": "historical only; current native recollection pending",
|
||||||
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
|
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
|
||||||
"independent_review": "pending",
|
"independent_review": "pending",
|
||||||
"accepted": false,
|
"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": {
|
"pan_zoom_world_identity": {
|
||||||
"implementation": "prepared",
|
"implementation": "prepared",
|
||||||
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
|
"local_checker_controls": "128 pure Python 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_Godot_handler": "historical only; current native recollection pending",
|
||||||
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
|
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
|
||||||
"independent_review": "pending",
|
"independent_review": "pending",
|
||||||
"accepted": false,
|
"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": {
|
"separate_pid_resume": {
|
||||||
"implementation": "prepared",
|
"implementation": "prepared",
|
||||||
"local_checker_controls": "116 pure Python checker tests, exit0; no gameplay acceptance",
|
"local_checker_controls": "128 pure Python 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_Godot_handler": "historical only; current native recollection pending",
|
||||||
"actual_native_OS": "Root native evidence collected at 31aa; independent semantic acceptance pending; Root final full175 regression pending; integration pending",
|
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
|
||||||
"independent_review": "pending",
|
"independent_review": "pending",
|
||||||
"accepted": false,
|
"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": [
|
"pending": [
|
||||||
@@ -1812,5 +1797,106 @@
|
|||||||
"preview/runtime stair difference": "open",
|
"preview/runtime stair difference": "open",
|
||||||
"coordinator runtime acceptance=false": "open",
|
"coordinator runtime acceptance=false": "open",
|
||||||
"Windows same-distribution": "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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
@@ -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.
|
||||||
+169
@@ -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
|
||||||
+20
-10
@@ -2781,17 +2781,17 @@ func _phase_name(phase: GameClock.Phase) -> String:
|
|||||||
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:
|
func _on_resource_changed(resource_name: StringName, value: int) -> void:
|
||||||
if not resource_labels.has(resource_name):
|
if not resource_labels.has(resource_name):
|
||||||
return
|
return
|
||||||
var titles := {
|
|
||||||
&"gold": "所持金",
|
|
||||||
&"mana": "マナ",
|
|
||||||
&"materials": "資材",
|
|
||||||
&"faith": "信仰",
|
|
||||||
}
|
|
||||||
var label := resource_labels[resource_name] as Label
|
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":
|
if resource_name == &"mana":
|
||||||
label.mouse_filter = Control.MOUSE_FILTER_STOP
|
label.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
label.tooltip_text = "残量0で全館停止。接客・調理・搬入・エレベーターと新規入館が停止します。\n残量と配電が回復すると再開します。破壊・摘発による休止は解除されません。"
|
label.tooltip_text = "残量0で全館停止。接客・調理・搬入・エレベーターと新規入館が停止します。\n残量と配電が回復すると再開します。破壊・摘発による休止は解除されません。"
|
||||||
@@ -3169,12 +3169,22 @@ func _confirm_demolition() -> void:
|
|||||||
# Paused play returns execution synchronously; running play returns acceptance.
|
# Paused play returns execution synchronously; running play returns acceptance.
|
||||||
if result.is_success() and _runtime.command_queue.get_processed_result(result.command_id) == null:
|
if result.is_success() and _runtime.command_queue.get_processed_result(result.command_id) == null:
|
||||||
_pending_demolition_commands[result.command_id] = target
|
_pending_demolition_commands[result.command_id] = target
|
||||||
|
_set_status("撤去を受け付けました", true)
|
||||||
|
return
|
||||||
_show_demolition_result(result)
|
_show_demolition_result(result)
|
||||||
|
|
||||||
|
|
||||||
func _show_demolition_result(result: CommandResult) -> void:
|
func _show_demolition_result(result: CommandResult) -> void:
|
||||||
var code := String(CommandResult.Code.keys()[result.code])
|
if result.is_success():
|
||||||
var reason := String(result.details.get(&"reason", ""))
|
var refund: Dictionary = result.details.get(&"refund", {})
|
||||||
_set_status("%s | %s | %s" % [code, reason, result.message], result.is_success())
|
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()
|
_refresh_selected_entity()
|
||||||
world_preview.queue_redraw()
|
world_preview.queue_redraw()
|
||||||
|
|||||||
+3
-3
@@ -239,8 +239,8 @@ def make(base, source, checker):
|
|||||||
row.update(expected=code, status=message)
|
row.update(expected=code, status=message)
|
||||||
elif label == 'missing_connection':
|
elif label == 'missing_connection':
|
||||||
row['expected'] = 'STAIR_LANDING_MISSING'
|
row['expected'] = 'STAIR_LANDING_MISSING'
|
||||||
row['results'] = [result('VALIDATION_FAILED', {'reason': row['expected']}, 'missing landing')]
|
report.update(can_build=False, error_codes=[row['expected']], reasons=[{'code':row['expected'], 'message':'先に階段の一つ下の階に通路を作ってください。', 'cells':[], 'details':{'reason':row['expected']}}])
|
||||||
row['status'] = '操作に失敗しました: missing landing'
|
row['status'] = report['reasons'][0]['message']
|
||||||
elif label == 'cancel_demolition':
|
elif label == 'cancel_demolition':
|
||||||
row.update(kind='cancel', status='撤去を取り消しました。')
|
row.update(kind='cancel', status='撤去を取り消しました。')
|
||||||
elif label in ('demolition', 'refused_demolition'):
|
elif label in ('demolition', 'refused_demolition'):
|
||||||
@@ -257,7 +257,7 @@ def make(base, source, checker):
|
|||||||
runtime['facilities']['instances'] = [v for v in runtime['facilities']['instances'] if v['instance_id'] != fid]
|
runtime['facilities']['instances'] = [v for v in runtime['facilities']['instances'] if v['instance_id'] != fid]
|
||||||
runtime['world']['occupancies'] = [v for v in runtime['world']['occupancies'] if v['entity_id'] != fid]
|
runtime['world']['occupancies'] = [v for v in runtime['world']['occupancies'] if v['entity_id'] != fid]
|
||||||
r = row['results'][0]
|
r = row['results'][0]
|
||||||
row['status'] = '%s | %s | %s' % (r['code'], r['details'].get('reason', ''), r['message'])
|
row['status'] = '撤去できません: '+r['message'] if row['expected'] else '撤去しました(返金: 資材 2)'
|
||||||
elif label == 'pan_zoom':
|
elif label == 'pan_zoom':
|
||||||
row.update(kind='camera', original_local=[1, 2], ctrl_pressed=False,
|
row.update(kind='camera', original_local=[1, 2], ctrl_pressed=False,
|
||||||
pan_before=[0,0], pan_after=[0,10],
|
pan_before=[0,0], pan_after=[0,10],
|
||||||
|
|||||||
Vendored
+1
-1
@@ -122,7 +122,7 @@ func build(id: StringName, cell: Vector3i, label: String, expected: String = "")
|
|||||||
if expected.is_empty():
|
if expected.is_empty():
|
||||||
expect(not results.is_empty() and results.back().code == "OK", label + " accepted")
|
expect(not results.is_empty() and results.back().code == "OK", label + " accepted")
|
||||||
elif expected == "STAIR_LANDING_MISSING":
|
elif expected == "STAIR_LANDING_MISSING":
|
||||||
expect(not results.is_empty() and results.back().details.get("reason") == expected, label + " domain refusal")
|
expect(not report.can_build and expected in report.error_codes and results.is_empty(), label + " preview refusal")
|
||||||
else:
|
else:
|
||||||
expect(expected in report.error_codes and results.is_empty(), label + " preview refusal")
|
expect(expected in report.error_codes and results.is_empty(), label + " preview refusal")
|
||||||
var row := {"id": label, "kind": "build", "definition": String(id), "expected": expected,
|
var row := {"id": label, "kind": "build", "definition": String(id), "expected": expected,
|
||||||
|
|||||||
@@ -559,11 +559,20 @@ def action_gate(row, typed, root=HERE.parent):
|
|||||||
expected_reason = REASONS[label][0] if label in REASONS else {'missing_connection':'STAIR_LANDING_MISSING', 'refused_demolition':'STAIR_LANDING_IN_USE'}.get(label, '')
|
expected_reason = REASONS[label][0] if label in REASONS else {'missing_connection':'STAIR_LANDING_MISSING', 'refused_demolition':'STAIR_LANDING_IN_USE'}.get(label, '')
|
||||||
require(row.get('expected', expected_reason) == expected_reason, 'required refusal reason changed')
|
require(row.get('expected', expected_reason) == expected_reason, 'required refusal reason changed')
|
||||||
results = row.get('results', [])
|
results = row.get('results', [])
|
||||||
|
if kind == 'build' and results and results[0]['code'] != 'OK':
|
||||||
|
reason = results[0]['details'].get('reason', results[0]['code'])
|
||||||
|
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||||
|
require(reason in row['report']['error_codes'], 'preview reason differs from Runtime reason')
|
||||||
if label in REASONS:
|
if label in REASONS:
|
||||||
code, message = REASONS[label]
|
code, message = REASONS[label]
|
||||||
require(row['report']['error_codes'][0] == code and row['status'] == message, 'reason mismatch')
|
require(row['report']['error_codes'][0] == code and row['status'] == message, 'reason mismatch')
|
||||||
require(not row['report']['can_build'] and not results, 'preview rejection bypassed')
|
require(not row['report']['can_build'] and not results, 'preview rejection bypassed')
|
||||||
preserve(before, after, results)
|
preserve(before, after, results)
|
||||||
|
elif label == 'missing_connection' and not results:
|
||||||
|
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||||
|
require('STAIR_LANDING_MISSING' in row['report']['error_codes'], 'preview reason differs from Runtime reason')
|
||||||
|
require(row['report']['reasons'] and row['status'] == row['report']['reasons'][0]['message'], 'connection reason mismatch')
|
||||||
|
preserve(before, after, [])
|
||||||
elif kind == 'cancel':
|
elif kind == 'cancel':
|
||||||
require(not results and row['status'] == '撤去を取り消しました。', 'cancel submitted command')
|
require(not results and row['status'] == '撤去を取り消しました。', 'cancel submitted command')
|
||||||
preserve(before, after, results)
|
preserve(before, after, results)
|
||||||
@@ -584,7 +593,16 @@ def action_gate(row, typed, root=HERE.parent):
|
|||||||
else:
|
else:
|
||||||
require(result['code'] == 'OK', 'positive rejected')
|
require(result['code'] == 'OK', 'positive rejected')
|
||||||
if kind == 'demolish':
|
if kind == 'demolish':
|
||||||
require(row['status'] == '%s | %s | %s' % (result['code'], result['details'].get('reason', ''), result['message']), 'displayed domain reason mismatch')
|
tokens = (result['code'], result['details'].get('reason', ''), ' | ')
|
||||||
|
require(not any(token and token in row['status'] for token in tokens), 'raw demolition token in status')
|
||||||
|
if row['expected']:
|
||||||
|
require(row['status'] == '撤去できません: '+result['message'], 'demolition refusal message mismatch')
|
||||||
|
else:
|
||||||
|
titles = {'gold':'所持金', 'mana':'マナ', 'materials':'資材', 'faith':'信仰'}
|
||||||
|
refund = result['details']['refund']
|
||||||
|
amounts = ['%s %d' % (titles.get(k, k), int(refund[k])) for k in sorted(refund) if int(refund[k]) != 0]
|
||||||
|
expected_status = '撤去しました(返金: '+ '、'.join(amounts)+')' if amounts else '撤去しました(返金なし)'
|
||||||
|
require(row['status'] == expected_status, 'demolition refund display mismatch')
|
||||||
if not row['expected']:
|
if not row['expected']:
|
||||||
fid = row['instance_id']
|
fid = row['instance_id']
|
||||||
old = keyed(a['systems']['runtime']['construction']['facilities'], 'instance_id')
|
old = keyed(a['systems']['runtime']['construction']['facilities'], 'instance_id')
|
||||||
@@ -606,7 +624,7 @@ def action_gate(row, typed, root=HERE.parent):
|
|||||||
require(row['report']['connection']['status'] == 'CONNECTED', 'positive connection absent')
|
require(row['report']['connection']['status'] == 'CONNECTED', 'positive connection absent')
|
||||||
if label == 'missing_connection':
|
if label == 'missing_connection':
|
||||||
require(row['status'] == '操作に失敗しました: '+result['message'], 'connection reason mismatch')
|
require(row['status'] == '操作に失敗しました: '+result['message'], 'connection reason mismatch')
|
||||||
require(row['report']['can_build'], 'connection refusal did not reach Runtime')
|
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||||
|
|
||||||
def check(project_root, evidence_dir):
|
def check(project_root, evidence_dir):
|
||||||
root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve()
|
root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve()
|
||||||
|
|||||||
@@ -98,6 +98,83 @@ class EvidenceTests(unittest.TestCase):
|
|||||||
def test_positive_whole_checker(self):
|
def test_positive_whole_checker(self):
|
||||||
self.assertEqual([], C.check(self.root, self.out))
|
self.assertEqual([], C.check(self.root, self.out))
|
||||||
|
|
||||||
|
def action_row(self, label):
|
||||||
|
rows = C.load(self.out/'pair/writer-actions.json')['actions']
|
||||||
|
return copy.deepcopy(next(row for row in rows if row['id'] == label))
|
||||||
|
|
||||||
|
def action_check(self, row, message=None, mutate_state=None):
|
||||||
|
def typed(name):
|
||||||
|
node = C.load(self.out/('pair/'+name+'.json'))
|
||||||
|
if mutate_state and name == row['after']:
|
||||||
|
mutate_state(node)
|
||||||
|
return node
|
||||||
|
if message:
|
||||||
|
with self.assertRaisesRegex(ValueError, message):
|
||||||
|
C.action_gate(row, typed, self.root)
|
||||||
|
else:
|
||||||
|
C.action_gate(row, typed, self.root)
|
||||||
|
|
||||||
|
def test_agreeing_preview_refusal(self):
|
||||||
|
row = self.action_row('missing_connection')
|
||||||
|
self.assertEqual(row['status'], row['report']['reasons'][0]['message'])
|
||||||
|
self.action_check(row)
|
||||||
|
|
||||||
|
def test_preview_command_disagreement(self):
|
||||||
|
row = self.action_row('missing_connection')
|
||||||
|
row['report']['can_build'] = True
|
||||||
|
row['results'] = [{'code':'VALIDATION_FAILED', 'details':{'reason':'STAIR_LANDING_MISSING'}, 'message':row['status']}]
|
||||||
|
self.action_check(row, 'preview/command disagreement')
|
||||||
|
|
||||||
|
def test_preview_runtime_reason_difference(self):
|
||||||
|
row = self.action_row('missing_connection')
|
||||||
|
row['report']['error_codes'] = ['OVERLAP']
|
||||||
|
row['results'] = [{'code':'VALIDATION_FAILED', 'details':{'reason':'STAIR_LANDING_MISSING'}, 'message':row['status']}]
|
||||||
|
self.action_check(row, 'preview reason differs from Runtime reason')
|
||||||
|
|
||||||
|
def test_preview_partial_state_change(self):
|
||||||
|
self.action_check(self.action_row('missing_connection'), 'cancel/preview partially applied',
|
||||||
|
lambda node: C.typed_member(node, 'game_state', 'resources', 'materials').update(text='999'))
|
||||||
|
|
||||||
|
def test_readable_demolition_success(self):
|
||||||
|
row = self.action_row('demolition')
|
||||||
|
self.assertEqual('撤去しました(返金: 資材 2)', row['status'])
|
||||||
|
self.action_check(row)
|
||||||
|
|
||||||
|
def test_demolition_missing_refund_amount(self):
|
||||||
|
row = self.action_row('demolition'); row['status'] = '撤去しました(返金: 資材)'
|
||||||
|
self.action_check(row, 'demolition refund display mismatch')
|
||||||
|
|
||||||
|
def test_demolition_wrong_refund_amount(self):
|
||||||
|
row = self.action_row('demolition'); row['status'] = '撤去しました(返金: 資材 3)'
|
||||||
|
self.action_check(row, 'demolition refund display mismatch')
|
||||||
|
|
||||||
|
def test_demolition_raw_enum_status(self):
|
||||||
|
row = self.action_row('demolition'); row['status'] = 'OK | |'
|
||||||
|
self.action_check(row, 'raw demolition token in status')
|
||||||
|
|
||||||
|
def test_readable_demolition_refusal(self):
|
||||||
|
row = self.action_row('refused_demolition')
|
||||||
|
self.assertEqual('STAIR_LANDING_IN_USE', row['results'][0]['details']['reason'])
|
||||||
|
self.assertEqual('撤去できません: '+row['results'][0]['message'], row['status'])
|
||||||
|
self.action_check(row)
|
||||||
|
|
||||||
|
def test_demolition_raw_reason_status(self):
|
||||||
|
row = self.action_row('refused_demolition'); row['status'] += ' STAIR_LANDING_IN_USE'
|
||||||
|
self.action_check(row, 'raw demolition token in status')
|
||||||
|
|
||||||
|
def test_demolition_altered_refusal_message(self):
|
||||||
|
for status in ('撤去できません', '撤去できません: altered'):
|
||||||
|
row = self.action_row('refused_demolition'); row['status'] = status
|
||||||
|
self.action_check(row, 'demolition refusal message mismatch')
|
||||||
|
|
||||||
|
def test_demolition_raw_reason_mismatch(self):
|
||||||
|
row = self.action_row('refused_demolition')
|
||||||
|
row['results'][0]['details']['reason'] = 'OTHER'
|
||||||
|
def mutate(node):
|
||||||
|
items = C.typed_member(node, 'systems', 'runtime', 'command_queue', 'processed')['items']
|
||||||
|
items[-1] = F.typed(row['results'][0])
|
||||||
|
self.action_check(row, 'reason mismatch', mutate)
|
||||||
|
|
||||||
def test_verify_only_cli_uses_build_output_directory(self):
|
def test_verify_only_cli_uses_build_output_directory(self):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
with patch.dict(os.environ, {'BUILD_OUTPUT_DIR':str(self.out.parent)}), contextlib.redirect_stdout(output):
|
with patch.dict(os.environ, {'BUILD_OUTPUT_DIR':str(self.out.parent)}), contextlib.redirect_stdout(output):
|
||||||
|
|||||||
Reference in New Issue
Block a user