OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_create_param.gd

133 lines · gd ref:
extends SceneTree
## GATE — the create tool makes ONLY parametric shapes (docs/parametric-shapes-design.md
## §13, hyper-games #109, user 2026-10-12: "make the create tool make parametric shapes").
##
##   C1  the shapes it offers are the ParamShapes types, in order (plugins after)
##   C2  an old sticky pref migrates: "arch" -> tunnel, "stairs" -> param stairs
##   C3  Stairs writes ONE shape.create and no brush.create; the fold makes its pieces
##   C4  Tunnel (the old arch): wall follows the arch's grid rule unless set; a set wall
##       is kept
##   C5  Auto steps "one per grid step" stores auto_riser = the grid step in the record;
##       "0.2 m risers" stores 0.2
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_create_param.gd

const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")

var _frame := 0
var _map: Node = null
var fails := 0
var checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	if not c:
		fails += 1
	print("  %-66s %s  %s" % [m, "OK" if c else "**FAIL**", str(got)])


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		root.get_node("/root/GameMode").set_mode("build")
		var keet: Node = root.get_node_or_null("/root/KeetService")
		if keet != null:
			keet.current_member_id = "gate-author"
		var gs: Node = root.get_node_or_null("/root/GameState")
		if gs != null:
			gs.route("world:level:*", 1, "peer")
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	_run()
	if checks < 8:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE create_param: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	quit(0 if fails == 0 else 1)
	return true


func _count(kind: String) -> int:
	var n := 0
	for r: Dictionary in (_map.get("_persist").records() as Array):
		if String(r.get("kind", "")) == kind and not bool(r.get("undone", false)):
			n += 1
	return n


func _last(kind: String) -> Dictionary:
	var best := {}
	for r: Dictionary in (_map.get("_persist").records() as Array):
		if String(r.get("kind", "")) == kind and (best.is_empty() or int(r["seq"]) > int(best["seq"])):
			best = r
	return best


## Place without aiming: the placement Adjust Last restores (origin on the floor, normal
## up, looking +x), so confirm() has bounds in a headless gate.
func _place(bx: CreateBoxOperator) -> void:
	bx.adjust_restore({"origin": Vector3(0, 0, 0), "normal": Vector3.UP, "axis": 1,
		"view": Vector3(1, 0, 0.01), "material": 0, "level_id": 0})


func _run() -> void:
	var bx: CreateBoxOperator = _map.get("_box")
	# ---- C1 ----
	var want: Array = []
	for t: String in ParamShapes.ids():
		want.append("param:" + t)
	var have: Array = bx.shape_ids()
	_p(have.slice(0, want.size()) == want and have.size() == want.size(), "C1 the tool offers the parametric types, in order", have)
	# ---- C2 ----
	bx.save_pref("shape", "arch")
	bx.load_prefs()
	_p(String(bx.shape_ids()[int(bx.get("_shape"))]) == "param:tunnel", "C2 pref 'arch' -> tunnel", bx.shape_ids()[int(bx.get("_shape"))])
	bx.save_pref("shape", "stairs")
	bx.load_prefs()
	_p(String(bx.shape_ids()[int(bx.get("_shape"))]) == "param:stairs", "C2 pref 'stairs' -> param stairs", bx.shape_ids()[int(bx.get("_shape"))])
	# ---- C3 ----
	bx.set_tool_setting("shape", CreateBoxOperator.Shape.STAIRS)
	bx.set_tool_setting("size", 4)
	bx.set_tool_setting("auto_steps", 0)
	_place(bx)
	var nb := _count("brush.create")
	var ns := _count("shape.create")
	var ok: bool = bx.confirm()
	_p(ok and _count("shape.create") == ns + 1 and _count("brush.create") == nb,
		"C3 Stairs writes one shape.create, no brush.create", "%s +%d shape +%d brush" % [ok, _count("shape.create") - ns, _count("brush.create") - nb])
	var rec := _last("shape.create")
	_p(String(rec.get("type", "")) == "stairs" and (_map.level.get_group_brush_ids(
		"%s:%d" % [rec.get("author", ""), int(rec.get("seq", 0))]) as PackedInt32Array).size() > 1,
		"C3 the fold made its pieces", rec.get("type", ""))
	# ---- C5 ----
	_p(is_equal_approx(float((rec.get("params", {}) as Dictionary).get("auto_riser", -1.0)), 0.2),
		"C5 '0.2 m risers' stores auto_riser 0.2", (rec.get("params", {}) as Dictionary).get("auto_riser"))
	bx.set_tool_setting("auto_steps", 1)
	_place(bx)
	bx.confirm()
	var rec2 := _last("shape.create")
	_p(is_equal_approx(float((rec2.get("params", {}) as Dictionary).get("auto_riser", -1.0)), bx.grid_step()),
		"C5 'one per grid step' stores auto_riser = the grid step (%.3f)" % bx.grid_step(),
		(rec2.get("params", {}) as Dictionary).get("auto_riser"))
	# ---- C4 ----
	bx.set_tool_setting("shape", CreateBoxOperator.Shape.ARCH)
	_place(bx)
	bx.confirm()
	var t1 := _last("shape.create")
	var c: Array = bx._corners()
	var span_r: float = ((c[1] as Vector3) - (c[0] as Vector3)).z * 0.5
	var height: float = ((c[1] as Vector3) - (c[0] as Vector3)).y
	var rule: float = ArchShape.thickness(span_r, height, bx.grid_step())
	_p(String(t1.get("type", "")) == "tunnel" and is_equal_approx(float((t1.get("params", {}) as Dictionary).get("wall", -1.0)), rule),
		"C4 tunnel wall follows the arch rule (%.3f)" % rule, (t1.get("params", {}) as Dictionary).get("wall"))
	bx.set_tool_setting("p.wall", 0.75)
	_place(bx)
	bx.confirm()
	var t2 := _last("shape.create")
	_p(is_equal_approx(float((t2.get("params", {}) as Dictionary).get("wall", -1.0)), 0.75), "C4 a set wall (0.75) is kept",
		(t2.get("params", {}) as Dictionary).get("wall"))