OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_terrace_graph.gd

130 lines · gd ref:
extends SceneTree
## GATE — TerraceGraph, the large-scale terraced landform (2026-10-06).
##   T1  the builder's graph and the bridged scene's (blender/geiss_terraces.tscn) both
##       compile to proc_density; the scene carries every TKNOBS knob as a labelled node
##   T2  CONTINUOUS: along transects at 0.05 m, the terraced height never jumps (max step
##       far below a terrace's height). NEGATIVE CONTROL: sharpness 1e4 makes the snap a
##       near-sawtooth and MUST fail the same check — the first undersea graph's floors
##       were exactly such a jump and meshed as floating razor sheets
##   T3  TERRACED: most of the ground lies on a tread (the snap within 0.04 of a level)
##   T6  RANDOM HEIGHTS: the tread levels' spacings lie in step * (1 ± step_random) and
##       actually spread across it (not one uniform step)
##   T4  RELIEF: the smooth field's p5..p95 over a 40 km grid is the `relief` knob ±15 %
##   T5  the surface is where the density says: d(x, T(x)) == 0 and d is +below / -above
## CPU, through evaluate_socket on the builder 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_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)


class Sampler:
	var b := TerraceGraph.new()
	var ids: Dictionary
	func _init(knobs: Dictionary = {}) -> void:
		ids = b.build(ProcNodeGraph.new(), Vector3.ZERO, knobs)
	func at(p: Vector3, what: String = "out") -> float:
		b.g.get_node(ids["p"]).set("value", p)
		return float(b.g.evaluate_socket(ids[what], 0))


## Largest |dT| between neighbours 0.05 m apart along three 400 m transects.
func _max_jump(s: Sampler) -> float:
	var worst := 0.0
	for z in [130.0, -2710.0, 5520.0]:
		var prev := s.at(Vector3(-200.0, 0.0, z), "T")
		var x := -200.0
		while x < 200.0:
			x += 0.05
			var t := s.at(Vector3(x, 0.0, z), "T")
			worst = maxf(worst, absf(t - prev))
			prev = t
	return worst


func _run() -> void:
	var step := float(TerraceGraph.TKNOBS["step"])
	# ---- T1 ----
	var built := TerraceGraph.make_terraces()
	_p(String(built.compile_to_glsl_function("proc_density", false, PackedStringArray())).contains("float proc_density"),
		"T1 the builder's graph compiles to proc_density")
	var saved := TerraceGraph.load_terraces()
	_p(saved != null and String(saved.compile_to_glsl_function("proc_density", false, PackedStringArray())).contains("float proc_density"),
		"T1 %s holds a graph that compiles" % TerraceGraph.SCENE_T)
	var have := UnderseaGeissGraph.knobs_from(saved)
	var missing: Array = []
	for key: String in TerraceGraph.TKNOBS:
		if not have.has(key):
			missing.append(key)
	_p(missing.is_empty(), "T1 the scene carries every knob as a labelled node (%d)" % have.size(), missing)

	var s := Sampler.new()
	# ---- T2 ----
	var jump := _max_jump(s)
	_p(jump < 0.25 * step, "T2 the terraced height is continuous (max step over 0.05 m %.2f m, terrace %.0f m)" % [jump, step])
	var saw := _max_jump(Sampler.new({"sharpness": 10000.0}))
	_p(saw >= 0.25 * step, "T2 negative control: a near-sawtooth snap FAILS the same check (max step %.2f m)" % saw)
	# ---- T3 + T4 ----
	var hs: Array[float] = []
	var levels := {}
	var on_tread := 0
	var n := 0
	for i in 41:
		for kk in 41:
			var p := Vector3(-20000.0 + i * 1000.0 + 13.7, 0.0, -20000.0 + kk * 1000.0 + 7.1)
			hs.append(s.at(p, "H"))
			var sn := s.at(p, "snap")
			if sn < 0.04 or sn > 0.96:
				on_tread += 1
			n += 1
	_p(float(on_tread) / n > 0.7, "T3 most of the ground is on a tread (%.0f %% with the snap within 0.04 of a level)" % (100.0 * on_tread / n))
	# ---- T6 ----
	# A dense transect: H is continuous, so walking it at 2 m visits EVERY level between
	# its lowest and highest point, and consecutive collected levels are true neighbours
	# (a coarse grid skips levels, and a skipped gap is indistinguishable by size).
	var x := -20000.0
	while x < 20000.0:
		levels[snappedf(s.at(Vector3(x, 0.0, 1234.5), "lo"), 0.001)] = true
		x += 2.0
	var lv: Array = levels.keys()
	lv.sort()
	var gmin := 1e9
	var gmax := 0.0
	for i in range(1, lv.size()):
		var gap := float(lv[i]) - float(lv[i - 1])
		gmin = minf(gmin, gap)
		gmax = maxf(gmax, gap)
	var r := float(TerraceGraph.TKNOBS["step_random"])
	_p(lv.size() >= 8 and gmin >= step * (1.0 - r) - 0.01 and gmax <= step * (1.0 + r) + 0.01,
		"T6 every step height is within step * (1 ± step_random) = %.0f..%.0f m" % [step * (1.0 - r), step * (1.0 + r)],
		"%d levels, gaps %.1f..%.1f m" % [lv.size(), gmin, gmax])
	_p(gmax - gmin >= step * r, "T6 the step heights are RANDOM, not uniform (gaps span %.1f m)" % (gmax - gmin))
	hs.sort()
	var span := hs[int(n * 0.95)] - hs[int(n * 0.05)]
	var relief := float(TerraceGraph.TKNOBS["relief"])
	_p(absf(span - relief) <= 0.15 * relief, "T4 the smooth field's p5..p95 is the relief knob ±15 %",
		"%.0f m against %.0f m (F_SPAN %.3f; measured span in F units %.3f)" % [span, relief, TerraceGraph.F_SPAN, span / relief * TerraceGraph.F_SPAN])
	# ---- T5 ----
	var bad := 0
	for i in 20:
		var p := Vector3(-3000.0 + i * 311.0, 0.0, 977.0 - i * 129.0)
		var t := s.at(p, "T")
		var on := absf(s.at(Vector3(p.x, t, p.z)))
		if on > 1e-3 or s.at(Vector3(p.x, t - 1.0, p.z)) <= 0.0 or s.at(Vector3(p.x, t + 1.0, p.z)) >= 0.0:
			bad += 1
	_p(bad == 0, "T5 d is 0 on T, solid 1 m below, air 1 m above (20 columns)", bad)