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")