extends Node
## GATE — INSET / OUTSET CUTS (#120 L1, docs/node-tree-and-stacks.md §12.3; dc-terrain
## brush.cut op "inset" / "outset" with a depth).
## I1 an inset (depth 0.25) leaves a pocket where the cutter meets the top face: air in
## the top 0.25 inside the cutter, solid (exactly once) below it and beside it
## I2 op.set depth 0.4 deepens the pocket
## O1 switched to outset: a boss 0.4 proud of the top inside the cutter, the target
## whole underneath, no point solid twice
## U1 Ctrl+Z of the switch: an inset again
## P1 the cut's pieces are named the same from a second fold of the same records
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_op_inset.tscn
var fails := 0
var checks := 0
var map: Node3D
var persist: Node
var level: Node
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")
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
await _run()
get_tree().root.get_node("/root/GameMode").set_mode("play")
if checks < 10:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE op_inset: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _box(at: Vector3, size: Vector3) -> String:
var d: String = persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, at)),
"size": [size.x, size.y, size.z], "material_id": 2, "level_id": 0})
persist.refold()
return d
## How many of the target group's SOLID brushes contain `p`: 0 air, 1 solid, 2+ overlap.
func _count(group: String, p: Vector3) -> int:
var n := 0
for b: int in (level.get_group_brush_ids(group) as PackedInt32Array):
var d: Dictionary = level.get_brush(b)
if bool(d.get("clearance", false)) or bool(d.get("cutter", false)):
continue
var pl: PackedFloat32Array = d["planes"]
var inside := true
for i in pl.size() / 4:
if Vector3(pl[i * 4], pl[i * 4 + 1], pl[i * 4 + 2]).dot(p) > pl[i * 4 + 3] + 1.0e-4:
inside = false
break
if inside:
n += 1
return n
func _keys(group: String) -> Array:
var out: Array = []
for b: int in (level.get_group_brush_ids(group) as PackedInt32Array):
out.append(String(level.get_brush_key(b)))
out.sort()
return out
func _run() -> void:
persist = map.get("_persist")
level = map.get("level")
persist.refold()
# Target: x 60..64, y 0..2, z 60..64. Cutter: x 61..63, y 1.5..3.5, z 61..63 — it meets
# the target's top face (y 2) and dips 0.5 into it.
var t := _box(Vector3(60, 0, 60), Vector3(4, 2, 4))
var c := _box(Vector3(61, 1.5, 61), Vector3(2, 2, 2))
persist.begin_gesture("Live inset")
var op: String = persist.record("op.create", {"op": "inset", "depth": 0.25, "cutter": c, "targets": [t]})
persist.end_gesture()
persist.refold()
# ---- I1 ----
_ok("I1 the pocket: y 1.9 inside the cutter is air", _count(t, Vector3(62, 1.9, 62)) == 0, _count(t, Vector3(62, 1.9, 62)))
_ok("I1 ... its floor: y 1.6 is solid, once", _count(t, Vector3(62, 1.6, 62)) == 1, _count(t, Vector3(62, 1.6, 62)))
_ok("I1 ... beside the cutter: y 1.9 is solid, once", _count(t, Vector3(60.5, 1.9, 60.5)) == 1)
_ok("I1 ... deep under it: y 0.5 is solid, once", _count(t, Vector3(62, 0.5, 62)) == 1)
# ---- I2 ----
map.set_op(op, {"depth": 0.4})
_ok("I2 depth 0.4: y 1.7 is now air", _count(t, Vector3(62, 1.7, 62)) == 0, _count(t, Vector3(62, 1.7, 62)))
_ok("I2 ... y 1.55 is still floor", _count(t, Vector3(62, 1.55, 62)) == 1)
# ---- O1 ----
map.set_op(op, {"op": "outset"})
_ok("O1 outset: the boss, y 2.2 inside the cutter, is solid once", _count(t, Vector3(62, 2.2, 62)) == 1, _count(t, Vector3(62, 2.2, 62)))
_ok("O1 ... above depth (y 2.6) is air", _count(t, Vector3(62, 2.6, 62)) == 0)
_ok("O1 ... the target is whole under it (y 1.9 solid once)", _count(t, Vector3(62, 1.9, 62)) == 1, _count(t, Vector3(62, 1.9, 62)))
_ok("O1 ... no boss outside the cutter (x 60.5, y 2.2)", _count(t, Vector3(60.5, 2.2, 60.5)) == 0)
# ---- P1 ----
var k1 := _keys(t)
persist.refold()
_ok("P1 a second fold names the same pieces", _keys(t) == k1, k1.size())
# ---- U1 ----
persist.undo_last()
persist.refold()
_ok("U1 Ctrl+Z of the switch: the pocket is back (y 1.9 air)", _count(t, Vector3(62, 1.9, 62)) == 0)