extends SceneTree
## GATE — the texture strip (hyper-games #94 P1 §9, first slice): the bottom-row
## browser, picking the current texture, apply-to-selection (U) and the eyedropper
## (Y), on a REAL empty-level map (map_flat) in the tree.
##
## Apply must land LOCALLY (the author never refolds its own echo) and in the
## JOURNAL (a refold reproduces it) — both are asserted, because either alone
## passes while one side of the room sees something different.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_texture_strip.gd
var ok := true
var checks := 0
var _frame := 0
var _map: Node = null
func _p(cond: bool, what: String, note: String = "") -> void:
checks += 1
if not cond:
ok = false
print(" %-62s %s %s" % [what, "OK" if cond else "**FAIL**", note])
func _process(_delta: float) -> bool:
_frame += 1
if _frame == 1:
var scene := load("res://scenes/maps/map_flat.tscn") as PackedScene
_map = scene.instantiate()
root.add_child(_map)
return false
if _frame < 30:
return false
_run()
if checks < 20:
_p(false, "only %d checks ran -- setup died before the assertions" % checks)
print("GATE texture_strip: %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 _texes(level: Node, id: int) -> Array:
var out: Array = []
for t: Dictionary in level.get_brush(id).get("face_tex", []):
out.append(String(t.get("tex", "")))
return out
func _run() -> void:
var dock: Node = root.get_node_or_null("/root/BuildDock")
var level: Node = _map.get_node_or_null("DCLevel")
var persist: Node = _map.get("_persist")
var gs: Node = root.get_node_or_null("/root/GameState")
var keet: Node = root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
if gs != null:
gs.route("world:level:*", 1, "peer")
print("-- the strip is a BOTTOM row of the dock --")
_p(dock != null and dock.has_panel("textures"), "map_flat registered the 'textures' panel")
if dock == null or not dock.has_panel("textures"):
return
_p(String(dock._panels["textures"]["side"]) == "bottom", "on the BOTTOM side",
String(dock._panels["textures"]["side"]))
var strip: Node = _map.get("_tex_strip")
_p(strip != null, "the map keeps a handle to the strip")
if strip == null:
return
print("\n-- browse: search and pack filter --")
strip.set_filter("", "")
# 1196 + 10 trimsheets.com textures + 1 composed trim sheet (2026-10-03).
_p(strip.shown_ids().size() + strip.hidden_count() == 1207, "no filter: every texture is listed or counted",
"%d shown + %d more" % [strip.shown_ids().size(), strip.hidden_count()])
strip.set_filter("brick", "")
var hits: PackedStringArray = strip.shown_ids()
var all_brick := hits.size() > 0
for id in hits:
all_brick = all_brick and id.to_lower().contains("brick")
_p(all_brick, "search narrows to matching ids", "%d hits" % hits.size())
strip.set_filter("", "kenney-prototype")
_p(strip.shown_ids().size() == 78, "pack filter: Kenney only", str(strip.shown_ids().size()))
strip.set_filter("", "")
# The strip makes previews lazily only while VISIBLE (never in a headless gate),
# so the preview itself is asserted directly.
var th: Texture2D = TexturePacks.thumbnail("ambientcg/Bricks105", 64)
_p(th != null and th.get_width() == 64 and th.get_height() == 64, "a 64 px preview, from a 1024x512 albedo")
_p(TexturePacks.thumbnail_cached("ambientcg/Bricks105") == th, "and it is cached, not re-decoded")
print("\n-- pick: a click sets the current texture --")
strip.pick("ambientcg/Bricks105")
_p(String(_map.get("current_tex")) == "ambientcg/Bricks105", "current_tex follows the click")
_p(strip.recent().size() >= 1 and strip.recent()[0] == "ambientcg/Bricks105", "and it heads the recent list")
_p(String(_map.current_face_tex().get("tex", "")) == "ambientcg/Bricks105", "new brushes would get it")
print("\n-- apply (U) to a BRUSH selection: every face, locally AND in the journal --")
var ft: Array = []
for i in 6:
ft.append({"tex": "kenney-prototype/dark_01", "mode": 1, "off_u": 0.3})
var bdot: String = persist.record("brush.create", {
"planes": _box(Vector3(0, 0, 0), Vector3(2, 2, 2)), "proj": [], "material_id": 1,
"level_id": 0, "sky": false, "face_tex": ft})
persist.refold()
var bid: int = level.brush_by_key(bdot)
_map.select_brushes(PackedInt32Array([bid]))
var n: int = _map.apply_texture_to_selection()
_p(n == 6, "applied to all 6 faces", str(n))
var local: Array = _texes(level, bid)
_p(local.count("ambientcg/Bricks105") == 6, "LOCAL brush shows it with no refold", str(local))
var t0: Dictionary = level.get_brush(bid)["face_tex"][0]
_p(is_equal_approx(float(t0.get("off_u", 0.0)), 0.3), "alignment kept (only the texture changed)", str(t0.get("off_u")))
var nrec := 0
for r: Dictionary in persist.records():
if String(r.get("kind", "")) == "face.tex":
nrec += 1
_p(nrec == 1, "ONE face.tex record for the brush", str(nrec))
persist.refold()
_p(_texes(level, bid).count("ambientcg/Bricks105") == 6, "a refold reproduces it")
print("\n-- apply to a FACE selection: that face only --")
strip.pick("polyhaven/clean_pebbles")
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[2]]))
_p(_map.apply_texture_to_selection() == 1, "applied to one face")
var after: Array = _texes(level, bid)
_p(after[2] == "polyhaven/clean_pebbles" and after.count("ambientcg/Bricks105") == 5, "only that face changed", str(after))
print("\n-- an UNTEXTURED face becomes paraxial --")
var udot: String = persist.record("brush.create", {
"planes": _box(Vector3(5, 0, 0), Vector3(6, 1, 1)), "proj": [], "material_id": 1,
"level_id": 0, "sky": false})
persist.refold()
var uid: int = level.brush_by_key(udot)
_map.select_brushes(PackedInt32Array([uid]))
_map.apply_texture_to_selection()
var ut: Dictionary = level.get_brush(uid)["face_tex"][0]
_p(String(ut.get("tex", "")) == "polyhaven/clean_pebbles" and int(ut.get("mode", 0)) == 1,
"texture AND paraxial mode", str(ut))
persist.refold()
_p(int(level.get_brush(uid)["face_tex"][0].get("mode", 0)) == 1, "and the refold agrees")
print("\n-- eyedropper (Y) --")
strip.pick("kenney-prototype/dark_01")
var got: String = _map.eyedrop_texture({"brush": bid, "position": Vector3(1, 1, 2)}) # on the +z face
_p(got != "" and String(_map.get("current_tex")) == got, "picks the aimed face's texture", got)
_p(got == "ambientcg/Bricks105" or got == "polyhaven/clean_pebbles", "a texture that face really has", got)
print("\n-- keys --")
var km: Node = root.get_node_or_null("/root/Keymap")
# Keymap step 3: U is the texture menu; apply / pick have their own keys in it.
var ap: Dictionary = km.binding_of("builder.texture_apply")
var pk: Dictionary = km.binding_of("builder.texture_pick")
_p(int(ap.get("key", 0)) == KEY_U and bool(ap.get("ctrl", false)), "Ctrl+U applies", str(ap))
_p(int(pk.get("key", 0)) == KEY_U and bool(pk.get("alt", false)), "Alt+U picks (eyedropper)", str(pk))
_p(int(km.binding_of("builder.uv_menu").get("key", 0)) == KEY_U, "U opens the texture menu")
_p((km.conflicts() as Array).is_empty(), "no key conflicts", str(km.conflicts()))