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])