OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_hotspot.gd

175 lines · gd ref:
extends SceneTree
## GATE — hotspot faces (#94 P2, docs/hotspot-trim-design.md §4-7) on a real map_flat.
##
## The composed industrial sheet (texture_packs/composed, 512 px/m, 1024²) has four
## 124 px strips (steel_panel 1, diamond_plate 2, corrugated 4, asphalt_floor 6) — inside
## their 8 px bevels 0.21 m tall, the closest to a 0.25 m tall face. So Fit must pick one
## of those four; re-roll must move between them; Pin must keep one through re-rolls; a
## texture without rects is skipped; records replicate (refold agrees); panel buttons work.
## The maths is libdc's (tests/test_face_tex_hotspot.cpp); this gates the wiring.
##
## Run: HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_hotspot.gd

const SHEET := "composed/industrial"
const NEAR_STRIPS := [1, 2, 4, 6]

var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _level: Node
var _persist: Node
var _bid := -1


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 _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_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 < 12:
		_p(false, "only %d checks ran" % checks)
	print("GATE hotspot: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
	quit(0 if ok else 1)
	return true


func _box(lo: Vector3, hi: Vector3) -> Array:
	return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]


func _ft(f: int) -> Dictionary:
	return (_level.get_brush(_bid).get("face_tex", []) as Array)[f]


func _chosen(f: int) -> int:
	var d: Dictionary = _level.face_tex_compute(_bid, f, "hotspot_pin", {})
	return int(d.get("rect", -9)) if not d.is_empty() else -9


func _select(f: int) -> void:
	var keys: PackedStringArray = _level.get_brush(_bid).get("plane_keys", PackedStringArray())
	_map.set("_sel_brushes", [_bid] as Array[int])
	_map.set("_sel_brush", _bid)
	_map.set("_sel_kind", "face")
	_map.set("_sel_keys", PackedStringArray([keys[f]]))


func _records() -> int:
	var n := 0
	for r: Dictionary in _persist.records():
		if String(r.get("kind", "")) == "face.tex":
			n += 1
	return n


func _refold_agrees(what: String) -> void:
	var before: Dictionary = _ft(4).duplicate()
	var pin_before := _chosen(4)
	_persist.refold()
	_map._process(0.0)
	var after := _ft(4)
	_p(int(after.get("mode")) == int(before.get("mode")) and int(after.get("rect")) == int(before.get("rect"))
		and int(after.get("seed")) == int(before.get("seed")) and _chosen(4) == pin_before,
		what + ": a refold reproduces mode / rect / seed and the same fitted rect",
		"local mode %s rect %s seed %s fit %d | refold mode %s rect %s seed %s fit %d" % [before.get("mode"),
		before.get("rect"), before.get("seed"), pin_before, after.get("mode"), after.get("rect"), after.get("seed"), _chosen(4)])


func _run() -> void:
	_level = _map.get_node("DCLevel")
	_persist = _map.get("_persist")
	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")
	_p(TexturePacks.entry(SHEET).get("rects") is Array, "the composed sheet has rects in its pack.json")
	var ft: Array = []
	for i in 6:
		ft.append({"tex": SHEET, "mode": 1})
	ft[0] = {"tex": "kenney-prototype/dark_01", "mode": 1}     # +x face: a texture WITHOUT rects
	# Box x 0..3, y 0..0.25, z 0..1: face 4 (+z) is a 3 x 0.25 m wall.
	var dot: String = _persist.record("brush.create", {"planes": _box(Vector3(0, 0, 0), Vector3(3, 0.25, 1)),
		"proj": [], "material_id": 1, "level_id": 0, "sky": false, "face_tex": ft})
	_persist.refold()
	_bid = _level.brush_by_key(dot)
	_map._process(0.0)                    # the pack material loads the sheet + its rects

	print("-- Fit rect on the 3 x 0.25 m wall --")
	_select(4)
	var r0 := _records()
	var n: int = _map.uv_op("hotspot")
	_p(n == 1 and int(_ft(4).get("mode")) == 4 and int(_ft(4).get("rect")) == -1, "face 4 is HOTSPOT, auto rect",
		"%d faces, %s" % [n, _ft(4)])
	_p(_records() == r0 + 1, "one face.tex record")
	var last: Dictionary = {}
	for r: Dictionary in _persist.records():
		if String(r.get("kind", "")) == "face.tex":
			last = r
	_p(int(last.get("mask", -1)) == (2 | 64), "mask MODE|P2", last.get("mask"))
	_map._process(0.0)
	var c0 := _chosen(4)
	_p(c0 in NEAR_STRIPS, "fitted to one of the 124 px strips", c0)
	_refold_agrees("fit")

	print("-- re-roll moves between the near-equal strips --")
	var seen := {c0: true}
	for i in 8:
		_map.uv_op("hotspot_reroll")
		seen[_chosen(4)] = true
	var all_near := true
	for k in seen:
		all_near = all_near and int(k) in NEAR_STRIPS
	_p(seen.size() >= 2 and all_near, "re-rolls pick other 124 px strips only", seen.keys())

	print("-- pin keeps the rect through re-rolls --")
	# By its HOTKEY (every command in a menu AND on a key): Ctrl+Shift+U, the hotspot
	# family's third (Shift+U fit, Shift+Alt+U re-roll).
	var pin_key := InputEventKey.new()
	pin_key.keycode = KEY_U
	pin_key.physical_keycode = KEY_U
	pin_key.pressed = true
	pin_key.ctrl_pressed = true
	pin_key.shift_pressed = true
	var km: Node = _map.get_tree().root.get_node("Keymap")      # not the autoload name: --script gate
	var gm: Node = _map.get_tree().root.get_node("GameMode")
	gm.set_mode("build")                  # where the key lives: build mode, a face selected
	_p(bool(_map.call("_uv_key", pin_key)), "Ctrl+Shift+U is the Pin key (%s)" % km.describe("builder.uv_pin"))
	gm.set_mode("play")
	var pinned := int(_ft(4).get("rect"))
	_p(pinned in NEAR_STRIPS, "pin stores the rect", pinned)
	for i in 4:
		_map.uv_op("hotspot_reroll")
	_p(int(_ft(4).get("rect")) == pinned, "re-roll keeps the pin (rect still stored)", _ft(4).get("rect"))
	_p(_chosen(4) == pinned, "re-rolls no longer change a pinned face", _chosen(4))
	_refold_agrees("pin")

	print("-- a texture without rects is skipped --")
	_select(0)
	_p(_map.uv_op("hotspot") == 0 and int(_ft(0).get("mode")) == 1, "kenney face: 0 faces, mode stays paraxial")

	print("-- panel buttons --")
	var panel: Node = _map.get("_face_panel")
	_p(panel != null and panel.button("Fit rect") != null and panel.button("Re-roll") != null and panel.button("Pin") != null,
		"panel has Fit rect / Re-roll / Pin")
	_p(panel != null and panel.button("Pin") != null and panel.button("Pin").tooltip_text.contains("Ctrl+Shift+U"),
		"the Pin button names its key", panel.button("Pin").tooltip_text if panel != null and panel.button("Pin") != null else "")
	if panel != null and panel.button("Fit rect") != null:
		_select(5)                         # -z face, also on the sheet
		panel.refresh()
		panel.button("Fit rect").pressed.emit()
		_p(int(_ft(5).get("mode")) == 4, "Fit rect button makes face 5 HOTSPOT")