extends SceneTree
## THE ORGANIZER'S MODEL — hyper-games #28.
##
## "$HYPER_TPS_GODOT" --headless --script scenes/test/outliner_model_gate.gd
##
## The model is UI-free, so all of it is testable without a panel — which is the
## point of the split. What is asserted here is the part that is easy to get
## subtly wrong and impossible to see in a screenshot:
##
## HIDING INHERITS, and un-hiding a parent restores exactly the children that
## were not hidden themselves.
##
## AND IT NEVER TOUCHES THE SHELL. Hiding a boundary wall must not un-seal the
## level; that is the failure that would fill every room with hillside.
const WALLS := [
[Vector3(0, 0, 0), Vector3(7, 0.5, 7)],
[Vector3(0, 4.5, 0), Vector3(7, 5, 7)],
[Vector3(0, 0.5, 0), Vector3(0.5, 4.5, 7)],
[Vector3(6.5, 0.5, 0), Vector3(7, 4.5, 7)],
[Vector3(0.5, 0.5, 0), Vector3(6.5, 4.5, 0.5)],
[Vector3(0.5, 0.5, 6.5), Vector3(6.5, 4.5, 7)],
]
const TUNNEL := [Vector3(7, 0.5, 3), Vector3(13, 2.5, 4)]
var _failures: Array[String] = []
var _frame := 0
var _map: Node3D = null
var _level: Node3D = null
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,
])
func _row(rs: Array, id: String) -> Dictionary:
for r: Dictionary in rs:
if r["id"] == id:
return r
return {}
func _build_scene() -> void:
_map = Node3D.new()
_map.name = "MapDCWorld"
root.add_child(_map)
_level = ClassDB.instantiate("DCLevel") as Node3D
_level.name = "DCLevel"
_map.add_child(_level)
var terrain := Node3D.new()
terrain.name = "DCTerrain"
_map.add_child(terrain)
var lights := Node3D.new()
lights.name = "QuakeLights"
_map.add_child(lights)
for w: Array in WALLS:
_level.add_brush(_box(w[0], w[1]), 0, PackedInt32Array(), 0, false, "")
# One sky brush and one clearance volume, so both derived groups exist.
_level.add_brush(_box(Vector3(2, 4.9, 2), Vector3(4, 5.1, 4)), 0,
PackedInt32Array(), 0, true, "")
_level.add_brush(_box(TUNNEL[0], TUNNEL[1]), 0, PackedInt32Array(), 0,
false, "t:1", true)
_level.rebuild()
func _process(_delta: float) -> bool:
_frame += 1
if _frame < 3:
return false
print("outliner model gate")
_build_scene()
var m := OutlinerModel.new(_map)
# `level` is read in _init from map.get("level"), which a plain Node3D has
# not got — the real map does. Set it the way the map would.
m.level = _level
# ---- the tree ---------------------------------------------------------
var rs: Array = m.rows()
_check("the map is the root row", not _row(rs, "node:.").is_empty(), "node:.")
_check("scene objects are rows", not _row(rs, "node:DCTerrain").is_empty()
and not _row(rs, "node:QuakeLights").is_empty(), "DCTerrain, QuakeLights")
var sky: Dictionary = _row(rs, "derived:sky")
var clr: Dictionary = _row(rs, "derived:clearance")
var rest: Dictionary = _row(rs, "derived:rest")
_check("Sky is a derived group", int(sky.get("count", 0)) == 1, "%d" % int(sky.get("count", -1)))
_check("Clearance is a derived group", int(clr.get("count", 0)) == 1, "%d" % int(clr.get("count", -1)))
_check("the six walls land in Unsorted", int(rest.get("count", 0)) == 6,
"%d" % int(rest.get("count", -1)))
_check("brush groups hang off the level, not the map",
String(sky.get("parent", "")) == "node:DCLevel", String(sky.get("parent", "?")))
# 1183 brushes must never become 1183 rows.
var brush_rows := 0
for r: Dictionary in rs:
if int(r["kind"]) == OutlinerModel.Kind.BRUSH:
brush_rows += 1
_check("no per-brush rows by default", brush_rows == 0, "%d" % brush_rows)
# ---- hiding, and the invariant that matters ---------------------------
var shell0: Dictionary = _level.build_shell()
var t0: int = _level.get_triangle_count()
m.set_row_hidden("derived:rest", true)
m.apply()
_check("hiding a group hides its brushes",
(_level.get_hidden_brush_ids() as PackedInt32Array).size() == 6,
"%d hidden" % (_level.get_hidden_brush_ids() as PackedInt32Array).size())
_check("and the level re-meshed", _level.get_triangle_count() < t0,
"%d vs %d tris" % [_level.get_triangle_count(), t0])
var shell1: Dictionary = _level.build_shell()
_check("THE SHELL IS UNTOUCHED",
int(shell1.get("triangles", 0)) == int(shell0.get("triangles", 0)),
"%d tris" % int(shell1.get("triangles", 0)))
_check("and still sealed", bool(shell1.get("sealed", false)), "yes")
# A node row really hides the node.
m.set_row_hidden("node:DCTerrain", true)
m.apply()
_check("hiding a node row hides the node",
not (_map.get_node("DCTerrain") as Node3D).visible, "visible=false")
m.set_row_hidden("node:DCTerrain", false)
m.set_row_hidden("derived:rest", false)
m.apply()
_check("unhiding restores the mesh exactly", _level.get_triangle_count() == t0,
"%d vs %d" % [_level.get_triangle_count(), t0])
# COLLECTIONS are room state since #120 F1 (journalled, CollectionFold): tested by
# gate_collection_fold / _persist / _edit / _keys, hide inheritance included (E14).
print("\nRESULT: %s" % ("PASS" if _failures.is_empty() else "FAIL"))
for f: String in _failures:
print(" - %s" % f)
quit(0 if _failures.is_empty() else 1)
return true