OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_knife_local.gd

126 lines · gd ref:
extends Node
## GATE — the knife's axis key pressed TWICE cuts on the brush's OWN axis (#109, user
## 2026-10-13: "I rotate a cube and when I press K the axis that K cuts by is still
## global"; Blender's X X = local).
##
## A plain 2 m cube turned 30° about Y, standing on the map's own aim ray:
##   K1  X: the cut plane's normal is the world X
##   K2  X again: the normal is the cube's own X — R·(1,0,0) — and the HUD says local
##   K3  X a third time: the world X again
##   K5  Y and Z likewise: world first, the cube's own on the second press (the cube is
##       tilted on two axes, so its own X, Y and Z all differ from the world's)
##   K4  a LOCAL cut commits one brush.split whose normal is the cube's own X, and it
##       lands on the cube's grid: d is a whole number of grid steps from its own face
## Keys go to the knife through handle_key, the stack's input path for an active tool.
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_knife_local.tscn

var fails := 0
var checks := 0
var _cam: Camera3D = null


func _ok(what: String, cond: bool, note: Variant = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-64s %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")
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	_cam = Camera3D.new()
	add_child(_cam)
	_cam.current = true
	_cam.global_position = Vector3(40, 14, 28)
	_cam.look_at(Vector3(40, 0, 40), Vector3.UP)
	for i in 6:
		await get_tree().process_frame
	await _run(map)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 11:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE knife_local: %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) -> InputEventKey:
	var e := InputEventKey.new()
	e.keycode = code
	e.physical_keycode = code
	e.pressed = true
	return e


func _run(map: Node3D) -> void:
	var persist: Node = map.get("_persist")
	var level: Node = map.get("level")
	var stack: Node = map.call("_ensure_ops")
	var ray: Dictionary = map.call("_crosshair_ray")
	var c: Vector3 = (ray["from"] as Vector3) + (ray["dir"] as Vector3) * 16.0
	# Tilted on TWO axes, so its own X, Y and Z all differ from the world's.
	var r := Basis(Vector3.UP, deg_to_rad(30.0)) * Basis(Vector3.RIGHT, deg_to_rad(20.0))
	# The cube [-1,1]^3 turned by r and centred on the aim: each face n' = r·n, d = n'·c + 1.
	var pl: Array = []
	for n: Vector3 in [Vector3.RIGHT, Vector3.LEFT, Vector3.UP, Vector3.DOWN, Vector3.BACK, Vector3.FORWARD]:
		var n2: Vector3 = r * n
		pl.append_array([n2.x, n2.y, n2.z, n2.dot(c) + 1.0])
	var dot: String = persist.record("brush.create", {"planes": pl, "proj": [], "material_id": 2,
		"level_id": 0, "sky": false, "group": ""})
	persist.refold()
	await get_tree().process_frame
	var b: int = level.brush_by_key(dot)
	var hit: Dictionary = map.call("_crosshair_hit")
	_ok("setup: the aim lands on the turned cube", not hit.is_empty() and int(map.call("_brush_from_hit", hit)) == b, b)

	Keymap.run("builder.knife")
	await get_tree().process_frame
	var kn: Object = stack.get("active")
	if not (kn is KnifeOperator):
		_ok("K the knife is up", false, kn)
		return
	kn.set_tool_setting("axis", 0)          # X
	kn.set_tool_setting("space", 0)         # global
	var local_x: Vector3 = r * Vector3.RIGHT
	_ok("K1 X: the world X", (kn.get("_plane_n") as Vector3).distance_to(Vector3.RIGHT) < 1e-3, kn.get("_plane_n"))
	kn.handle_key(_key(KEY_X))
	_ok("K2 X again: the cube's own X", (kn.get("_plane_n") as Vector3).distance_to(local_x) < 1e-3,
		[kn.get("_plane_n"), local_x])
	_ok("K2 and the HUD says local", String(kn.status()).contains("local"), kn.status())
	kn.handle_key(_key(KEY_X))
	_ok("K3 X a third time: the world X again", (kn.get("_plane_n") as Vector3).distance_to(Vector3.RIGHT) < 1e-3,
		kn.get("_plane_n"))
	# K5: Y and Z the same way — world first, the cube's own on the second press.
	for pair: Array in [["y", KEY_Y, Vector3.UP], ["z", KEY_Z, Vector3.BACK]]:
		var a: String = pair[0]
		kn.handle_key(_key(pair[1]))
		_ok("K5 %s: the world %s" % [a.to_upper(), a.to_upper()],
			(kn.get("_plane_n") as Vector3).distance_to(pair[2]) < 1e-3, kn.get("_plane_n"))
		kn.handle_key(_key(pair[1]))
		var want: Vector3 = r * (pair[2] as Vector3)
		_ok("K5 %s again: the cube's own %s" % [a.to_upper(), a.to_upper()],
			(kn.get("_plane_n") as Vector3).distance_to(want) < 1e-3, [kn.get("_plane_n"), want])
	kn.handle_key(_key(KEY_X))                # back to X (world) for K4
	kn.handle_key(_key(KEY_X))                # local again, and cut
	var seq0: int = persist._max_seq_seen()
	_ok("K4 the local cut commits", bool(stack.call("confirm")))
	var splits: Array = []
	for rec: Dictionary in (persist.records() as Array):
		if int(rec["seq"]) > seq0 and String(rec["kind"]) == "brush.split":
			splits.append(rec)
	_ok("K4 one brush.split on the cube's own X", splits.size() == 1
		and (splits[0]["n"] as Vector3).distance_to(local_x) < 1e-3, splits)
	if splits.size() == 1:
		var step: float = float(kn.call("grid_step"))
		var from_face: float = float(splits[0]["d"]) - (local_x.dot(c) - 1.0)    # from the -X face
		_ok("K4 it lands on the cube's grid (whole steps from its own face)",
			absf(from_face / step - roundf(from_face / step)) < 1e-3, [from_face, step])