| … | … | @@ -0,0 +1,142 @@ |
| 1 | extends SceneTree |
| 2 | ## DCLevel's NARROW READS equal the get_brush loops they replace (hyper-games #127). |
| 3 | ## |
| 4 | ## "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_brush_narrow_reads.gd |
| 5 | ## |
| 6 | ## get_brushes_bounds / count_clearance exist because the organizer read one field of |
| 7 | ## get_brush (~60 us a brush) for every brush on every fold. They must answer EXACTLY |
| 8 | ## what those loops answered — the same floats, not merely close — so the reference |
| 9 | ## below is each loop as it stood (map_dc_level selection_centre / selection_size, |
| 10 | ## OutlinerModel._group_label), run on the same ids: |
| 11 | ## all live brushes (60 boxes at non-round offsets, so the centre sum carries rounding), |
| 12 | ## a TOMBSTONED one (get_brush still answers it), an unknown id, a repeat, the empty set, |
| 13 | ## and reversed order (the centre is summed in the order given). |
| 14 | ## Anti-absence: the level must hold clearance AND sky brushes and the tombstone must be |
| 15 | ## in the set, or the comparisons prove nothing. |
| 16 | |
| 17 | var _failures: Array[String] = [] |
| 18 | var _frame := 0 |
| 19 | |
| 20 | |
| 21 | func _check(label: String, ok: bool, detail: String) -> void: |
| 22 | if not ok: |
| 23 | _failures.append("%s (%s)" % [label, detail]) |
| 24 | print(" %-52s %s %s" % [label, "OK " if ok else "FAIL", detail]) |
| 25 | |
| 26 | |
| 27 | static func _box(lo: Vector3, hi: Vector3) -> PackedFloat32Array: |
| 28 | return PackedFloat32Array([ |
| 29 | -1, 0, 0, -lo.x, 1, 0, 0, hi.x, |
| 30 | 0, -1, 0, -lo.y, 0, 1, 0, hi.y, |
| 31 | 0, 0, -1, -lo.z, 0, 0, 1, hi.z, |
| 32 | ]) |
| 33 | |
| 34 | |
| 35 | ## map_dc_level.selection_centre's loop, verbatim but for its inputs. |
| 36 | static func _ref_centre(level: Node, ids: Array) -> Vector3: |
| 37 | var sum := Vector3.ZERO |
| 38 | var n := 0 |
| 39 | for b: int in ids: |
| 40 | var d: Dictionary = level.get_brush(b) |
| 41 | if d.has("center"): |
| 42 | sum += d["center"] as Vector3 |
| 43 | n += 1 |
| 44 | return sum / float(n) if n > 0 else Vector3.ZERO |
| 45 | |
| 46 | |
| 47 | ## map_dc_level.selection_size's loop, verbatim but for its inputs. |
| 48 | static func _ref_size(level: Node, ids: Array) -> Vector3: |
| 49 | var lo := Vector3.INF |
| 50 | var hi := -Vector3.INF |
| 51 | var any := false |
| 52 | for b: int in ids: |
| 53 | var d: Dictionary = level.get_brush(b) |
| 54 | if not d.has("aabb_min"): |
| 55 | continue |
| 56 | lo = lo.min(d["aabb_min"] as Vector3) |
| 57 | hi = hi.max(d["aabb_max"] as Vector3) |
| 58 | any = true |
| 59 | return (hi - lo) if any else Vector3.ZERO |
| 60 | |
| 61 | |
| 62 | ## OutlinerModel._group_label's count, verbatim but for its inputs. |
| 63 | static func _ref_clearance(level: Node, ids: PackedInt32Array) -> int: |
| 64 | var clearance := 0 |
| 65 | for b: int in ids: |
| 66 | if bool((level.get_brush(b) as Dictionary).get("clearance", false)): |
| 67 | clearance += 1 |
| 68 | return clearance |
| 69 | |
| 70 | |
| 71 | func _compare(level: Node, label: String, ids: Array) -> void: |
| 72 | var p := PackedInt32Array(ids) |
| 73 | var bb: Dictionary = level.get_brushes_bounds(p) |
| 74 | var c: Vector3 = bb["center"] |
| 75 | var sz: Vector3 = (bb["aabb_max"] as Vector3) - (bb["aabb_min"] as Vector3) if int(bb["count"]) > 0 else Vector3.ZERO |
| 76 | var rc := _ref_centre(level, ids) |
| 77 | var rs := _ref_size(level, ids) |
| 78 | _check("%s: centre bit-equal" % label, c == rc, "%s vs %s" % [str(c), str(rc)]) |
| 79 | _check("%s: size bit-equal" % label, sz == rs, "%s vs %s" % [str(sz), str(rs)]) |
| 80 | var k: int = level.count_clearance(p) |
| 81 | var rk := _ref_clearance(level, p) |
| 82 | _check("%s: clearance count" % label, k == rk, "%d vs %d" % [k, rk]) |
| 83 | |
| 84 | |
| 85 | func _process(_delta: float) -> bool: |
| 86 | _frame += 1 |
| 87 | if _frame < 3: |
| 88 | return false |
| 89 | print("brush narrow reads gate") |
| 90 | var holder := Node3D.new() |
| 91 | root.add_child(holder) |
| 92 | var level := ClassDB.instantiate("DCLevel") as Node3D |
| 93 | holder.add_child(level) |
| 94 | if not level.has_method("get_brushes_bounds") or not level.has_method("count_clearance"): |
| 95 | _check("the loaded dc-terrain has the narrow reads", false, "rebuild the .so") |
| 96 | return _finish() |
| 97 | var rng := RandomNumberGenerator.new() |
| 98 | rng.seed = 127 |
| 99 | for i in 60: |
| 100 | var lo := Vector3(rng.randf_range(-300, 300), rng.randf_range(-40, 40), rng.randf_range(-300, 300)) |
| 101 | var hi := lo + Vector3(rng.randf_range(0.03, 9), rng.randf_range(0.03, 9), rng.randf_range(0.03, 9)) |
| 102 | # Every 7th a clearance volume, every 11th sky: both flags present among the ids. |
| 103 | level.add_brush(_box(lo, hi), 0, PackedInt32Array(), 0, i % 11 == 3, "", i % 7 == 2) |
| 104 | level.rebuild() |
| 105 | var live: PackedInt32Array = level.get_live_brush_ids() |
| 106 | var dead := live[5] |
| 107 | level.remove_brush(dead) |
| 108 | live = level.get_live_brush_ids() |
| 109 | var all: Array = Array(live) |
| 110 | all.append(dead) |
| 111 | var sky := 0 |
| 112 | for b: int in all: |
| 113 | sky += 1 if bool((level.get_brush(b) as Dictionary).get("sky", false)) else 0 |
| 114 | _check("anti-absence: clearance brushes among the ids", _ref_clearance(level, PackedInt32Array(all)) > 0, |
| 115 | "%d" % _ref_clearance(level, PackedInt32Array(all))) |
| 116 | _check("anti-absence: sky brushes among the ids", sky > 0, "%d" % sky) |
| 117 | _check("anti-absence: the tombstone still answers get_brush", (level.get_brush(dead) as Dictionary).has("center"), |
| 118 | "brush %d" % dead) |
| 119 | _check("anti-absence: the tombstone is not live", not live.has(dead), "brush %d" % dead) |
| 120 | _compare(level, "all + tombstone", all) |
| 121 | var rev := all.duplicate() |
| 122 | rev.reverse() |
| 123 | _compare(level, "reversed", rev) |
| 124 | _compare(level, "with unknown id + repeat", [live[0], 999999, live[0], live[1]]) |
| 125 | _compare(level, "empty", []) |
| 126 | _compare(level, "unknown only", [999999]) |
| 127 | var bb: Dictionary = level.get_brushes_bounds(PackedInt32Array()) |
| 128 | _check("empty: count 0, zero vectors", int(bb["count"]) == 0 and bb["center"] == Vector3.ZERO |
| 129 | and bb["aabb_min"] == Vector3.ZERO and bb["aabb_max"] == Vector3.ZERO, str(bb)) |
| 130 | return _finish() |
| 131 | |
| 132 | |
| 133 | func _finish() -> bool: |
| 134 | if _failures.is_empty(): |
| 135 | print("PASS brush narrow reads") |
| 136 | quit(0) |
| 137 | else: |
| 138 | print("FAIL brush narrow reads: %d" % _failures.size()) |
| 139 | for f in _failures: |
| 140 | print(" - " + f) |
| 141 | quit(1) |
| 142 | return true |