extends Node
## GATE: map_dc_terrain's dev key wall is gone and its useful tools live in the F4
## editor dock (key audit 2026-10-01).
##
## "$G" --path . --rendering-driver vulkan --resolution 1280x720 res://scenes/test/gate_dc_terrain_tools.tscn
##
## The map SHIPS (map_test's teleporter leads here), and its own `match keycode`
## ignored modifiers, key repeat and the mode: Delete wiped the room's durable
## edits (and repeated while held), R rebuilt the terrain, RMB carved AND grappled.
## Asserts, through the map's own _unhandled_input:
## - Delete / R / C / B / H / V / M / N / F2 / F4 / F6-F10 / F12 / T / F / G / P / arrows
## touch nothing in play (edit count unchanged, no brush picked);
## - RMB does not carve; LMB (player.fire, map_core) does;
## - in build mode Ctrl+Z (builder.undo) takes the last edit back;
## - the dock carries the DC Terrain, Quake Level and Terrain Brushes panels,
## and "Clear ALL edits" asks before it wipes.
## ⛔ NOT HEADLESS.
const MAP := "res://scenes/maps/map_dc_terrain.tscn"
const PROBE := "res://scenes/test/terrain_probe.gd"
const SETTLE_FRAMES := 60
const SETTLE_CAP_S := 120.0
var _map: Node
var _cam: Camera3D
var _fails := 0
func _ready() -> void:
_run.call_deferred()
func _ok(what: String, c: bool) -> void:
if not c:
_fails += 1
print("[DCTOOLS] %s %s" % ["PASS" if c else "FAIL", what])
func _since(t0: int) -> float:
return (Time.get_ticks_msec() - t0) / 1000.0
func _terrain() -> Node:
return _map.get_node_or_null(^"DCTerrain")
func _settle() -> void:
var tr := _terrain()
var t0 := Time.get_ticks_msec()
var calm := 0
while calm < SETTLE_FRAMES and _since(t0) < SETTLE_CAP_S:
await get_tree().process_frame
var ok: bool = bool(tr.call("is_lod_settled")) and int(tr.call("get_pending_count")) == 0 \
and int(tr.call("get_collision_queue_size")) == 0
calm = calm + 1 if ok else 0
func _mouse(button: int) -> InputEventMouseButton:
var e := InputEventMouseButton.new()
e.button_index = button
e.pressed = true
return e
func _key(k: int, shift := false) -> InputEventKey:
var e := InputEventKey.new()
e.physical_keycode = k
e.keycode = k
e.pressed = true
e.shift_pressed = shift
return e
func _send(ev: InputEvent) -> void:
_map.call("_unhandled_input", ev)
await get_tree().process_frame
func _edits() -> int:
return int(_terrain().call("get_edit_count"))
func _run() -> void:
if DisplayServer.get_name() == "headless":
print("RESULT: SKIP (needs a display)")
get_tree().quit(2)
return
GameState.enter_map("map_dc_terrain")
var mgr := TerrainEditManager.new()
mgr.name = "TerrainEditManager"
add_child(mgr)
_cam = Camera3D.new()
_cam.far = 20000.0
add_child(_cam)
_cam.current = true
_map = (load(MAP) as PackedScene).instantiate()
add_child(_map)
_cam.current = true
_cam.global_position = Vector3(0, 40, 0)
await _settle()
var tr := _terrain()
var s = _map.call("_find_surface_gpu", load(PROBE), tr, Vector3(10, 400, 0), 64.0) if _map.has_method("_find_surface_gpu") else null
var site := Vector3(10, float(s) if s != null else 0.0, 0)
_cam.global_position = site + Vector3(0, 12, 12)
_cam.look_at(site)
await _settle()
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
await get_tree().process_frame
# One real edit first, so a wipe or an undo would have something to remove.
var n0 := _edits()
await _send(_mouse(MOUSE_BUTTON_LEFT))
_ok("LMB carves -- the carver weapon (edits %d -> %d)" % [n0, _edits()], _edits() == n0 + 1)
var n1 := _edits()
await _send(_mouse(MOUSE_BUTTON_RIGHT))
_ok("RMB does NOT carve (it is the grapple)", _edits() == n1)
for k: int in [KEY_DELETE, KEY_R, KEY_C, KEY_B, KEY_H, KEY_V, KEY_N, KEY_F2, KEY_F4, KEY_F6,
KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F12, KEY_T, KEY_F, KEY_G, KEY_P,
KEY_LEFT, KEY_UP, KEY_PAGEUP, KEY_BRACKETLEFT]:
await _send(_key(k))
_ok("no dev key touched the edits in play (still %d)" % _edits(), _edits() == n1)
_ok("and none picked a brush", int(_map.get("_selected_brush")) < 0 and (_map.get("_lifted") as Dictionary).is_empty())
GameMode.set_mode(GameMode.BUILD)
await get_tree().process_frame
var n2 := _edits()
# Ctrl+Z since keymap steps 2-3 (6b0cfac): bare Z is Blender's wireframe toggle.
var undo := _key(KEY_Z)
undo.ctrl_pressed = true
await _send(undo)
await _settle()
_ok("in build mode Ctrl+Z undoes the last edit (edits %d -> %d)" % [n2, _edits()], _edits() == n2 - 1)
var dock: Node = get_tree().root.get_node_or_null(^"/root/BuildDock")
_ok("the DC Terrain panel is in the dock", dock != null and bool(dock.call("has_panel", "dc_terrain")))
_ok("the Quake Level panel is in the dock", dock != null and bool(dock.call("has_panel", "quake_level")))
_ok("the Terrain Brushes panel is in the dock", dock != null and bool(dock.call("has_panel", "terrain_brushes")))
if _map.has_method("request_clear_all"):
await _send(_mouse(MOUSE_BUTTON_LEFT)) # in build mode: must not carve either
GameMode.set_mode(GameMode.PLAY)
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
await _send(_mouse(MOUSE_BUTTON_LEFT))
var n3 := _edits()
_map.call("request_clear_all")
await get_tree().process_frame
_ok("Clear ALL asks first -- nothing wiped yet (%d)" % _edits(), _edits() == n3 and n3 > 0)
else:
_ok("the map exposes request_clear_all (the panel's guarded wipe)", false)
# THE BUTTONS THEMSELVES, pressed through the dock -- not just "a panel exists".
var sky: Node = _map.get_node_or_null(^"Sky3DExtended")
if sky != null:
var t0: float = float(sky.get("current_time"))
_press_button(dock, "+1 h")
_ok("+1 h moves this map's sky (%.2f -> %.2f)" % [t0, float(sky.get("current_time"))],
is_equal_approx(fmod(t0 + 1.0, 24.0), float(sky.get("current_time"))))
var n4 := _edits()
_press_button(dock, "Stamp fandisk at crosshair (carver size)")
await get_tree().process_frame
_ok("Stamp fandisk adds one edit (%d -> %d)" % [n4, _edits()], _edits() == n4 + 1)
_press_button(dock, "Rebuild terrain (re-mesh everything)")
await _settle()
_ok("Rebuild keeps the edits (%d)" % _edits(), _edits() == n4 + 1)
GameMode.set_mode(GameMode.PLAY)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
print("RESULT: %s" % ("PASS" if _fails == 0 else "FAIL"))
get_tree().quit(0 if _fails == 0 else 1)
func _press_button(dock: Node, text: String) -> void:
for b: Node in dock.find_children("*", "Button", true, false):
if (b as Button).text == text:
(b as Button).pressed.emit()
return
_ok("button '%s' exists in the dock" % text, false)