OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_shapes.gd

121 lines · gd ref:
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