OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_move_plane.gd

155 lines · gd ref:
extends SceneTree
## GATE — Shift + the axis key LOCKS that axis and moves in the other two (hyper-games
## #109, 2026-10-12: "like in blender the ability to move based on constraint so if you
## press x twice it will move on y / z but not x"). 2026-10-13 the user moved the double
## press to Blender's meaning — X X = the LOCAL axis — so the lock is on Shift+X now
## (Blender's "exclude X"); the local axis itself is gate_knife_local / gate_axis_local.
##
## The pointer is driven through the map's own stand-in (_wrap_pending, which
## _crosshair_ray reads while the real headless mouse is elsewhere).
##
##   A1  G then X = axis X (world); X again = LOCAL X, not a plane; X a third time = world;
##       Shift+X = plane (X locked); Shift+X again = back to the axis; another letter =
##       that axis, not a plane
##   A2  Y locked: pointing at the floor plane moves on X and Z only, whole grid steps
##   A3  Enter commits exactly that delta (Y untouched) as one brush.translate
##   A4  the status says which axis is locked
##   A5  [ ] do not change a plane move
##   A6  the settings show "Lock axis"; the line-only Distance row is hidden in plane mode
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_move_plane.gd

var _frame := 0
var _map: Node = null
var _cam: Camera3D = null
var fails := 0
var checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	if not c:
		fails += 1
	print("  %-66s %s  %s" % [m, "OK" if c else "**FAIL**", str(got)])


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


func _box(lo: Vector3, hi: Vector3) -> Array:
	return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		root.size = Vector2i(1920, 1080)
		root.get_node("/root/GameMode").set_mode("build")
		var keet: Node = root.get_node_or_null("/root/KeetService")
		if keet != null:
			keet.current_member_id = "gate-author"
		var gs: Node = root.get_node_or_null("/root/GameState")
		if gs != null:
			gs.route("world:level:*", 1, "peer")
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	_run()
	if checks < 10:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE move_plane: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	quit(0 if fails == 0 else 1)
	return true


func _count(kind: String) -> int:
	var n := 0
	for r: Dictionary in (_map.get("_persist").records() as Array):
		if String(r.get("kind", "")) == kind and not bool(r.get("undone", false)):
			n += 1
	return n


func _ids(op: Object) -> Array:
	var out: Array = []
	for d: Dictionary in (op.tool_settings() as Array):
		out.append(String(d["id"]))
	return out


func _run() -> void:
	# A camera above and behind, looking down at 45° onto a brush on the floor plane y=300.
	_cam = Camera3D.new()
	root.add_child(_cam)
	_cam.global_position = Vector3(0, 310, 10)
	_cam.look_at(Vector3(0, 300, 0))
	_cam.make_current()
	var persist: Node = _map.get("_persist")
	var dot: String = persist.record("brush.create", {"planes": _box(Vector3(-1, 299, -1), Vector3(1, 300, 1)),
		"proj": [], "material_id": 0, "level_id": 0, "sky": false})
	persist.refold()
	var b: int = _map.level.brush_by_key(dot)
	_map.select_brushes(PackedInt32Array([b]))
	_map.set("_sel_point", Vector3(0, 300, 0))
	var centre := Vector2(960, 540)
	_map.set("_wrap_pending", centre)
	_map._unhandled_key_input(_key(KEY_G))
	var stack: Node = _map.call("_ensure_ops")
	var op: Object = stack.active
	_p(op is MoveOperator, "setup: G starts Move", op)
	if not (op is MoveOperator):
		return
	op.set("_grab", Vector3(0, 300, 0))
	# ---- A1 ----
	_map._unhandled_key_input(_key(KEY_X))
	_p(String(op.get("_axis")) == "x" and not bool(op.get("_plane")) and String(op.get("_space")) == "global",
		"A1 X = axis X (world)")
	_map._unhandled_key_input(_key(KEY_X))
	_p(String(op.get("_axis")) == "x" and not bool(op.get("_plane")) and String(op.get("_space")) == "local",
		"A1 X again = LOCAL X, not a plane")
	_map._unhandled_key_input(_key(KEY_X))
	_p(String(op.get("_space")) == "global" and not bool(op.get("_plane")), "A1 X a third time = world X")
	_map._unhandled_key_input(_key(KEY_X, true))
	_p(String(op.get("_axis")) == "x" and bool(op.get("_plane")), "A1 Shift+X = plane, X locked")
	_map._unhandled_key_input(_key(KEY_X, true))
	_p(not bool(op.get("_plane")), "A1 Shift+X again = back to the axis")
	_map._unhandled_key_input(_key(KEY_Z))
	_p(String(op.get("_axis")) == "z" and not bool(op.get("_plane")), "A1 another letter = that axis, not a plane")
	# ---- A2 ----
	_map._unhandled_key_input(_key(KEY_Y, true))
	_p(bool(op.get("_plane")) and String(op.get("_axis")) == "y", "A2 setup: Shift+Y = Y locked (plane XZ)")
	_map.set("_wrap_pending", centre + Vector2(300, -200))
	op.update_preview()
	var d: Vector3 = op.call("_delta")
	var gs: float = op.grid_step()
	var on_grid := absf(d.x / gs - roundf(d.x / gs)) < 1e-4 and absf(d.z / gs - roundf(d.z / gs)) < 1e-4
	_p(is_zero_approx(d.y) and absf(d.x) > gs * 0.5 and absf(d.z) > gs * 0.5 and on_grid,
		"A2 the move is on X and Z only, whole grid steps", d)
	# ---- A4 ----
	_p(String(op.status()).contains("Y locked"), "A4 the status says Y is locked", op.status())
	# ---- A5 ----
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	op.update_preview()
	_p((op.call("_delta") as Vector3).is_equal_approx(d), "A5 ] does not change a plane move", op.call("_delta"))
	# ---- A6 ----
	var ids := _ids(op)
	_p(ids.has("plane") and not ids.has("distance"), "A6 Lock axis shown, Distance hidden in plane mode", ids)
	# ---- A3 ----
	var b0: Dictionary = _map.level.get_brush(b)
	var n0 := _count("brush.translate")
	_map._unhandled_key_input(_key(KEY_ENTER))
	var b1: Dictionary = _map.level.get_brush(_map.level.brush_by_key(dot))
	var moved: Vector3 = (b1["aabb_min"] as Vector3) - (b0["aabb_min"] as Vector3)
	_p(stack.active == null and _count("brush.translate") == n0 + 1, "A3 Enter commits one brush.translate",
		"+%d" % (_count("brush.translate") - n0))
	_p(moved.is_equal_approx(d), "A3 the brush moved by exactly that delta (Y untouched)", moved)