OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_op_journal.gd

168 lines · gd ref:
extends Node
## HEADLESS GATE — LIVE CUTS through the REAL journal path (docs/cutting-and-operations.md
## §9a): persist.record("op.*") -> GameState -> LevelPersistence._to_record -> ShapeFold
## (cutter pieces) + OpFold (brush.cut, folded last) -> DCLevel.
##
## What "live" has to mean, asserted by RAY through the level (solid = a hit):
##   a cut opens a door in a box shape, and the cutter itself is not solid;
##   moving the CUTTER moves the door; resizing the TARGET keeps the door;
##   op.set changes subtract -> intersect; undo of the op gives the whole wall back
##   and the cutter shape becomes solid again; a second peer folds the same pieces.
##
## RUN AS A SCENE (level_persistence.gd names GameState at compile time):
##   "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_op_journal.tscn

var fails := 0
var checks := 0
var level: Node3D = null


func _ok(what: String, cond: bool, note: String = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-64s %s  %s" % [what, "OK" if cond else "**FAIL**", note])


func _ready() -> void:
	await get_tree().process_frame
	_run()
	if checks < 16:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE op_journal: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


## Solid at (x, y) through the wall: a ray along -z from in front of it.
func _solid(lv: Node, x: float, y: float, z0 := 6.0, z1 := -6.0) -> bool:
	return not (lv.raycast(Vector3(x, y, z0), Vector3(x, y, z1)) as Dictionary).is_empty()


## Does some live solid piece of group `g` contain `p` (n·p <= d for every plane)?
func _wall_contains(lv: Node, g: String, p: Vector3) -> bool:
	for id: int in (lv.get_group_brush_ids(g) as PackedInt32Array):
		var b: Dictionary = lv.get_brush(id)
		if bool(b.get("cutter", false)) or bool(b.get("clearance", false)):
			continue
		var pl: PackedFloat32Array = b.get("planes", PackedFloat32Array())
		var inside := true
		for k in range(0, pl.size(), 4):
			if Vector3(pl[k], pl[k + 1], pl[k + 2]).dot(p) > pl[k + 3] + 1e-4:
				inside = false
				break
		if inside:
			return true
	return false


func _live_solid(lv: Node, g: String) -> int:
	var n := 0
	for id: int in (lv.get_group_brush_ids(g) as PackedInt32Array):
		var b: Dictionary = lv.get_brush(id)
		if not bool(b.get("clearance", false)) and not bool(b.get("cutter", false)):
			n += 1
	return n


func _keys(lv: Node) -> Array:
	var out: Array = []
	for id: int in (lv.get_live_brush_ids() as PackedInt32Array):
		out.append(String(lv.get_brush_key(id)))
	out.sort()
	return out


func _box(persist: Node, origin: Vector3, size: Vector3) -> String:
	return persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
		"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, origin)),
		"size": [size.x, size.y, size.z], "material_id": 2, "level_id": 0})


func _run() -> void:
	if not ClassDB.class_exists("DCLevel"):
		_ok("DCLevel registered", false)
		return
	if not ClassDB.class_has_method("DCLevel", "get_cutter_brush_ids"):
		_ok("this DCLevel build knows cutters (dc-terrain f0efefd+)", false)
		return
	var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
	gs.route("world:level:*", 1, "peer")          # 1 == Channel.DURABLE
	var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	level = ClassDB.instantiate("DCLevel")
	add_child(level)
	var persist := LevelPersistence.new()
	persist.level = level
	add_child(persist)

	print("-- a wall (box shape) and a cutter (box shape) --")
	var wall: String = _box(persist, Vector3.ZERO, Vector3(8, 4, 1))
	var cutter: String = _box(persist, Vector3(3, -1, -1), Vector3(2, 4, 3))
	persist.refold()
	_ok("before any op the cutter is a solid box", _solid(level, 4, 1, 6, 1.5))

	print("-- op.create subtract: the door opens, the cutter stops being solid --")
	persist.begin_gesture("Live cut")
	var op: String = persist.record("op.create", {"op": "subtract", "cutter": cutter, "targets": [wall]})
	persist.end_gesture()
	_ok("op.create recorded", op != "")
	var st: Dictionary = persist.refold()
	_ok("nothing skipped", int(st.get("skipped", 0)) == 0, str(st))
	_ok("the door is air (ray passes through)", not _solid(level, 4, 1))
	_ok("the wall beside it is solid", _solid(level, 1, 1) and _solid(level, 7, 1))
	_ok("the lintel above the door is solid", _solid(level, 4, 3.5))
	_ok("the cutter is a cutter (1 cutter brush)", (level.get_cutter_brush_ids() as PackedInt32Array).size() == 1)
	_ok("the wall is now several pieces, all in its group", _live_solid(level, wall) >= 3, "%d" % _live_solid(level, wall))
	_ok("a cutter is pickable only with include_cutters",
		not (level.raycast(Vector3(4, 1, 6), Vector3(4, 1, 1.5), false, true) as Dictionary).is_empty())
	var n_cut := _live_solid(level, wall)
	persist.refold()
	persist._dirty = true
	persist.refold()
	_ok("a re-fold changes nothing", _live_solid(level, wall) == n_cut)

	print("-- live: move the CUTTER, the door follows --")
	persist.record("shape.xform", {"target": cutter,
		"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, Vector3(5.5, -1, -1)))})
	persist.refold()
	_ok("the old door is solid again", _solid(level, 4, 1))
	_ok("the new door (x 5.5..7.5) is air", not _solid(level, 6.5, 1))

	print("-- live: resize the TARGET, it is re-cut --")
	persist.record("shape.size", {"target": wall, "size": [10.0, 4.0, 1.0]})
	persist.refold()
	_ok("the door is still air", not _solid(level, 6.5, 1))
	_ok("the wall reaches its new end (x 9.5)", _solid(level, 9.5, 1))

	print("-- op.set: subtract -> intersect --")
	persist.record("op.set", {"target": op, "op": "intersect"})
	persist.refold()
	_ok("intersect: only the overlap is solid", _solid(level, 6.5, 1) and not _solid(level, 1, 1))

	print("-- a second peer folding the same journal names the same pieces --")
	var level2: Node3D = ClassDB.instantiate("DCLevel")
	add_child(level2)
	var persist2 := LevelPersistence.new()
	persist2.level = level2
	add_child(persist2)
	persist2.refold()
	_ok("identical live key sets", _keys(level) == _keys(level2), "%d vs %d" % [_keys(level).size(), _keys(level2).size()])

	print("-- undo the op: the wall is whole, the cutter solid again --")
	persist.undo_op(op)
	persist.refold()
	# Not a ray: the cutter, solid again, stands exactly where the door was.
	_ok("the wall's own piece fills where the door was", _wall_contains(level, wall, Vector3(6.5, 1, 0.5)))
	_ok("no cutter brushes remain", (level.get_cutter_brush_ids() as PackedInt32Array).size() == 0)
	_ok("the cutter shape is solid again", _live_solid(level, cutter) == 1)
	_ok("the wall is one piece again", _live_solid(level, wall) == 1, "%d" % _live_solid(level, wall))

	print("-- flatten refuses while a live op exists --")
	persist.redo_op(op)
	persist.refold()
	var fl: Dictionary = persist.flatten_map()
	_ok("flatten_map refused, nothing changed", fl.has("error") and String(fl["error"]).contains("live cuts"), str(fl))
	_ok("labels: op kinds have History names", String(persist.KIND_LABELS.get("op.create", "")) == "Live cut")