OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_terrace_geiss_graph.gd

128 lines · gd ref:
extends SceneTree
## GATE — TerraceGeissGraph, the Geiss field draped on the random terraces (2026-10-06).
##   C1  the combined graph compiles to proc_density (both builders in one graph, every
##       node on a GPU path)
##   C2  the composition is EXACT: combined(x, y, z) == Geiss(x, y - T(x, z), z), sampled
##       against a separate TerraceGraph and a separate UnderseaGeissGraph (ground_y 0)
##   C3  the knobs stay distinct: every terrace knob as "knob:<name>", every Geiss knob as
##       "knob:geiss.<name>"
##   C4  a Geiss knob reaches the field (prewarp 0 changes it) and a terrace knob does too
##   C5  CAVES: nothing carved where the mask is 0 or above the roof line (entrances only
##       via walls); beside risers below the roof, chambers hollow 5-60 % of the rock;
##       negative control: an impossible threshold carves nothing
## CPU, through evaluate_socket with a constant Vector in place of Position.
##
## Run: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_terrace_geiss_graph.gd

var ok := true
var checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		ok = false


func _init() -> void:
	_run()
	print("\n%d checks — %s" % [checks, "PASS" if ok else "FAIL"])
	quit(0 if ok else 1)


func _at(ids: Dictionary, p: Vector3, what: String = "out") -> float:
	var g: ProcNodeGraph = ids["graph"] if ids.has("graph") else ids["_g"]
	g.get_node(ids["p"]).set("value", p)
	return float(g.evaluate_socket(ids[what], 0))


func _run() -> void:
	# ---- C1 ----
	var shipped := TerraceGeissGraph.make()
	_p(String(shipped.compile_to_glsl_function("proc_density", false, PackedStringArray())).contains("float proc_density"),
		"C1 the combined graph compiles to proc_density", shipped.get_node_count())
	# EXACTLY ONE Group Output, fed by "out". The GPU compiler takes the FIRST one; with a
	# sub-builder's left in the graph the map rendered the pre-cave surface, and every
	# other check here passed because they evaluate nodes by id, not the output.
	var probe := TerraceGeissGraph.build(ProcNodeGraph.new(), null)
	var pg: ProcNodeGraph = probe["graph"]
	var gos: Array[int] = []
	for i in pg.get_node_count():
		if pg.get_node(i) is GroupOutputNode: gos.append(i)
	var fed := -1
	var cf := pg.get_connections_flat()
	for i in range(0, cf.size(), 4):
		if gos.size() == 1 and cf[i + 2] == gos[0] and cf[i + 3] == 0: fed = cf[i]
	_p(gos.size() == 1 and fed == int(probe["out"]), "C1 exactly ONE Group Output, fed by out (the caves)",
		"%d outputs, fed by %d, out %d" % [gos.size(), fed, int(probe["out"])])
	# ---- C2 ----
	var c := TerraceGeissGraph.build(ProcNodeGraph.new(), Vector3.ZERO)
	var tb := TerraceGraph.new()
	var t := tb.build(ProcNodeGraph.new(), Vector3.ZERO)
	t["_g"] = tb.g
	var gb := UnderseaGeissGraph.new()
	var gg := gb.build(ProcNodeGraph.new(), Vector3.ZERO, {"ground_y": 0.0})
	gg["_g"] = gb.g
	var worst := 0.0
	var rng := RandomNumberGenerator.new()
	rng.seed = 11
	for i in 40:
		var x := rng.randf_range(-5000, 5000)
		var z := rng.randf_range(-5000, 5000)
		var tv := _at(t, Vector3(x, 0, z), "T")
		var y := tv + rng.randf_range(-30.0, 30.0)
		worst = maxf(worst, absf(_at(c, Vector3(x, y, z), "surface") - _at(gg, Vector3(x, y - tv, z))))
	_p(worst < 1e-3, "C2 surface(x,y,z) == Geiss(x, y - T, z) at 40 points within 30 m of the terrace", worst)
	# ---- C3 ----
	var kn := UnderseaGeissGraph.knobs_from(shipped)
	var missing: Array = []
	for key: String in TerraceGraph.TKNOBS:
		if not kn.has(key): missing.append(key)
	for key: String in UnderseaGeissGraph.KNOBS:
		if not kn.has(TerraceGeissGraph.GEISS_PREFIX + key): missing.append(TerraceGeissGraph.GEISS_PREFIX + key)
	_p(missing.is_empty(), "C3 terrace knobs bare, Geiss knobs prefixed '%s' (%d knobs)" % [TerraceGeissGraph.GEISS_PREFIX, kn.size()], missing)
	# ---- C4 ----
	var flat := TerraceGeissGraph.build(ProcNodeGraph.new(), Vector3.ZERO, {}, {"prewarp": 0.0})
	var steep := TerraceGeissGraph.build(ProcNodeGraph.new(), Vector3.ZERO, {"step": 40.0}, {})
	var dg := 0
	var dt := 0
	for i in 20:
		var p := Vector3(rng.randf_range(-5000, 5000), 0.0, rng.randf_range(-5000, 5000))
		p.y = _at(c, p, "T") + rng.randf_range(-10.0, 10.0)
		if absf(_at(flat, p) - _at(c, p)) > 0.01: dg += 1
		if absf(_at(steep, p) - _at(c, p)) > 0.01: dt += 1
	_p(dg > 10 and dt > 10, "C4 a Geiss knob (prewarp) and a terrace knob (step) both reach the field", "%d / %d of 20" % [dg, dt])
	# ---- C5 caves ----
	var roof := float(TerraceGeissGraph.CAVE_KNOBS["cave_roof"])
	var none := TerraceGeissGraph.build(ProcNodeGraph.new(), Vector3.ZERO, {}, {}, {"cave_threshold": 1000.0})
	var leak := 0          # carved where the mask is 0, or above the roof line
	var masked := 0        # samples with the mask fully on
	var carved := 0        # ...of which rock (surface > 0) became hollow (out < 0)
	var rock := 0
	var ctl := 0           # negative control: carved with an impossible threshold
	for i in 3000:
		var p := Vector3(rng.randf_range(-6000, 6000), 0.0, rng.randf_range(-6000, 6000))
		var tv := _at(c, p, "T")
		p.y = tv + rng.randf_range(-70.0, 10.0)
		var m := _at(c, p, "cave_mask")
		var sv := _at(c, p, "surface")
		var ov := _at(c, p)
		# CARVED = solid became hollow. Not "value changed": deep in the rock the guarded
		# cut (~100 - L) can be the min() and lower d's VALUE while it stays solid —
		# measured 13 such samples, none of them a sign change. Near the surface (|d| < 5,
		# where the mesh is) the value must be exactly d's, or the walls move.
		if (m == 0.0 or p.y > tv - roof) and ((sv > 0.0) != (ov > 0.0) or (absf(sv) < 5.0 and absf(ov - sv) > 1e-4)):
			leak += 1
		if m >= 0.99 and sv > 0.0:
			masked += 1
			rock += 1
			if ov < 0.0:
				carved += 1
		if sv > 0.0 and _at(none, p) < 0.0:
			ctl += 1
	_p(leak == 0, "C5 nothing is carved where the mask is 0 or above the roof line (T - %.0f m): entrances via walls only" % roof, leak)
	_p(masked >= 30 and carved > 0.05 * rock and carved < 0.6 * rock,
		"C5 beside risers below the roof, chambers hollow 5-60 % of the rock", "%d of %d rock samples" % [carved, rock])
	_p(ctl == 0, "C5 negative control: an impossible cave_threshold carves nothing", ctl)