OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_unbounded_scale.gd

122 lines · gd ref:
extends SceneTree
## GATE — sizes and scales have no ceiling (hyper-games #109, 2026-10-12: "scale is
## currently bounded, should be unbounded" · "when creating arches, unit scale is blocked
## at 32").
##
## The create tool (box / stairs / arch) clamped size to 32 grid steps and height to 64;
## the scale tool clamped a model to ×100 and the model's Scale prop to 100. The FLOORS
## stay (a size of at least one step, a scale above zero); the ceilings go. The Object
## panel's slider keeps its 0.01–100 drag range — a ceiling there is a UI range, not a
## limit on the value (`open_max`).
##
##   U1  the create tool takes size 100 and height 200; its settings declare no max
##   U2  the floors stay: size 0 -> 1
##   U3  a model at Scale 80, scaled ×2 with the tool, becomes 160 (was capped at 100)
##   U4  the Scale prop keeps 500 through sanitize; 0.001 still rises to 0.01
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_unbounded_scale.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 _key(code: int) -> InputEventKey:
	var e := InputEventKey.new()
	e.physical_keycode = code
	e.keycode = code
	e.pressed = true
	return e


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 unbounded_scale: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	quit(0 if fails == 0 else 1)
	return true


func _setting(op: Object, sid: String) -> Dictionary:
	for d: Dictionary in (op.tool_settings() as Array):
		if String(d.get("id", "")) == sid:
			return d
	return {}


func _run() -> void:
	# ---- U1 / U2: the create tool ----
	var bx: CreateBoxOperator = _map.get("_box")
	bx.set_tool_setting("size", 100)
	_p(int(bx.get("_size_steps")) == 100, "U1 size 100 grid steps is kept", bx.get("_size_steps"))
	bx.set_tool_setting("height", 200)
	_p(int(bx.get("_height_steps")) == 200, "U1 height 200 grid steps is kept", bx.get("_height_steps"))
	_p(not _setting(bx, "size").has("max") and not _setting(bx, "height").has("max"),
		"U1 the size/height settings declare no max", "%s / %s" % [_setting(bx, "size").get("max"), _setting(bx, "height").get("max")])
	bx.set_tool_setting("size", 0)
	_p(int(bx.get("_size_steps")) == 1, "U2 size 0 still rises to 1", bx.get("_size_steps"))
	bx.set_tool_setting("size", 2)
	bx.set_tool_setting("height", 2)

	# ---- U3: the scale tool on a model ----
	var md: String = _map.create_object("model", Transform3D(Basis(), Vector3(10, 0, 10)))
	_map.set_object_props(md, {"scale": 80.0})
	_map.select_object(md)
	_map._unhandled_key_input(_key(KEY_S))          # scale is S since keymap step 3
	var stack: Node = _map.call("_ensure_ops")
	var op: Object = stack.active
	if op is ScaleOperator:
		# A model steps ×1.25 (the grid is a brush's; 2026-10-18): four steps from 80.
		for i in 4:
			_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
		_p(is_equal_approx(float(op.object_factor()) * 80.0, 80.0 * pow(1.25, 4.0)), "U3 Scale 80 ×1.25⁴ -> 195.3 (was capped at 100)",
			float(op.object_factor()) * 80.0)
		stack.cancel() if stack.has_method("cancel") else stack.handle_escape()
	else:
		_p(false, "U3 S started the scale tool on the model", op)

	# ---- U4: the prop ----
	var spec: Dictionary = {}
	for s: Dictionary in PrefabCatalogue.props_of("model", {}):
		if String(s["id"]) == "scale":
			spec = s
	_p(float(PrefabCatalogue.sanitize(spec, 500.0)) == 500.0, "U4 Scale 500 survives sanitize", PrefabCatalogue.sanitize(spec, 500.0))
	_p(is_equal_approx(float(PrefabCatalogue.sanitize(spec, 0.001)), 0.01), "U4 Scale 0.001 still rises to 0.01",
		PrefabCatalogue.sanitize(spec, 0.001))

	# ---- U5: the Object panel shows it ----
	_map.set_object_props(md, {"scale": 160.0})
	_map.select_object(md)
	var panel: Node = _map.get("_object_panel")
	var shown := -1.0
	if panel != null:
		panel.refresh()
		var w: Variant = panel.widget("scale")
		if w is HSlider:
			shown = (w as HSlider).value
	_p(is_equal_approx(shown, 160.0), "U5 the Object panel's Scale slider reads 160, not 100", shown)