extends SceneTree
## GATE — the new create-tool shapes (#31: TrenchBroom BasicShapes): cylinder, cone, sphere.
## S1 each is in the shape list, after box / stairs / arch (built-in order kept)
## S2 each creates ONE brush through CreateBoxOperator, which the store accepts
## S3 its bounds are the requested footprint, standing on the aimed surface
## S4 its own params drive it (sides / rings rows exist; extreme counts still build)
## Through the real operator on map_flat, as gate_editor_e4 does.
##
## Run: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_shapes.gd
var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _level: Node
func _p(c: bool, m: String, got: Variant = "") -> void:
checks += 1
print(("ok " if c else "FAIL: ") + m + ("" if str(got) == "" else " (" + str(got) + ")"))
if not c:
ok = false
func _place(bx: CreateBoxOperator, at: Vector3) -> void:
bx.set("_origin", at)
bx.set("_normal", Vector3.UP)
bx.set("_axis", 1)
bx.set("_view", Vector3(0, 0, 1))
bx.set("_valid", true)
func _row(op: LevelOperator, sid: String) -> Dictionary:
for d: Dictionary in op.tool_settings():
if String(d.get("id")) == sid:
return d
return {}
func _newest_bounds() -> AABB:
var ids: PackedInt32Array = _level.get_live_brush_ids()
var d: Dictionary = _level.get_brush(ids[ids.size() - 1])
return AABB(d["aabb_min"], (d["aabb_max"] as Vector3) - (d["aabb_min"] as Vector3))
func _process(_d: float) -> bool:
_frame += 1
if _frame == 1:
var dock := root.get_node("/root/BuildDock")
dock.set_pref("snap", true)
dock.set_pref("grid", 5)
_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
root.add_child(_map)
return false
if _frame < 30:
return false
_run()
print("\n%d checks — %s" % [checks, "PASS" if ok else "FAIL"])
quit(0 if ok else 1)
return true
func _run() -> void:
_level = _map.get_node("DCLevel")
# ---- S1 ----
# §13 (2026-10-12): the tool offers the PARAMETRIC types; cylinder / cone / sphere
# are the baked generators in the local frame, so S2–S4 hold unchanged.
_map._unhandled_key_input(_key_for("builder.create_box"))
var bx: CreateBoxOperator = _map.get("_box")
_p(bx != null, "the create tool exists")
if bx == null:
return
var ids: Array = bx.shape_ids()
_p(ids.slice(0, 7) == ["param:box", "param:stairs", "param:spiral_stairs", "param:tunnel",
"param:cylinder", "param:cone", "param:sphere"], "S1 shapes in order", ids)
var step: float = bx.grid_step()
var x := 10.0
for sid: String in ["cylinder", "cone", "sphere"]:
_place(bx, Vector3(x, 0, 10))
bx.set_tool_setting("shape", bx.shape_ids().find("param:" + sid))
bx.set_tool_setting("size", 6)
_place(bx, Vector3(x, 0, 10))
var n0: int = _level.get_brush_count()
_p(bx.call("_pieces").size() == 1, "S2 %s is ONE piece" % sid, bx.call("_pieces").size())
_p(bx.confirm(), "S2 %s creates" % sid)
_p(_level.get_brush_count() == n0 + 1, "S2 %s: the store accepted it" % sid, _level.get_brush_count() - n0)
var b := _newest_bounds()
_p(is_equal_approx(b.position.y, 0.0) and absf(b.size.x - 6.0 * step) < step * 0.05 and absf(b.size.z - 6.0 * step) < step * 0.05,
"S3 %s stands on the surface with the requested footprint" % sid, b)
# A box fills the same footprint — the FACE COUNT is what says it is this shape.
var live: PackedInt32Array = _level.get_live_brush_ids()
var faces: int = (_level.get_brush(live[live.size() - 1]).get("planes", PackedFloat32Array()) as PackedFloat32Array).size() / 4
var want: int = {"cylinder": 14, "cone": 13, "sphere": 72}[sid]
_p(faces == want, "S3 %s has its own faces (%d), not a box's 6" % [sid, want], faces)
x += 10.0
# ---- S4 ----
bx.set_tool_setting("shape", bx.shape_ids().find("param:sphere"))
_p(not _row(bx, "p.segments").is_empty() and not _row(bx, "p.rings").is_empty(), "S4 sphere shows Sides + Rings")
bx.set_tool_setting("p.segments", 32)
bx.set_tool_setting("p.rings", 16)
bx.set_tool_setting("size", 8)
_place(bx, Vector3(60, 0, 10))
var n1: int = _level.get_brush_count()
_p(bx.confirm() and _level.get_brush_count() == n1 + 1, "S4 a 32 x 16 sphere (512 faces) still builds")
bx.set_tool_setting("shape", bx.shape_ids().find("param:cylinder"))
bx.set_tool_setting("p.segments", 3)
_place(bx, Vector3(75, 0, 10))
var n2: int = _level.get_brush_count()
_p(bx.confirm() and _level.get_brush_count() == n2 + 1, "S4 a 3-sided cylinder (a prism) builds")
bx.set_tool_setting("p.segments", 12)
bx.set_tool_setting("shape", 0)
func _key_for(action: String) -> InputEventKey:
var e := InputEventKey.new()
var kc: int = int(root.get_node("/root/Keymap").binding_of(action).get("key", 0))
e.physical_keycode = kc
e.keycode = kc
e.pressed = true
return e