OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_op_inset.gd

122 lines · gd ref:
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)