extends SceneTree
## GATE — editor redesign E0 foundations (docs/editor-redesign.md §1-2, narrow fly rule):
## 1. text_input: a focused text field is an EXCLUSIVE context — no builder key fires,
## and movement stops reading the keyboard (typing "wasd" in the texture search or
## chat must not walk you).
## 2. tool: an active operator pushes it; while it is up and RMB is NOT held, the fly
## camera does not own Ctrl / Shift / Space. RMB held = it does again. No tool =
## today's behaviour.
## 3. the bridge: one Shortcut per action shows the LIVE key, and run(action) reaches
## the same Keymap.tapped dispatch a key press does.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_editor_e0.gd
var ok := true
var checks := 0
var _frame := 0
var _km: Node
var _gm: Node
var _field: LineEdit
var _rec: Node
var _pi: Node
class Recorder extends Node:
var km: Node
var seen: Array[String] = []
# _unhandled_KEY_input, because that is where map_dc_level dispatches every builder
# key — an action replayed as anything but a key event never reaches it.
func _unhandled_key_input(event: InputEvent) -> void:
for a: String in ["builder.move", "builder.knife"]:
if km.tapped(event, a):
seen.append(a)
func _p(cond: bool, what: String, note: String = "") -> void:
checks += 1
if not cond:
ok = false
print(" %-62s %s %s" % [what, "OK" if cond else "**FAIL**", note])
func _process(_d: float) -> bool:
_frame += 1
match _frame:
1:
_km = root.get_node("/root/Keymap")
_gm = root.get_node("/root/GameMode")
_rec = Recorder.new()
_rec.km = _km
root.add_child(_rec)
var ui := Control.new()
root.add_child(ui)
_field = LineEdit.new()
ui.add_child(_field)
_pi = (load("res://scenes/player/player_input.gd") as GDScript).new()
root.add_child(_pi)
_gm.set_mode("build")
_km.push_context("builder")
print("-- contexts exist --")
_p(_km.CONTEXTS.has("text_input") and bool(_km.CONTEXTS["text_input"].get("exclusive", false)),
"text_input is an exclusive context")
_p(_km.CONTEXTS.has("tool"), "tool context exists")
print("\n-- no text focus: builder live, movement reads the keyboard --")
_p(_km.is_context_active("builder"), "builder active in build mode")
_p(_km.movement_allowed(), "movement allowed")
Input.action_press("move_forward")
10:
_p(is_equal_approx(float(_pi.get("input_direction").y), -1.0), "W moves (input_direction.y = -1)",
str(_pi.get("input_direction")))
_field.grab_focus()
20:
print("\n-- a focused text field --")
_p(_km.is_context_active("text_input"), "text_input active while the field has focus")
_p(not _km.is_context_active("builder"), "builder SUPPRESSED (typing fires no tool)")
_p(not _km.movement_allowed(), "movement not allowed")
_p(is_equal_approx(float(_pi.get("input_direction").length()), 0.0), "W held but input_direction is zero",
str(_pi.get("input_direction")))
_p(_km.is_context_active("global"), "global (always) still live — Esc gets you out")
_field.release_focus()
30:
_p(not _km.is_context_active("text_input") and _km.is_context_active("builder"),
"focus released: builder back")
_p(is_equal_approx(float(_pi.get("input_direction").y), -1.0), "and W moves again")
Input.action_release("move_forward")
print("\n-- the tool context and the fly modifiers --")
# Keymap step 1 (docs/keymap-blender-279.md §2a): the camera owns Ctrl / Shift /
# Space ONLY while flying (RMB held / Shift+F) — not merely when no tool is up.
_p(not _km.fly_modifiers_allowed(), "not flying, no tool: the camera owns nothing (keymap step 1)")
var stack := OperatorStack.new()
root.add_child(stack)
var op := LevelOperator.new()
root.add_child(op)
_p(stack.activate(op), "an operator activates")
_p(_km.is_context_active("tool"), "tool context pushed by OperatorStack.activate")
_p(not _km.fly_modifiers_allowed(), "tool up, RMB not held: the TOOL owns Ctrl/Shift/Space")
_gm.set("_temp_look", true)
_p(_km.fly_modifiers_allowed(), "RMB held: the camera owns them again")
_gm.set("_temp_look", false)
stack.cancel()
_p(not _km.is_context_active("tool"), "cancel pops the tool context")
_p(not _km.fly_modifiers_allowed(), "and, not flying, still nothing")
print("\n-- the Shortcut bridge --")
var sc: Shortcut = _km.shortcut_of("builder.move")
_p(sc != null and sc.resource_name == String(_km.binding_of("builder.move").get("label", "")),
"Shortcut named by the action's label", sc.resource_name if sc else "")
_p(sc != null and sc.get_as_text().contains("G"), "and shows the LIVE key", sc.get_as_text() if sc else "")
_p(_km.shortcut_of("builder.move") == sc, "one shared Shortcut per action")
_km.run("builder.move")
34:
_p(_rec.seen.has("builder.move"), "run() reaches the same tapped() dispatch a key press does",
str(_rec.seen))
_p(not _rec.seen.has("builder.knife"), "and only that action")
print("\n-- a REAL fly camera: Space rises unless a tool owns it --")
_ply = (load("res://scenes/player/player_procedural.tscn") as PackedScene).instantiate()
root.add_child(_ply)
_ply.global_position = Vector3(0, 50, 0)
40:
_ply.set_fly_mode(true)
Input.action_press("jump")
_gm.set("_temp_look", true) # keymap step 1: only FLYING moves the camera
_y0 = _ply.global_position.y
60:
_rise_free = _ply.global_position.y - _y0
_p(_rise_free > 0.05, "no tool, RMB held: holding Space flies UP", "%.3f m" % _rise_free)
_gm.set("_temp_look", false)
_stack2 = OperatorStack.new()
root.add_child(_stack2)
var op2 := LevelOperator.new()
root.add_child(op2)
_stack2.activate(op2)
_y0 = _ply.global_position.y
80:
var rise_tool: float = _ply.global_position.y - _y0
_p(absf(rise_tool) < 0.01, "a tool is up: Space does NOT fly (it is the tool's)", "%.3f m" % rise_tool)
_gm.set("_temp_look", true)
_y0 = _ply.global_position.y
100:
var rise_look: float = _ply.global_position.y - _y0
_p(rise_look > 0.05, "tool up + RMB held: Space flies again", "%.3f m" % rise_look)
_gm.set("_temp_look", false)
_stack2.cancel()
Input.action_release("jump")
if checks < 25:
_p(false, "only %d checks ran" % checks)
print("GATE editor_e0: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
quit(0 if ok else 1)
return true
return false
var _ply: Node3D
var _y0 := 0.0
var _rise_free := 0.0
var _stack2: Node