extends SceneTree
## Gate: every demo map can build brushes (#93 §3, #94 — user decision 2026-10-02:
## "all four maps, including the initial teleport map").
##
## Brushes need a DCLevel and the brush half of map_dc_level (journal wired to the
## level, BuildDock/builder context, brush view, outliner). Only the Mountain had
## it — by extending map_dc_level with level_import OFF (an empty brush level).
## Flat, Erosion and the hub (map_test, the teleport map) ran map_core with no
## DCLevel, so F4 brushes could not exist there at all.
##
## Structural for all four (instantiate, no tree). Behavioural for the hub, in the
## tree: the journal's level IS the map's DCLevel.
##
## Run: HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_brush_maps.gd
const MAPS := [
"res://scenes/maps/map_mountain.tscn",
"res://scenes/maps/map_test.tscn",
"res://scenes/maps/map_dc_erosion.tscn",
"res://scenes/maps/map_flat.tscn",
]
const DC_LEVEL_SCRIPT := "res://scenes/maps/map_dc_level.gd"
var ok := true
var _frame := 0
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _inherits(script: Script, path: String) -> bool:
var s := script
while s != null:
if s.resource_path == path:
return true
s = s.get_base_script()
return false
## Maps put in the tree one at a time, ~30 frames each: their journal must fold into
## their own DCLevel. (The Mountain's in-tree wiring is covered by its own gates.)
const IN_TREE := [
"res://scenes/maps/map_test.tscn",
"res://scenes/maps/map_dc_erosion.tscn",
"res://scenes/maps/map_flat.tscn",
]
var _idx := -1
var _cur: Node = null
var _cur_path := ""
var _since := 0
func _process(_delta: float) -> bool:
_frame += 1
if _frame == 1:
_structural()
return false
if _cur == null:
_idx += 1
if _idx >= IN_TREE.size():
print("GATE brush_maps: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
_cur_path = IN_TREE[_idx]
var scene := load(_cur_path) as PackedScene
_cur = scene.instantiate() if scene else null
if _cur == null:
_p(false, "%s instantiates" % _cur_path.get_file())
return false
root.add_child(_cur)
_since = 0
return false
_since += 1
if _since == 30:
_behavioural(_cur, _cur_path.get_file())
_cur.queue_free()
_cur = null
return false
func _structural() -> void:
for path: String in MAPS:
var scene := load(path) as PackedScene
_p(scene != null, "%s loads" % path.get_file())
if scene == null:
continue
var m: Node = scene.instantiate()
var level: Node = m.get_node_or_null("DCLevel")
_p(level != null and level.get_class() == "DCLevel", "%s has a DCLevel node" % path.get_file())
_p(m.get_script() != null and _inherits(m.get_script(), DC_LEVEL_SCRIPT),
"%s runs the map_dc_level brush editor" % path.get_file())
# map_mountain sets level_import = false in CODE, before super._ready (its own
# comment says why); the others set it in their scene. Not-yet-ready nodes only
# show the scene value, so the Mountain is read from its source instead.
var off: bool = m.get("level_import") == false
if path.ends_with("map_mountain.tscn"):
off = FileAccess.get_file_as_string("res://scenes/maps/map_mountain.gd").contains("level_import = false")
_p(off, "%s: level_import off (an empty brush level, no START.MAP)" % path.get_file())
m.free()
func _behavioural(m: Node, name: String) -> void:
print("-- %s in the tree" % name)
var level: Node = m.get_node_or_null("DCLevel")
var persist: Node = m.get("_persist")
_p(persist != null, "%s has its brush journal (LevelPersistence)" % name)
_p(persist != null and level != null and persist.get("level") == level, "%s: the journal folds into its DCLevel" % name)