P08-03: 配置の通常操作受入
This commit is contained in:
+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
|
||||
Reference in New Issue
Block a user