OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_undersea_erosion.gd

175 lines · gd ref:
extends SceneTree
## GATE — our undersea term inside the erosion / diffusion generator (§4 P3), on the
## REAL maps: their own DCTerrainGeneratorProcNodes, graphs and wrappers.
##   E1  the undersea graph compiled (get_undersea_error() == "") and the combined
##       density shader runs on the GPU — two graphs in one shader needs proc-nodes'
##       guarded library globals (without them: 'pn_terrain_offset' : redefinition)
##   E2  ABOVE the coast band (water_y - 10 m) the density is unchanged by it — the
##       shoreline stays a single-valued heightfield
##   E3  BELOW the band the term is present where there is sea; on a map with no sea
##       (diffusion: water below its lowest terrain) it touches nothing
## Each map's GPU density is sampled with the graph, then with it removed
## (set_undersea_graph(null) recompiles the same generator), at the same points.
##
## ⚠ WINDOWED (needs a RenderingDevice). Run: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 DISPLAY=:0 "$HYPER_TPS_GODOT" --path . --script res://scenes/test/gate_undersea_erosion.gd

const MAPS := ["res://scenes/maps/map_dc_erosion.tscn", "res://scenes/maps/map_dc_diffusion.tscn"]
const CELLS := 6
var XZ: Array = []   # a 7x7 grid over ±40 km, filled in _init — enough to hit ocean on both maps

var ok := true
var checks := 0
var _frame := 0
var _i := 0
var _map: Node


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:
	for i in 7:
		for j in 7:
			XZ.append(Vector2(-40000.0 + i * 13333.0, -40000.0 + j * 13333.0))


## Stages per map, the OFF samples frames later and TWICE (they must agree).
##
## ⛔ CORRECTED: this used to say a generator change reaches debug_gpu_density
## "asynchronously". It did not — DCGpuThread recompiled only when the shader
## source's POINTER changed, and a generator that rebuilt its GLSL string in place
## kept the same address, so the STALE shader answered (OFF == ON at points where the
## term is -35 m; later ON with the knob graphs). Fixed in dc-terrain (pointer OR
## text). E3 is what catches it: a stale baseline makes the term look absent.
const T_ON := 150
const T_OFF1 := 270
const T_OFF2 := 330

var _on: Dictionary = {}
var _off1: Dictionary = {}


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_load()
	elif _frame == T_ON:
		_stage_on()
	elif _frame == T_OFF1:
		_off1 = _sample()
	elif _frame == T_OFF2:
		_stage_off(_sample())
		_map.queue_free()
		_i += 1
		if _i >= MAPS.size():
			print("\n%d checks — %s" % [checks, "PASS" if ok else "FAIL"])
			quit(0 if ok else 1)
			return true
		_frame = 0
	return false


func _load() -> void:
	_map = (load(MAPS[_i]) as PackedScene).instantiate()
	root.add_child(_map)


func _blocks(terrain: Node, y0: float, vox: float) -> Array:
	var out: Array = []
	for xz: Vector2 in XZ:
		out.append(terrain.call("debug_gpu_density", Vector3(xz.x, y0, xz.y), vox, CELLS))
	return out


func _sample() -> Dictionary:
	var terrain: Node = _map.get_node("DCTerrain")
	var water: float = float(_map.get("_gen").call("get_submarine_water_y"))
	return {"shallow": _blocks(terrain, water - 10.0 - CELLS * 1.0, 1.0),   # top at water-10
		"deep": _blocks(terrain, water - 600.0, 100.0)}


func _name() -> String:
	return String(MAPS[_i]).get_file()


func _stage_on() -> void:
	var name := _name()
	var gen: Object = _map.get("_gen")
	_p(gen != null and _map.get_node_or_null("DCTerrain") != null, "%s: generator and terrain exist" % name)
	if gen == null:
		return
	_p(String(gen.call("get_undersea_error")) == "", "E1 %s: the undersea graph compiled" % name,
		String(gen.call("get_undersea_error")))
	var glsl := String(gen.call("get_compiled_glsl"))
	_p(glsl.contains("undersea_detail(vec3(x, y, z))"), "E1 %s: the map's density shader CALLS the undersea term" % name)
	_on = _sample()
	var ran := true
	for b: PackedFloat32Array in (_on["shallow"] as Array) + (_on["deep"] as Array):
		if b.size() != (CELLS + 1) * (CELLS + 1) * (CELLS + 1):
			ran = false
	_p(ran, "E1 %s: the combined density shader ran on the GPU (every block returned)" % name)
	# THE BASELINE IS "NEITHER": the undersea graph REPLACES the submarine fBm, so
	# removing the graph alone brings the fBm back — what E2 first measured (420 m):
	# the old term, gated by COLUMN depth, not ours.
	_map.set_meta("gate_graph", gen.call("get_undersea_graph"))
	_map.set_meta("gate_amp", float(gen.call("get_submarine_amp")))
	gen.call("set_undersea_graph", null)
	gen.call("set_submarine_amp", 0.0)


func _maxdiff(a_list: Array, b_list: Array) -> float:
	var m := 0.0
	for i in a_list.size():
		var a: PackedFloat32Array = a_list[i]
		var b: PackedFloat32Array = b_list[i]
		for j in mini(a.size(), b.size()):
			m = maxf(m, absf(a[j] - b[j]))
	return m


func _stage_off(off: Dictionary) -> void:
	var name := _name()
	var gen: Object = _map.get("_gen")
	gen.call("set_submarine_amp", float(_map.get_meta("gate_amp")))
	gen.call("set_undersea_graph", _map.get_meta("gate_graph"))
	if _on.is_empty():
		return
	var settle := _maxdiff(_off1["deep"], off["deep"]) + _maxdiff(_off1["shallow"], off["shallow"])
	_p(settle < 1e-3, "%s: the baseline shader has settled (two OFF samples agree)" % name, settle)
	var water: float = float(gen.call("get_submarine_water_y"))
	_p(_maxdiff(_on["shallow"], off["shallow"]) < 1e-3, "E2 %s: above the coast band the density is unchanged" % name,
		_maxdiff(_on["shallow"], off["shallow"]))
	var moved := 0
	var water_pts := 0
	var biggest := 0.0
	var s := CELLS + 1
	var deep_on: Array = _on["deep"]
	var deep_off: Array = off["deep"]
	for i in deep_on.size():
		var a: PackedFloat32Array = deep_on[i]
		var b: PackedFloat32Array = deep_off[i]
		for z in s:
			for y in s:
				if water - 600.0 + float(y) * 100.0 > water - 50.0:
					continue           # only the full-strength part of the band
				for x in s:
					var j := z * s * s + y * s + x
					if b[j] < 0.0:
						water_pts += 1          # below the water line and not rock: ocean
					var dd := absf(a[j] - b[j])
					biggest = maxf(biggest, dd)
					if dd > 0.5:
						moved += 1
	if water_pts == 0:
		# NO SEA ON THIS MAP: map_dc_diffusion's WATER_Y (-5900) is below its lowest
		# terrain (-5300) on purpose ("this map is about mountains"), so nothing is
		# underwater and the term must touch nothing — the cap is 0 over land.
		_p(moved == 0, "E3 %s: no sea here (water below the lowest terrain) — the term is inert, as it must be" % name, moved)
	else:
		_p(moved > 0, "E3 %s: below the band the undersea term is present (%d samples moved, max %.1f m; %d ocean samples)"
			% [name, moved, biggest, water_pts])