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