extends SceneTree
## Gate: map_flat can be built in.
##
## map_dc_level was the only map that instantiated SceneObjects, so "building"
## and "the Quake brush level" were the same thing by accident of where the
## wiring lived. A builder bot needs a journal, a prefab catalogue and something
## to fold records into nodes; none of those need brushes, and map_flat is the
## map a bot testbed actually runs in.
##
## This instances the REAL map_flat.tscn rather than a stand-in, because what is
## under test is the wiring in that scene's script — a fixture built here would
## be asserting against itself.
var ok := true
var _ran := false
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _process(_delta: float) -> bool:
if _ran:
return true
_ran = true
_run()
print("GATE map_flat_objects: %s" % ("PASS" if ok else "FAIL"))
if not ok:
quit(1)
return true
func _run() -> void:
var ps: PackedScene = load("res://scenes/maps/map_flat.tscn")
_p(ps != null, "map_flat.tscn loads")
if ps == null:
return
var map: Node = ps.instantiate()
_p(map != null, "and instantiates")
if map == null:
return
root.add_child(map)
# ---- the wiring exists and was made eagerly ----
var objs: Node = map.get_node_or_null(^"SceneObjects")
var persist: Node = map.get_node_or_null(^"LevelPersistence")
_p(objs != null, "map_flat has a SceneObjects")
_p(persist != null, "and a LevelPersistence, made in _ready rather than on first use")
if objs == null or persist == null:
return
# CHANGED 2026-10-02 (user decision: brush building on every demo map): map_flat
# extends map_dc_level with level_import off, so the journal now folds brushes
# into its DCLevel too. The pair is INHERITED -- there must be exactly one, or
# two managers would fold one journal.
var lvl: Node = map.get_node_or_null(^"DCLevel")
_p(lvl != null and persist.level == lvl, "the journal folds brushes into this map's DCLevel")
var n_persist := 0
var n_objs := 0
for c in map.get_children():
if String(c.name).begins_with("LevelPersistence"):
n_persist += 1
if String(c.name).begins_with("SceneObjects"):
n_objs += 1
_p(n_persist == 1 and n_objs == 1, "exactly ONE journal and ONE objects manager (%d / %d)" % [n_persist, n_objs])
_p(persist.objects == objs, "and the journal is wired to the objects manager")
# ---- the journal is a control surface a bot can drive ----
_p(persist.is_in_group("admin_controllable"), "the journal is admin-controllable")
_p(String(persist.get_meta("admin_name", "")) == "level", "exposed as 'level'")
var scopes: Dictionary = persist.get_meta("admin_call_scopes", {}) as Dictionary
_p(String(scopes.get("record", "")) == "authority",
"record is scope authority — one peer records, or N peers build N blocks")
# ---- and it can actually build ----
_p(map.has_method("create_object"), "map_flat exposes create_object()")
if not map.has_method("create_object"):
return
var before: int = objs.count()
var dot: String = map.create_object("block.bevelled",
Transform3D(Basis(), Vector3(0.0, 5.0, 0.0)))
_p(dot != "", "creating a block returns a dot (%s)" % dot)
_p(objs.count() == before + 1, "and a node exists for it (%d -> %d)" % [before, objs.count()])
var n: Node = objs.node_for(dot)
_p(n != null, "the node resolves from its dot")
if n is Node3D:
_p((n as Node3D).transform.origin.is_equal_approx(Vector3(0.0, 5.0, 0.0)),
"and is where it was asked for (%s)" % str((n as Node3D).transform.origin))
# SELF-CHECK: an unknown prefab must be refused, or create_object would build
# anything a peer names and the catalogue's allow-list would be decorative.
var bad: String = map.create_object("definitely.not.a.prefab", Transform3D())
_p(bad == "", "self-check: an unknown prefab id is refused")
_p(objs.count() == before + 1, "and nothing was built for it")
map.free()