extends SceneTree
## GATE — Enter confirms the active tool even when a dock control holds the keyboard
## (hyper-games #109, 2026-10-12: "when scaling, pressing enter should apply the scale,
## currently you have to manually click SCALE in the editor modal").
##
## gate_scale K3 (now a real key into the map) passes, so Enter dies BEFORE the map:
## through the viewport, a focused Control gets every key first. Each case below sends
## Enter the way the game receives it — Viewport.push_input — not into the map directly.
##
## F0 control: nothing focused — Enter closes the tool and writes one brush.scale
## F1 the Outliner's Tree focused (you clicked a row to select the brush; BuildDock
## leaves a Tree FOCUS_CLICK for inline rename) — the Tree took Enter as
## "activate row" and the tool never saw it
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_confirm_focus.gd
var _frame := 0
var _map: Node = null
var _level: Node = null
var _persist: Node = null
var fails := 0
var checks := 0
var _step := 0
var _brush := -1
var _nrec := 0
func _p(c: bool, m: String, got: Variant = "") -> void:
checks += 1
if not c:
fails += 1
print(" %-66s %s %s" % [m, "OK" if c else "**FAIL**", str(got)])
func _key(code: int) -> InputEventKey:
var e := InputEventKey.new()
e.physical_keycode = code
e.keycode = code
e.pressed = true
return e
func _release(code: int) -> InputEventKey:
var e := _key(code)
e.pressed = false
return e
func _box(lo: Vector3, hi: Vector3) -> Array:
return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]
func _count() -> int:
var n := 0
for r: Dictionary in (_persist.records() as Array):
if String(r.get("kind", "")) == "brush.scale":
n += 1
return n
func _press(code: int) -> void:
root.push_input(_key(code))
root.push_input(_release(code))
func _process(_d: float) -> bool:
_frame += 1
if _frame == 1:
root.get_node("/root/GameMode").set_mode("build")
var keet: Node = root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
var gs: Node = root.get_node_or_null("/root/GameState")
if gs != null:
gs.route("world:level:*", 1, "peer")
_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
root.add_child(_map)
return false
if _frame < 30:
return false
# One case per few frames: push_input is delivered on the next frame.
if _frame % 4 != 0:
return false
match _step:
0: _setup()
1: _start_case(null) # F0
2: _check("F0 control: nothing focused")
3: _start_case("tree") # F1
4: _check("F1 the Outliner's Tree focused")
_:
if checks < 4:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE confirm_focus: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
quit(0 if fails == 0 else 1)
return true
_step += 1
return false
func _setup() -> void:
_level = _map.get("level")
_persist = _map.get("_persist")
var dot: String = _persist.record("brush.create", {"planes": _box(Vector3(0, 0, 0), Vector3(2, 1, 1)),
"proj": [], "material_id": 0, "level_id": 0, "sky": false})
_persist.refold()
_brush = _level.brush_by_key(dot)
func _start_case(focus: Variant) -> void:
var vp_focus := root.gui_get_focus_owner()
if vp_focus != null:
vp_focus.release_focus()
_map.select_brushes(PackedInt32Array([_brush]))
_press(KEY_S) # scale is S since keymap step 3
_nrec = _count()
# Grow one step through the tool directly — the case is about Enter, not ].
var stack: Node = _map.call("_ensure_ops")
if stack.active != null:
stack.active.set_tool_setting("steps", 1)
stack.active.update_preview()
if focus == "tree":
var panel: Node = _map.get("_outliner")
var tree: Tree = panel.get("_tree") if panel != null else null
# The REAL focus mode — the dock's rule set it; the gate does not change it.
print(" [case tree] focus_mode %d (FOCUS_CLICK=%d)" % [tree.focus_mode if tree else -1, Control.FOCUS_CLICK])
if tree != null:
tree.grab_focus()
print(" [case %s] focus owner: %s" % [str(focus), str(root.gui_get_focus_owner())])
root.push_input(_key(KEY_ENTER))
root.push_input(_release(KEY_ENTER))
func _check(label: String) -> void:
var stack: Node = _map.call("_ensure_ops")
var wrote := _count() - _nrec
_p(stack.active == null and wrote == 1, label + ": one Enter confirms",
"active=%s wrote=%d" % [str(stack.active), wrote])
if stack.active != null:
stack.cancel() if stack.has_method("cancel") else stack.handle_escape()
_persist.undo_gesture(_persist.newest_live_gesture()) if wrote > 0 else null
_persist.refold()
_p(root.gui_get_focus_owner() == null or not (root.gui_get_focus_owner() is LineEdit),
label + ": the keyboard is back with the game", str(root.gui_get_focus_owner()))