P08-03: 配置の通常操作受入
This commit is contained in:
+21
-2
@@ -183,6 +183,8 @@ def make(base, source, checker):
|
||||
runtime['command_queue']['processed'].append(copy.deepcopy(row))
|
||||
return row
|
||||
|
||||
os_sequences = {}
|
||||
|
||||
def os_meta(row, pid):
|
||||
row['input_method'] = method
|
||||
if method != 'native_os': return
|
||||
@@ -200,11 +202,28 @@ def make(base, source, checker):
|
||||
requests.append({'action':'button','node':'/root/Main/ManualSaveButton'})
|
||||
requests += [{'action':'wheel','ctrl':False,'move_mode':True}]
|
||||
requests += [{'action':'wheel','ctrl':False,'move_mode':False} for _ in range(18)]
|
||||
if row['id'] == 'supported_connected':
|
||||
geometry = {'target_rect':[120,0,20,20], 'target_screen_rect':[120,0,20,20],
|
||||
'palette_rect':[0,0,100,30], 'thumb_rect':[0,20,50,10],
|
||||
'viewport_to_screen':[1,0,0,1,0,0],
|
||||
'bar':{'rect':[0,20,100,10], 'visible':True, 'min_value':0,
|
||||
'max_value':200, 'page':100, 'value':0}}
|
||||
requests = [dict(geometry, action='reveal_palette', node='/root/Main/Palette/Target',
|
||||
direction='right', scroll_before=0),
|
||||
{'action':'button','node':'/root/Main/Palette/Target'}] + requests
|
||||
row['requests'] = []
|
||||
for sequence, request in enumerate(requests, 1):
|
||||
prefix = directory+'/'+row['id']+'-'+str(sequence)+'/'
|
||||
for local_sequence, request in enumerate(requests, 1):
|
||||
sequence = os_sequences.get(pid, 0)+1
|
||||
os_sequences[pid] = sequence
|
||||
prefix = directory+'/'+row['id']+'-'+str(local_sequence)+'/'
|
||||
request.update(target_pid=pid, sequence=sequence)
|
||||
put(out/(prefix+'request.json'), request)
|
||||
if request['action'] == 'reveal_palette':
|
||||
after = copy.deepcopy(geometry)
|
||||
after.update(target_rect=[60,0,20,20], target_screen_rect=[60,0,20,20],
|
||||
thumb_rect=[30,20,50,10], scroll_after=60, visible_after=True)
|
||||
after['bar']['value'] = 60
|
||||
put(out/(prefix+'reveal.json'), after)
|
||||
events = [{'synthetic':True}]
|
||||
if request['action'] == 'cell':
|
||||
events = [{'synthetic':True, 'button':1, 'pressed':True,
|
||||
|
||||
+90
-6
@@ -3,6 +3,10 @@ extends "res://tests/fixtures/p08-03/placement.gd"
|
||||
## Shared parent contains the handler endpoints, but every such endpoint below
|
||||
## is overridden; this class never emits UI signals or invokes product handlers.
|
||||
var receipts: Array = []
|
||||
var terminated := false
|
||||
var terminal_record: Dictionary = {}
|
||||
var terminal_cause_recorded := false
|
||||
var reveal_rows: Array = []
|
||||
var all_requests: Array = []
|
||||
var input_observer: Node
|
||||
var last_dispatch: Dictionary = {}
|
||||
@@ -21,7 +25,29 @@ class InputObserver extends Node:
|
||||
row["dispatch"] = endpoint.dispatch_position(event.position)
|
||||
events.append(row)
|
||||
|
||||
func expect(ok: bool, message: String) -> void:
|
||||
if terminated and ok:
|
||||
return
|
||||
if terminated and terminal_cause_recorded:
|
||||
if not ok:
|
||||
terminal_record["suppressed_after_terminal"] += 1
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
return
|
||||
if terminated and not ok:
|
||||
terminal_cause_recorded = true
|
||||
super.expect(ok, message)
|
||||
|
||||
func terminate(stem: String, reason: String) -> void:
|
||||
terminated = true
|
||||
terminal_record = {"pid": OS.get_process_id(), "role": role, "sequence": serial,
|
||||
"stem": stem, "reason": reason, "suppressed_after_terminal": 0}
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
expect(false, reason)
|
||||
tree.quit(1)
|
||||
|
||||
func request(action: Dictionary) -> void:
|
||||
if terminated:
|
||||
return
|
||||
serial += 1
|
||||
var stem := "%s-request-%04d" % [role, serial]
|
||||
var folder := directory.path_join(stem)
|
||||
@@ -40,13 +66,11 @@ func request(action: Dictionary) -> void:
|
||||
while not FileAccess.file_exists(path) and Time.get_ticks_msec() < deadline:
|
||||
await tree.process_frame
|
||||
if not FileAccess.file_exists(path):
|
||||
expect(false, "Root CUA acknowledgement timeout")
|
||||
tree.quit(1)
|
||||
terminate(stem, "Root CUA acknowledgement timeout")
|
||||
return
|
||||
var ack: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
|
||||
if not ack is Dictionary or ack.get("target_pid") != OS.get_process_id() or ack.get("sequence") != serial or ack.get("request_sha256") != FileAccess.get_sha256(folder.path_join("request.json")):
|
||||
expect(false, "Root CUA acknowledgement identity/hash")
|
||||
tree.quit(1)
|
||||
terminate(stem, "Root CUA acknowledgement identity/hash")
|
||||
return
|
||||
await frames()
|
||||
for event: Dictionary in input_observer.events.slice(first_event):
|
||||
@@ -59,6 +83,8 @@ func request(action: Dictionary) -> void:
|
||||
all_requests.append("os-pair/" + stem + "/ack.json")
|
||||
|
||||
func annotate(row: Dictionary) -> void:
|
||||
row["reveal"] = reveal_rows.duplicate(true)
|
||||
reveal_rows.clear()
|
||||
row["input_method"] = "native_os"
|
||||
row["requests"] = receipts.duplicate()
|
||||
receipts.clear()
|
||||
@@ -121,11 +147,57 @@ func save_initial_fixture() -> void:
|
||||
# Root loaded it using the normal menu. No model/setup mutation here.
|
||||
pass
|
||||
|
||||
func rect_values(rect: Rect2) -> Array:
|
||||
return [rect.position.x, rect.position.y, rect.size.x, rect.size.y]
|
||||
|
||||
func control_rect(node: Control, screen: bool = false) -> Rect2:
|
||||
var transform := node.get_screen_transform() if screen else node.get_global_transform_with_canvas()
|
||||
return transform * Rect2(Vector2.ZERO, node.size)
|
||||
|
||||
func reveal_geometry(node: Control) -> Dictionary:
|
||||
var bar: HScrollBar = main.palette_scroll.get_h_scroll_bar()
|
||||
var bar_rect := control_rect(bar)
|
||||
var span := bar.max_value - bar.min_value
|
||||
var thumb := bar_rect
|
||||
if span > 0:
|
||||
thumb.size.x = bar_rect.size.x * bar.page / span
|
||||
thumb.position.x += bar_rect.size.x * (bar.value - bar.min_value) / span
|
||||
return {"target_rect": rect_values(control_rect(node)), "target_screen_rect": rect_values(control_rect(node, true)),
|
||||
"palette_rect": rect_values(main.palette_scroll.get_global_rect()),
|
||||
"bar": {"rect": rect_values(bar_rect), "visible": bar.is_visible_in_tree(),
|
||||
"min_value": bar.min_value, "max_value": bar.max_value, "page": bar.page, "value": bar.value},
|
||||
"thumb_rect": rect_values(thumb), "viewport_to_screen": point(node)["viewport_to_screen"]}
|
||||
|
||||
func reveal(node: Control) -> void:
|
||||
await request({"action": "reveal_palette", "node": String(node.get_path()),
|
||||
"instructions": "Scroll the normal palette until this button is visible. Do not activate it yet."})
|
||||
if terminated:
|
||||
return
|
||||
var palette: Rect2 = main.palette_scroll.get_global_rect()
|
||||
var target_rect := control_rect(node)
|
||||
if palette.encloses(target_rect):
|
||||
reveal_rows.append({"node": String(node.get_path()), "direction": "none"})
|
||||
return
|
||||
var direction := "right" if target_rect.end.x > palette.end.x else "left"
|
||||
var before: int = main.palette_scroll.scroll_horizontal
|
||||
var action := reveal_geometry(node)
|
||||
action.merge({"action": "reveal_palette", "node": String(node.get_path()), "direction": direction,
|
||||
"scroll_before": before, "instructions": "Use the normal horizontal scrollbar to reveal this palette button without activating it."})
|
||||
await request(action)
|
||||
if terminated:
|
||||
return
|
||||
await frames()
|
||||
var after := reveal_geometry(node)
|
||||
var visible_after: bool = main.palette_scroll.get_global_rect().encloses(control_rect(node))
|
||||
after.merge({"scroll_after": main.palette_scroll.scroll_horizontal, "visible_after": visible_after})
|
||||
var stem := "%s-request-%04d" % [role, serial]
|
||||
json_file(stem + "/reveal.json", after)
|
||||
reveal_rows.append(action)
|
||||
var moved: bool = main.palette_scroll.scroll_horizontal > before if direction == "right" else main.palette_scroll.scroll_horizontal < before
|
||||
if not visible_after or not moved:
|
||||
terminate(stem, "palette target not visible after OS reveal")
|
||||
|
||||
func button(node: BaseButton) -> void:
|
||||
if terminated:
|
||||
return
|
||||
expect(node != null and not node.disabled, "available production button")
|
||||
if node == null or node.disabled:
|
||||
return
|
||||
@@ -143,6 +215,8 @@ func button(node: BaseButton) -> void:
|
||||
input_observer = old_observer
|
||||
|
||||
func click(cell: Vector3i) -> Dictionary:
|
||||
if terminated:
|
||||
return {}
|
||||
var view: WorldPreview = main.world_preview
|
||||
for attempt: int in range(8):
|
||||
if Rect2(Vector2.ZERO, view.size).has_point(view.cell_to_canvas(cell)):
|
||||
@@ -150,6 +224,8 @@ func click(cell: Vector3i) -> Dictionary:
|
||||
var framing := point(view)
|
||||
framing.merge({"action": "framing", "world_cell": vector(cell), "instructions": "Use ordinary zoom-out at the preview center until the requested world cell is visible; coordinates will be recomputed after ack."})
|
||||
await request(framing)
|
||||
if terminated:
|
||||
return {}
|
||||
expect(Rect2(Vector2.ZERO, view.size).has_point(view.cell_to_canvas(cell)), "OS target visible after normal framing")
|
||||
var local := view.cell_to_canvas(cell)
|
||||
var viewport := view.get_global_transform_with_canvas() * local
|
||||
@@ -163,6 +239,8 @@ func click(cell: Vector3i) -> Dictionary:
|
||||
"guide_rect": [guide_rect.position.x, guide_rect.position.y, guide_rect.size.x, guide_rect.size.y],
|
||||
"guide_visible": main.opening_guide.visible,
|
||||
"instructions": "Click screen coordinates derived from viewport_to_screen (includes canvas stretch and window translation). Retain actual CUA coordinates and screenshots. If clipped or intercepted, recollect; never substitute fixed pixels."})
|
||||
if terminated:
|
||||
return {}
|
||||
var selected: Array = last_dispatch.get("world_cell", [])
|
||||
expect(selected == vector(cell), "observed OS selected cell")
|
||||
var occupant: StringName = runtime.world_service.get_occupant(cell)
|
||||
@@ -202,18 +280,24 @@ func vector2(value: Vector2) -> Array:
|
||||
return [value.x, value.y]
|
||||
|
||||
func wheel(event: InputEventMouseButton, move_mode: bool) -> void:
|
||||
if terminated:
|
||||
return
|
||||
var action := point(main.world_preview)
|
||||
action.merge({"action": "wheel", "direction": "up" if event.button_index == MOUSE_BUTTON_WHEEL_UP else "down",
|
||||
"ctrl": false, "move_mode": move_mode, "instructions": "Send one ordinary wheel step at the preview center without Ctrl; retain the CUA receipt."})
|
||||
await request(action)
|
||||
|
||||
func complete_build(id: StringName) -> void:
|
||||
if terminated:
|
||||
return
|
||||
await request({"action": "complete_construction", "definition": String(id),
|
||||
"minutes": runtime.definition_registry.get_facility(id).build_minutes,
|
||||
"instructions": "Use normal speed/play controls, wait for construction completion, then pause. No injected clock or model changes."})
|
||||
expect(clock.paused, "Root paused after ordinary construction progression")
|
||||
|
||||
func load_menu(menu: MenuButton) -> void:
|
||||
if terminated:
|
||||
return
|
||||
var action := point(menu)
|
||||
action.merge({"action": "load_menu", "item_id": 0, "instructions": "Open this normal menu and choose manual load (item 0)."})
|
||||
await request(action)
|
||||
|
||||
Reference in New Issue
Block a user