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)