extends Node
## HEADLESS GATE — parametric shapes through the REAL journal path
## (docs/parametric-shapes-design.md §2): persist.record("shape.*") -> GameState ->
## LevelPersistence._to_record -> ShapeFold.expand -> DCLevel fold.
##
## The trap journal_texture_gate exists for applies here too: a kind or field that
## _to_record drops becomes {} and VANISHES on the next fold while the pure
## shape_fold gate stays green. So this goes through the journal end to end.
##
## RUN AS A SCENE (level_persistence.gd names GameState at compile time):
## "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_shape_journal.tscn
var fails := 0
var checks := 0
func _ok(what: String, cond: bool, note: String = "") -> void:
checks += 1
if not cond:
fails += 1
print(" %-64s %s %s" % [what, "OK" if cond else "**FAIL**", note])
func _ready() -> void:
await get_tree().process_frame
_run()
if checks < 12:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE shape_journal: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _members(level: Node, g: String, clearance: bool) -> int:
var n := 0
for id: int in (level.get_group_brush_ids(g) as PackedInt32Array):
var b: Dictionary = level.get_brush(id)
# A re-fold rewinds and re-applies, so a piece no longer generated (steps
# 7 -> 4) is still in the store, TOMBSTONED. Live pieces only.
if bool(b.get("tombstoned", false)):
continue
if bool(b.get("clearance", false)) == clearance:
n += 1
return n
## The live solid piece nearest the start of the run (the first tread), or 0.
func _first_piece(level: Node, g: String) -> int:
var best := 0
var bx := INF
for id: int in (level.get_group_brush_ids(g) as PackedInt32Array):
var b: Dictionary = level.get_brush(id)
if bool(b.get("tombstoned", false)) or bool(b.get("clearance", false)):
continue
var x := float((b.get("aabb_min", Vector3.ZERO) as Vector3).x)
if x < bx:
bx = x
best = id
return best
func _run() -> void:
if not ClassDB.class_exists("DCLevel"):
_ok("DCLevel registered", false)
return
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer") # 1 == Channel.DURABLE
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
var level: Node3D = ClassDB.instantiate("DCLevel")
add_child(level)
var persist := LevelPersistence.new()
persist.level = level
add_child(persist)
print("-- shape.create: one record, the type's pieces --")
var frame := Transform3D(Basis.IDENTITY, Vector3(5, 0, 0))
persist.begin_gesture("Stairs")
var dot: String = persist.record("shape.create", {"type": "stairs", "gv": 1,
"params": {"steps": 4}, "frame": SceneObjects.xform_to(frame), "size": [4.0, 2.0, 2.0],
"material_id": 2, "level_id": 0})
persist.end_gesture()
_ok("shape.create recorded", dot != "")
var st: Dictionary = persist.refold()
_ok("nothing skipped", int(st.get("skipped", 0)) == 0, str(st))
_ok("4 solid pieces in the shape's group", _members(level, dot, false) == 4, "%d" % _members(level, dot, false))
_ok("and its headroom clearance", _members(level, dot, true) == 1, "%d" % _members(level, dot, true))
var id0 := _first_piece(level, dot)
var b0: Dictionary = level.get_brush(id0) if id0 > 0 else {}
var k0: String = level.get_brush_key(id0) if id0 > 0 else ""
_ok("piece 0 is named <author>/0@<ver>:<seq>", k0.begins_with("gate-author/0@") and k0.ends_with(":" + dot.get_slice(":", 1)), k0)
_ok("the frame placed it (x >= 5)", not b0.is_empty() and float((b0.get("aabb_min", Vector3.ZERO) as Vector3).x) >= 4.99,
str(b0.get("aabb_min", "?")))
print("-- shape.set: steps 4 -> 7, then undo --")
persist.begin_gesture("Edit shape")
persist.record("shape.set", {"target": dot, "params": {"steps": 7}})
persist.end_gesture()
persist.refold()
_ok("7 pieces after shape.set", _members(level, dot, false) == 7, "%d" % _members(level, dot, false))
persist.undo_last()
persist.refold()
_ok("4 again after undo", _members(level, dot, false) == 4, "%d" % _members(level, dot, false))
print("-- shape.xform / shape.size --")
persist.record("shape.xform", {"target": dot, "frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, Vector3(20, 0, 0)))})
persist.record("shape.size", {"target": dot, "size": [8.0, 2.0, 2.0]})
persist.refold()
var id1 := _first_piece(level, dot)
var b1: Dictionary = level.get_brush(id1) if id1 > 0 else {}
_ok("moved by shape.xform (x >= 20)", not b1.is_empty() and float((b1.get("aabb_min", Vector3.ZERO) as Vector3).x) >= 19.99,
str(b1.get("aabb_min", "?")))
_ok("resized by shape.size (first step 2 m deep)", not b1.is_empty()
and absf(float((b1.get("aabb_max", Vector3.ZERO) as Vector3).x) - 22.0) < 0.01, str(b1.get("aabb_max", "?")))
print("-- shape.detach (Make Real) --")
persist.record("shape.detach", {"target": dot})
persist.refold()
_ok("detached: the shape generates nothing", _members(level, dot, false) == 0)
_ok("labels: shape kinds have History names", String(persist.KIND_LABELS.get("shape.detach", "")) == "Make real"
and String(persist.KIND_LABELS.get("shape.set", "")) == "Edit shape")