OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_kernel_finite.gd

184 lines · gd ref:
extends Node
## GATE: the mesh-time material kernel (#90, Settings > Terrain Material "Vertex")
## writes only FINITE values, for every brush. ashatron's log (2026-10-01) showed
## "[GPU Mesh] material kernel wrote 6/9/15 non-finite values (zeroed)" during box
## carves; dc-terrain zeroes them on readback, so the bake silently loses the
## material at those vertices.
##
##   "$G" --path . --rendering-driver vulkan --resolution 1280x720 res://scenes/test/gate_kernel_finite.tscn
##
## Mountain with the kernel forced ON (mountain_terrain_material = 2), then at three
## sites near the spawn: 8 stepped carves + 8 on ONE spot (the "spam" case) with the
## hard sphere, the smooth sphere and the box. Fails if DCTerrain.
## get_kernel_nonfinite_stats().values grew; prints where it grew and on which kind
## of vertex (zero qef normal / non-finite position).
## ⛔ NOT HEADLESS (the mountain's field is GPU-only).

const MAP := "res://scenes/maps/map_mountain.tscn"
const MAP_ID := "map_mountain"
const PROBE := "res://scenes/test/terrain_probe.gd"
const R := 3.0
const SETTLE_FRAMES := 60
const SETTLE_CAP_S := 90.0
const REPLAY := "res://scenes/test/data/ashatron_edits_2026-10-01.txt"

var _map: Node
var _cam: Camera3D


func _ready() -> void:
	_run.call_deferred()


func _since(t0: int) -> float:
	return (Time.get_ticks_msec() - t0) / 1000.0


func _terrain() -> Node:
	return _map.get_node_or_null(^"MountainTerrain")


func _settle() -> void:
	var tr := _terrain()
	var t0 := Time.get_ticks_msec()
	var calm := 0
	while calm < SETTLE_FRAMES and _since(t0) < SETTLE_CAP_S:
		await get_tree().process_frame
		var ok: bool = bool(tr.call("is_lod_settled")) and int(tr.call("get_pending_count")) == 0 \
				and int(tr.call("get_worker_queue_depth")) == 0
		calm = calm + 1 if ok else 0


func _stats() -> Dictionary:
	return _terrain().call("get_kernel_nonfinite_stats")


func _fmt(d: Dictionary) -> String:
	return "values %d, verts %d (zero normal %d, bad position %d), chunks %d" % [
			d["values"], d["verts"], d["verts_zero_n"], d["verts_bad_p"], d["chunks"]]


func _run() -> void:
	if DisplayServer.get_name() == "headless":
		print("RESULT: SKIP (needs a display)")
		get_tree().quit(2)
		return
	GameState.enter_map(MAP_ID)
	var mgr := TerrainEditManager.new()
	mgr.name = "TerrainEditManager"
	add_child(mgr)
	_cam = Camera3D.new()
	_cam.far = 20000.0
	add_child(_cam)
	_cam.current = true
	_map = (load(MAP) as PackedScene).instantiate()
	_map.set("mountain_terrain_material", 2)   # Vertex: the kernel runs
	add_child(_map)
	var tr := _terrain()
	if tr == null or not tr.has_method("get_kernel_nonfinite_stats"):
		print("[GATE-KF] FAIL: no MountainTerrain, or a dc-terrain without get_kernel_nonfinite_stats")
		get_tree().quit(1)
		return
	var spawn := Vector3(0, 250, 0)
	var sp := get_tree().get_nodes_in_group("spawn_points")
	if not sp.is_empty():
		spawn = (sp[0] as Node3D).global_position
	_cam.global_position = spawn + Vector3(0, 2, 0)
	_cam.look_at(spawn + Vector3(20, 0, 0))
	await _settle()
	if String(tr.call("get_material_kernel")).is_empty():
		print("[GATE-KF] FAIL: the kernel is not set (mesh bake off?)")
		get_tree().quit(1)
		return
	var base := _stats()
	print("[GATE-KF] after load: %s" % _fmt(base))

	var Probe = load(PROBE)
	var fails := 0
	var plan := [["hard sphere", Vector3(spawn.x + 20.0, 0, spawn.z)],
			["smooth sphere", Vector3(spawn.x + 20.0, 0, spawn.z + 30.0)],
			["box", Vector3(spawn.x + 20.0, 0, spawn.z + 60.0)]]
	for site_def in plan:
		var kind: String = site_def[0]
		var col: Vector3 = site_def[1]
		var s = _map.call("_find_surface_gpu", Probe, tr, Vector3(col.x, 1500.0, col.z), 64.0)
		if s == null:
			print("[GATE-KF] no surface at %s" % str(col)); get_tree().quit(3); return
		var f = _map.call("_find_surface_gpu", Probe, tr, Vector3(col.x, float(s), col.z), 1.0)
		var site := Vector3(col.x, float(f if f != null else s), col.z)
		_cam.global_position = site + Vector3(0, 25, 25)
		_cam.look_at(site)
		await _settle()
		var before := _stats()
		for k in 16:
			var c := site + Vector3(1.5 * mini(k, 7), -1.0, 0.0)   # 8 stepped, then 8 on the last spot
			match kind:
				"hard sphere": mgr.call("apply_sphere", c, R, -3.0, 1)
				"smooth sphere": mgr.call("apply_sphere", c, R, -3.0, 0)
				_: mgr.call("apply_edit", c, Vector3(R, R, R), -3.0)
			await get_tree().create_timer(0.2).timeout
		await _settle()
		var after := _stats()
		var grew := int(after["values"]) - int(before["values"])
		print("[GATE-KF] %s %s: +%d non-finite values (+%d verts: zero normal +%d, bad position +%d)" % [
				"PASS" if grew == 0 else "FAIL", kind, grew,
				int(after["verts"]) - int(before["verts"]),
				int(after["verts_zero_n"]) - int(before["verts_zero_n"]),
				int(after["verts_bad_p"]) - int(before["verts_bad_p"])])
		if grew != 0:
			fails += 1
	# THE ASHATRON SESSION, edit for edit (data/ashatron_edits_2026-10-01.txt: 600
	# carves from its log). The camera follows each carve, as the player did.
	var rep := await _replay(mgr)
	if rep > 0:
		fails += 1
	var end := _stats()
	print("[GATE-KF] total: %s" % _fmt(end))
	if int(base["values"]) != 0:
		print("[GATE-KF] FAIL: non-finite values before any carve")
		fails += 1
	print("RESULT: %s" % ("PASS" if fails == 0 else "FAIL"))
	get_tree().quit(0 if fails == 0 else 1)


## Replays REPLAY; returns how many non-finite values it produced. Growth is
## attributed to the kind of the edit applied just before it was seen.
func _replay(mgr: Node) -> int:
	var f := FileAccess.open(REPLAY, FileAccess.READ)
	var edits: Array = []
	while f != null and not f.eof_reached():
		var l := f.get_line().strip_edges()
		if l.is_empty() or l.begins_with("#"):
			continue
		var t := l.split(" ")
		edits.append([t[0], Vector3(float(t[1]), float(t[2]), float(t[3]))])
	var t0 := _stats()
	var by_kind := {}
	var first := -1
	var prev := int(t0["values"])
	for i in edits.size():
		var kind: String = edits[i][0]
		var c: Vector3 = edits[i][1]
		_cam.global_position = c + Vector3(0, 12, 12)
		_cam.look_at(c)
		match kind:
			"hard": mgr.call("apply_sphere", c, R, -3.0, 1)
			"smooth": mgr.call("apply_sphere", c, R, -3.0, 0)
			_: mgr.call("apply_edit", c, Vector3(R, R, R), -3.0)
		for k in 4:
			await get_tree().process_frame
		var v := int(_stats()["values"])
		if v > prev:
			by_kind[kind] = int(by_kind.get(kind, 0)) + (v - prev)
			if first < 0:
				first = i
				print("[GATE-KF] replay: first non-finite after edit %d (%s at %s)" % [i, kind, str(c)])
			prev = v
	await _settle()
	var t1 := _stats()
	var grew := int(t1["values"]) - int(t0["values"])
	print("[GATE-KF] %s ashatron replay (%d edits): +%d non-finite values (+%d verts: zero normal +%d, bad position +%d), by kind %s" % [
			"PASS" if grew == 0 else "FAIL", edits.size(), grew, int(t1["verts"]) - int(t0["verts"]),
			int(t1["verts_zero_n"]) - int(t0["verts_zero_n"]), int(t1["verts_bad_p"]) - int(t0["verts_bad_p"]), str(by_kind)])
	return grew