OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_editor_e0.gd

155 lines · gd ref:
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