OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_brush_narrow_reads.gd

143 lines · gd ref:
extends SceneTree
## DCLevel's NARROW READS equal the get_brush loops they replace (hyper-games #127).
##
##     "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_brush_narrow_reads.gd
##
## get_brushes_bounds / count_clearance exist because the organizer read one field of
## get_brush (~60 us a brush) for every brush on every fold. They must answer EXACTLY
## what those loops answered — the same floats, not merely close — so the reference
## below is each loop as it stood (map_dc_level selection_centre / selection_size,
## OutlinerModel._group_label), run on the same ids:
##   all live brushes (60 boxes at non-round offsets, so the centre sum carries rounding),
##   a TOMBSTONED one (get_brush still answers it), an unknown id, a repeat, the empty set,
##   and reversed order (the centre is summed in the order given).
## Anti-absence: the level must hold clearance AND sky brushes and the tombstone must be
## in the set, or the comparisons prove nothing.

var _failures: Array[String] = []
var _frame := 0


func _check(label: String, ok: bool, detail: String) -> void:
	if not ok:
		_failures.append("%s (%s)" % [label, detail])
	print("  %-52s %s  %s" % [label, "OK  " if ok else "FAIL", detail])


static func _box(lo: Vector3, hi: Vector3) -> PackedFloat32Array:
	return PackedFloat32Array([
		-1, 0, 0, -lo.x,   1, 0, 0, hi.x,
		0, -1, 0, -lo.y,   0, 1, 0, hi.y,
		0, 0, -1, -lo.z,   0, 0, 1, hi.z,
	])


## map_dc_level.selection_centre's loop, verbatim but for its inputs.
static func _ref_centre(level: Node, ids: Array) -> Vector3:
	var sum := Vector3.ZERO
	var n := 0
	for b: int in ids:
		var d: Dictionary = level.get_brush(b)
		if d.has("center"):
			sum += d["center"] as Vector3
			n += 1
	return sum / float(n) if n > 0 else Vector3.ZERO


## map_dc_level.selection_size's loop, verbatim but for its inputs.
static func _ref_size(level: Node, ids: Array) -> Vector3:
	var lo := Vector3.INF
	var hi := -Vector3.INF
	var any := false
	for b: int in ids:
		var d: Dictionary = level.get_brush(b)
		if not d.has("aabb_min"):
			continue
		lo = lo.min(d["aabb_min"] as Vector3)
		hi = hi.max(d["aabb_max"] as Vector3)
		any = true
	return (hi - lo) if any else Vector3.ZERO


## OutlinerModel._group_label's count, verbatim but for its inputs.
static func _ref_clearance(level: Node, ids: PackedInt32Array) -> int:
	var clearance := 0
	for b: int in ids:
		if bool((level.get_brush(b) as Dictionary).get("clearance", false)):
			clearance += 1
	return clearance


func _compare(level: Node, label: String, ids: Array) -> void:
	var p := PackedInt32Array(ids)
	var bb: Dictionary = level.get_brushes_bounds(p)
	var c: Vector3 = bb["center"]
	var sz: Vector3 = (bb["aabb_max"] as Vector3) - (bb["aabb_min"] as Vector3) if int(bb["count"]) > 0 else Vector3.ZERO
	var rc := _ref_centre(level, ids)
	var rs := _ref_size(level, ids)
	_check("%s: centre bit-equal" % label, c == rc, "%s vs %s" % [str(c), str(rc)])
	_check("%s: size bit-equal" % label, sz == rs, "%s vs %s" % [str(sz), str(rs)])
	var k: int = level.count_clearance(p)
	var rk := _ref_clearance(level, p)
	_check("%s: clearance count" % label, k == rk, "%d vs %d" % [k, rk])


func _process(_delta: float) -> bool:
	_frame += 1
	if _frame < 3:
		return false
	print("brush narrow reads gate")
	var holder := Node3D.new()
	root.add_child(holder)
	var level := ClassDB.instantiate("DCLevel") as Node3D
	holder.add_child(level)
	if not level.has_method("get_brushes_bounds") or not level.has_method("count_clearance"):
		_check("the loaded dc-terrain has the narrow reads", false, "rebuild the .so")
		return _finish()
	var rng := RandomNumberGenerator.new()
	rng.seed = 127
	for i in 60:
		var lo := Vector3(rng.randf_range(-300, 300), rng.randf_range(-40, 40), rng.randf_range(-300, 300))
		var hi := lo + Vector3(rng.randf_range(0.03, 9), rng.randf_range(0.03, 9), rng.randf_range(0.03, 9))
		# Every 7th a clearance volume, every 11th sky: both flags present among the ids.
		level.add_brush(_box(lo, hi), 0, PackedInt32Array(), 0, i % 11 == 3, "", i % 7 == 2)
	level.rebuild()
	var live: PackedInt32Array = level.get_live_brush_ids()
	var dead := live[5]
	level.remove_brush(dead)
	live = level.get_live_brush_ids()
	var all: Array = Array(live)
	all.append(dead)
	var sky := 0
	for b: int in all:
		sky += 1 if bool((level.get_brush(b) as Dictionary).get("sky", false)) else 0
	_check("anti-absence: clearance brushes among the ids", _ref_clearance(level, PackedInt32Array(all)) > 0,
		"%d" % _ref_clearance(level, PackedInt32Array(all)))
	_check("anti-absence: sky brushes among the ids", sky > 0, "%d" % sky)
	_check("anti-absence: the tombstone still answers get_brush", (level.get_brush(dead) as Dictionary).has("center"),
		"brush %d" % dead)
	_check("anti-absence: the tombstone is not live", not live.has(dead), "brush %d" % dead)
	_compare(level, "all + tombstone", all)
	var rev := all.duplicate()
	rev.reverse()
	_compare(level, "reversed", rev)
	_compare(level, "with unknown id + repeat", [live[0], 999999, live[0], live[1]])
	_compare(level, "empty", [])
	_compare(level, "unknown only", [999999])
	var bb: Dictionary = level.get_brushes_bounds(PackedInt32Array())
	_check("empty: count 0, zero vectors", int(bb["count"]) == 0 and bb["center"] == Vector3.ZERO
		and bb["aabb_min"] == Vector3.ZERO and bb["aabb_max"] == Vector3.ZERO, str(bb))
	return _finish()


func _finish() -> bool:
	if _failures.is_empty():
		print("PASS brush narrow reads")
		quit(0)
	else:
		print("FAIL brush narrow reads: %d" % _failures.size())
		for f in _failures:
			print("  - " + f)
		quit(1)
	return true