OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_editor_e2.gd

226 lines · gd ref:
extends SceneTree
## GATE — editor redesign E2 (docs/editor-redesign.md §4): snapping, on a real map_flat.
##   - grid 0.25 – 256 Quake units, default 8;
##   - snap toggle (top bar + Shift+Tab): OFF = steps of 1 unit (TrenchBroom's rule: the
##     brushes stay on whole units);
##   - WHILE A TOOL IS UP: Ctrl inverts the snap toggle, Shift = 1/8 steps (rule 1c);
##     with no tool they do nothing to snapping (they fly);
##   - every operator reads the EFFECTIVE units through LevelOperator.grid_units(), and the
##     placement snap (snap_to_map_grid) follows them;
##   - units switch u | m: grid dropdown, status bar and operator lines follow it.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_editor_e2.gd

var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _km: Node
var _set: Node
var _hud: Node
var _top: Node
var _stack: Node
var _saved_grid: Variant = 5
var _saved_snap: Variant = true
var _saved_tv: Variant = false
var _saved_te: Variant = false


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 _key(code: Key, pressed: bool) -> void:
	var e := InputEventKey.new()
	e.keycode = code
	e.physical_keycode = code
	e.pressed = pressed
	Input.parse_input_event(e)


func _process(_d: float) -> bool:
	_frame += 1
	match _frame:
		1:
			# KNOWN START, captured and forced BEFORE the map's _init reads the prefs:
			# gates share the installed game's user://, so the player's saved grid/snap
			# (or a leak from an interrupted run) must not become the gate's baseline.
			var d0: Node = root.get_node("/root/BuildDock")
			_saved_grid = d0.pref("grid", 5)
			_saved_snap = d0.pref("snap", true)
			_saved_tv = d0.pref("snap_vertex", false)
			_saved_te = d0.pref("snap_edge", false)
			d0.set_pref("grid", 5)
			d0.set_pref("snap", true)
			_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
			root.add_child(_map)
		30:
			_km = root.get_node("/root/Keymap")
			_set = root.get_node("/root/Settings")
			_hud = root.get_node("/root/BuildHud")
			root.get_node("/root/GameMode").set_mode("build")
			_top = _map.get("_top_bar")
			print("-- the grid --")
			var g: Array = _map.GRID_UNITS
			_p(g.has(0.25) and g.has(256.0), "grid spans 0.25 .. 256 units", str(g))
			_p(is_equal_approx(float(_map.base_grid_units()), 8.0), "default grid 8u")
			# The LABEL must match the result, and = / - must mean bigger / smaller as they
			# do for the carve brush in play mode (keymap_gate): = coarser, - finer.
			for pair: Array in [["builder.grid_finer", 4.0], ["builder.grid_coarser", 16.0]]:
				_map.set_grid_index(5)
				var b: Dictionary = _km.binding_of(String(pair[0]))
				var ev := InputEventKey.new()
				ev.physical_keycode = int(b.get("key", 0))
				ev.keycode = ev.physical_keycode
				ev.pressed = true
				_map._unhandled_key_input(ev)
				_p(is_equal_approx(float(_map.base_grid_units()), float(pair[1])),
					"\"%s\" (%s) takes 8u to %su" % [String(b.get("label", "")), _km.describe(String(pair[0])), str(pair[1])],
					"got %s" % str(_map.base_grid_units()))
			_p(int(_km.binding_of("builder.grid_coarser").get("key", 0)) == KEY_EQUAL
				and int(_km.binding_of("builder.grid_finer").get("key", 0)) == KEY_MINUS,
				"= is the bigger grid, - the smaller (same as the carve brush)")
			_map.set_grid_index(5)
			print("\n-- the snap toggle --")
			_p(bool(_map.get("snap_enabled")), "snap is ON by default")
			_p(is_equal_approx(float(_map.grid_units()), 8.0), "snap on: effective step = the grid")
			_km.run("builder.snap_toggle")
		32:
			_p(not bool(_map.get("snap_enabled")), "Shift+Tab turned snap OFF")
			var dock: Node = root.get_node("/root/BuildDock")
			_p(dock.pref("snap", true) == false, "snap state saved per player (user://editor_layout.json)")
			_p(is_equal_approx(float(_map.grid_units()), 1.0), "snap off: steps of 1 unit (whole units)")
			var s: float = _map.snap_to_map_grid(0.5, Vector3.RIGHT, _map.grid_units(), Vector3.ZERO, Vector3.ZERO)
			_p(is_equal_approx(s, roundf(0.5 / 0.028) * 0.028), "placement snaps to 1u when off", "%.4f" % s)
			_p(_top.snap_button() != null and not _top.snap_button().button_pressed, "the top-bar magnet shows it")
			_top.snap_button().button_pressed = true
			_top.snap_button().toggled.emit(true)
			_p(bool(_map.get("snap_enabled")), "clicking the magnet turns it back ON")
			print("\n-- no tool: Ctrl / Shift do nothing to snapping (they fly) --")
			_key(KEY_CTRL, true)
		34:
			_p(is_equal_approx(float(_map.grid_units()), 8.0), "Ctrl held, no tool: still 8u")
			_key(KEY_CTRL, false)
			_stack = _map.call("_ensure_ops")
			var op := LevelOperator.new()
			op.map = _map
			op.level = _map.get_node("DCLevel")
			_map.add_child(op)
			_stack.activate(op)
			_key(KEY_CTRL, true)
		36:
			print("\n-- a tool is up --")
			_p(is_equal_approx(float(_map.grid_units()), 1.0), "Ctrl held: snap INVERTED (1u)", str(_map.grid_units()))
			_p(is_equal_approx(float(_stack.active.grid_units()), 1.0), "and the operator sees the same effective units")
			_p(String(_stack.status_line()).contains("snap off: 1u steps"),
				"the tool's status line SAYS snap is off and what a step is", _stack.status_line())
			_key(KEY_CTRL, false)
			_key(KEY_SHIFT, true)
		38:
			_p(is_equal_approx(float(_map.grid_units()), 1.0), "Shift held: precision = grid / 8", str(_map.grid_units()))
			_p(is_equal_approx(float(_stack.active.grid_step()), 1.0 * 0.028), "operator step in metres follows")
			_p(not String(_stack.status_line()).contains("snap off"),
				"snap on (Shift is precision, not snap-off): no snap-off note", _stack.status_line())
			_key(KEY_SHIFT, false)
			_stack.cancel()
			print("\n-- units: Quake units or metres --")
			_p(String(_map.fmt_units(8.0)) == "8u", "u: 8 units reads '8u'", _map.fmt_units(8.0))
			_set.set_value("editor_units", 1)
		40:
			_p(String(_map.fmt_units(8.0)) == "0.224 m", "m: 8 units reads '0.224 m'", _map.fmt_units(8.0))
			_p(_hud.status_right().contains("0.224 m"), "the status bar follows", _hud.status_right())
			_p(_top.grid_option().get_item_text(5) == "0.224 m", "the grid dropdown follows", _top.grid_option().get_item_text(5))
			_set.set_value("editor_units", 0)
		42:
			_p(_top.grid_option().get_item_text(5) == "8u", "and back to units")
			_map.set_grid_index(7)
			var dock2: Node = root.get_node("/root/BuildDock")
			_p(int(dock2.pref("grid", -1)) == 7, "grid choice saved per player", str(dock2.pref("grid", -1)))
			# A fresh map instance picks both up (snap was turned back ON above).
			var m2: Node = (load("res://scenes/maps/map_flat.gd") as GDScript).new()
			_p(int(m2.get("_grid_idx")) == 7 and bool(m2.get("snap_enabled")), "a new map restores grid + snap",
				"%d %s" % [int(m2.get("_grid_idx")), str(m2.get("snap_enabled"))])
			m2.free()
			_map.set_grid_index(5)                       # back to 8u for the outliner checks
			_p(_hud.status_right().contains("snap"), "status bar shows the snap state", _hud.status_right())
			print("\n-- the outliner's position / size boxes follow the units --")
			var gs: Node = root.get_node_or_null("/root/GameState")
			if gs != null:
				gs.route("world:level:*", 1, "peer")
			var keet: Node = root.get_node_or_null("/root/KeetService")
			if keet != null:
				keet.current_member_id = "gate-author"
			var s16 := 16.0 * 0.028                       # a 16-unit cube
			var persist: Node = _map.get("_persist")
			var bdot: String = persist.record("brush.create", {
				"planes": [1, 0, 0, s16, -1, 0, 0, 0, 0, 1, 0, s16, 0, -1, 0, 0, 0, 0, 1, s16, 0, 0, -1, 0],
				"proj": [], "material_id": 1, "level_id": 0, "sky": false})
			persist.refold()
			_map.select_brushes(PackedInt32Array([_map.get_node("DCLevel").brush_by_key(bdot)]))
			_ol = _map.get("_outliner")
			_ol.call("_sync_position")
			var sz: Array = _ol.get("_size")
			_p(is_equal_approx(float(sz[0].value), 16.0) and String(sz[0].suffix) == "u", "units: size reads 16 u",
				"%s %s" % [str(sz[0].value), sz[0].suffix])
			_set.set_value("editor_units", 1)
		44:
			var sz2: Array = _ol.get("_size")
			var pos2: Array = _ol.get("_pos")
			_p(is_equal_approx(float(sz2[0].value), 0.448) and String(sz2[0].suffix) == "m", "metres: size reads 0.448 m",
				"%s %s" % [str(sz2[0].value), sz2[0].suffix])
			_p(is_equal_approx(float(pos2[0].value), 0.224) and String(pos2[0].suffix) == "m", "metres: centre X reads 0.224 m",
				str(pos2[0].value))
			sz2[0].value = 0.9                            # ~32u, snapped to the 8u grid → 32u
		46:
			var sel_size: Vector3 = _map.selection_size()
			_p(is_equal_approx(sel_size.x, 32.0 * 0.028), "typing 0.9 m snaps to 32u (0.896 m) on the grid",
				"%.4f m" % sel_size.x)
			_set.set_value("editor_units", 0)
		48:
			var sz3: Array = _ol.get("_size")
			_p(is_equal_approx(float(sz3[0].value), 32.0), "and back in units it reads 32", str(sz3[0].value))
			print("\n-- snap TARGETS: vertex / edge on the aimed face --")
			var lvl: Node = _map.get_node("DCLevel")
			var s8 := 32.0 * 0.028                 # 32u: an edge midpoint is 0.448 m from any corner
			var tdot: String = _map.get("_persist").record("brush.create", {
				"planes": [1, 0, 0, 10 + s8, -1, 0, 0, -10, 0, 1, 0, s8, 0, -1, 0, 0, 0, 0, 1, s8, 0, 0, -1, 0],
				"proj": [], "material_id": 1, "level_id": 0, "sky": false})
			_map.get("_persist").refold()
			var tb: int = lvl.brush_by_key(tdot)
			var corner := Vector3(10 + s8, s8, s8)          # a corner of the top face
			var near_corner := corner + Vector3(-0.05, 0, -0.04)   # on the top face, ~0.06 m away
			var mid_edge := Vector3(10 + s8 * 0.5, s8, s8 - 0.03)  # near the middle of an edge
			_p(_map.snap_target_point(near_corner, tb) == null, "targets off: nothing")
			_map.set_snap_target("vertex", true)
			var v: Variant = _map.snap_target_point(near_corner, tb)
			_p(v is Vector3 and (v as Vector3).is_equal_approx(corner), "vertex on: a point near a corner snaps TO it", str(v))
			var e0: Variant = _map.snap_target_point(mid_edge, tb)
			_p(e0 == null, "vertex only: the middle of an edge is not near a vertex", str(e0))
			_map.set_snap_target("edge", true)
			var e: Variant = _map.snap_target_point(mid_edge, tb)
			_p(e is Vector3 and is_equal_approx((e as Vector3).z, s8) and is_equal_approx((e as Vector3).y, s8),
				"edge on: a point near an edge snaps ONTO it", str(e))
			_p(_map.get_node("/root/BuildDock").pref("snap_vertex", false) == true, "targets remembered per player")
			_map.snap_enabled = false
			_p(_map.snap_target_point(near_corner, tb) == null, "snap off: targets off too")
			_map.snap_enabled = true
			_map.set_snap_target("vertex", false)
			_map.set_snap_target("edge", false)
			var dock3: Node = root.get_node("/root/BuildDock")
			dock3.set_pref("grid", _saved_grid)
			dock3.set_pref("snap", _saved_snap)
			dock3.set_pref("snap_vertex", _saved_tv)
			dock3.set_pref("snap_edge", _saved_te)
			if checks < 28:
				_p(false, "only %d checks ran" % checks)
			print("GATE editor_e2: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
			quit(0 if ok else 1)
			return true
	return false


var _ol: Node