OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_overlays.gd

191 lines · gd ref:
extends Node
## GATE — keymap step 6.5, THE OVERLAYS (docs/keymap-blender-279.md §6a.5):
##   one switch per overlay on its key (V outlines, Shift+V clearance, Shift+Alt+L links,
##   ' grid, Shift+Alt+C cutters) and a MASTER (Shift+Alt+Z) that hides them all and brings
##   each back as it was; outlines off no longer hides clearance / cutters; the states
##   survive a mode change; the Outliner eye and the links overlay agree; the top bar's
##   Overlays toggle + ▾ rows follow and grey out.
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_overlays.tscn

var fails := 0
var checks := 0


func _ok(what: String, cond: bool, note: Variant = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-66s %s  %s" % [what, "OK" if cond else "**FAIL**", str(note)])


func _ready() -> void:
	var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
	gs.route("world:level:*", 1, "peer")
	var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	get_tree().root.get_node("/root/GameMode").set_mode("build")
	get_tree().root.size = Vector2i(1920, 1080)
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 8:
		await get_tree().process_frame
	await _run(map)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 24:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE overlays: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _key(code: Key, shift := false, alt := false) -> InputEventKey:
	var e := InputEventKey.new()
	e.keycode = code
	e.physical_keycode = code
	e.pressed = true
	e.shift_pressed = shift
	e.alt_pressed = alt
	return e


func _press(map: Node, e: InputEventKey) -> void:
	map._unhandled_key_input(e)
	await get_tree().process_frame


func _run(map: Node3D) -> void:
	# ⛔ The Outliner's save is the USER's default.dat on map_test (CI=true does not
	# isolate it). This gate drives the model only, and isolates the key anyway.
	var ol: Node = map.get("_outliner")
	if ol != null:
		ol.set("_save_key", "gate_overlays")
	var bv: Node = map.call("_ensure_brush_view")
	var links: Node3D = map.call("_ensure_entity_links")
	var gm: Node = get_tree().root.get_node("/root/GameMode")

	print("-- defaults (CI: a fresh player) --")
	_ok("outlines, links, grid, cutters on; clearance off; master on",
		map.overlay_on("outlines") and map.overlay_on("links") and map.overlay_on("grid")
		and map.overlay_on("cutters") and not map.overlay_on("clearance") and map.overlays_all_on())

	print("-- V is the level outlines ONLY --")
	await _press(map, _key(KEY_V))
	_ok("V turned the outlines overlay off", not map.overlay_on("outlines") and not bool(bv.show_outlines))
	_ok("... the BrushView node stays up (it is build mode, not an overlay)", bool(bv.visible))
	await _press(map, _key(KEY_V, true))
	_ok("Shift+V with outlines off: clearance shows AND is pickable (V used to hide it)",
		bool(bv.show_clearance) and bool(map.get("_show_clearance")))
	_ok("... and the cutter wire is still on", bool(bv.show_cutters) and bool(map.get("_show_cutters")))

	print("-- the REAL input path (Keymap.run replays the key through the GUI) --")
	# The ▾ MenuButton's rows carry the keys as accelerators; a MenuButton FIRES an item
	# on its accelerator, which ran Keymap.run, which replayed the key: an endless loop
	# (gate_editor_e1 hung on View > Brush outlines). Keys sent to the map directly, as
	# above, never pass the GUI, so this asks for exactly one flip the real way.
	var before_v: bool = map.overlay_on("outlines")
	Keymap.run("builder.brush_view")
	for i in 3:
		await get_tree().process_frame
	_ok("Keymap.run(V) flips the outlines exactly once (no menu-accelerator loop)",
		map.overlay_on("outlines") != before_v)
	Keymap.run("builder.brush_view")
	for i in 3:
		await get_tree().process_frame

	print("-- Shift+Alt+C: live cutters --")
	await _press(map, _key(KEY_C, true, true))
	_ok("Shift+Alt+C turned the cutters off (drawing and picking)",
		not map.overlay_on("cutters") and not bool(bv.show_cutters) and not bool(map.get("_show_cutters")))
	await _press(map, _key(KEY_C, true, true))
	_ok("... and back on", map.overlay_on("cutters") and bool(bv.show_cutters))

	print("-- Shift+Alt+Z: the master --")
	await _press(map, _key(KEY_Z, true, true))
	_ok("master off", not map.overlays_all_on())
	_ok("... clearance hidden and NOT pickable", not bool(bv.show_clearance) and not bool(map.get("_show_clearance")))
	_ok("... cutters hidden and not pickable", not bool(bv.show_cutters) and not bool(map.get("_show_cutters")))
	_ok("... entity links hidden", not links.visible)
	_ok("... the grid is off", not bool(map.call("_grid_view_on")))
	_ok("... each overlay KEPT its own switch", map.overlay_on("clearance") and map.overlay_on("links")
		and not map.overlay_on("outlines"))
	await _press(map, _key(KEY_V))
	_ok("V while all are off flips its own switch, still not drawn",
		map.overlay_on("outlines") and not bool(bv.show_outlines))
	await _press(map, _key(KEY_V))
	await _press(map, _key(KEY_Z, true, true))
	_ok("Shift+Alt+Z again: back exactly as it was", map.overlays_all_on() and bool(bv.show_clearance)
		and links.visible and bool(map.call("_grid_view_on")) and not bool(bv.show_outlines) and bool(bv.show_cutters))

	print("-- a mode change does not reset them --")
	gm.set_mode("play")
	await get_tree().process_frame
	_ok("play: links hidden (build-only)", not links.visible)
	gm.set_mode("build")
	await get_tree().process_frame
	_ok("back to build: outlines still off, clearance still on, links back",
		not bool(bv.show_outlines) and bool(bv.show_clearance) and links.visible)

	print("-- the Outliner eye and the links overlay agree --")
	var model: Variant = ol.get("model") if ol != null else null
	_ok("setup: the map has an Outliner model", model != null)
	if model != null:
		model.set_row_hidden("node:EntityLinks", true)
		model.apply()
		_ok("the eye hides the links", not links.visible)
		map.set_overlay("links", false)
		model.set_row_hidden("node:EntityLinks", false)
		model.apply()
		_ok("unhiding with the links overlay OFF keeps them hidden", not links.visible)
		map.set_overlay("links", true)
		_ok("... and the overlay brings them back", links.visible)

	print("-- the top bar --")
	var top: Node = map.get("_top_bar")
	_ok("setup: the top bar exists", top != null)
	if top == null:
		return
	var btn: Button = top.overlays_button()
	var mb: MenuButton = top.overlays_menu()
	_ok("there is an Overlays toggle and its ▾", btn != null and mb != null)
	if btn == null or mb == null:
		return
	await get_tree().process_frame
	_ok("the toggle shows the master (on)", btn.button_pressed)
	await _press(map, _key(KEY_Z, true, true))
	await get_tree().process_frame
	_ok("Shift+Alt+Z: the toggle follows (off)", not btn.button_pressed)
	var pm: PopupMenu = mb.get_popup()
	top._sync_checks(pm)
	var rows := 0
	var greyed := 0
	var bad := PackedStringArray()
	for i in pm.item_count:
		if pm.is_item_separator(i):
			continue
		rows += 1
		var a := String(pm.get_item_metadata(i))
		if pm.is_item_disabled(i):
			greyed += 1
		if pm.get_item_accelerator(i) != top.accel_of(a):
			bad.append(a)
	var n_ov: int = (map.get("OVERLAYS") as Dictionary).size()     # six since Curves (2026-10-18)
	_ok("▾ has a row per overlay (%d) with its live key" % n_ov, rows == n_ov and n_ov == 6 and bad.is_empty(), [rows, bad])
	_ok("... greyed while the master is off", greyed == n_ov, greyed)
	btn.pressed.emit()                          # what clicking the toggle does
	for i in 3:
		await get_tree().process_frame
	_ok("clicking the toggle turns the master back on", map.overlays_all_on())
	var view_rows: Array = []
	for vm: PopupMenu in top.menus():
		if vm.name == "View":
			for i in vm.item_count:
				if not vm.is_item_separator(i):
					view_rows.append(String(vm.get_item_metadata(i)))
	_ok("the View menu lists the master and the cutters row", view_rows.has("builder.overlays_all")
		and view_rows.has("builder.cutters_view"))
	# Leave the (CI) prefs as the defaults.
	map.set_overlay("outlines", true)
	map.set_overlay("clearance", false)