extends Node
## GATE — ASSET THUMBNAILS (#120 L4 step 6, docs/node-tree-and-stacks.md §15.6). WINDOWED:
## it needs a real renderer (the headless dummy draws nothing). A collection "Kit" (a box
## with a mirror + a stair) saved; its thumbnail renders over the next frames.
## T1 a save yields a thumbnail (thumbnail_ready)
## T2 a 128 px PNG on disk, named by the CONTENT OVERLAY's hash of it
## T3 it is a RENDER of the thing: many colours, and the middle is not background
## T4 the room's catalogue entry and the personal index carry it
## T5 the browser's tile shows it
## T6 a late thumbnail never clobbers a NEWER save's entry (only its own oid's)
##
## RUN WINDOWED: CI=true DISPLAY=:0 HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --path . res://scenes/test/gate_asset_thumb.tscn
const AssetLibrary := preload("res://scenes/maps/asset_library.gd")
var fails := 0
var checks := 0
var map: Node3D
var persist: Node
var lib: Node
var _thumb := ""
func _ok(what: String, cond: bool, note: Variant = "") -> void:
checks += 1
if not cond:
fails += 1
print(" %-64s %s %s" % [what, "OK" if cond else "**FAIL**", str(note)])
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "probe"
get_tree().root.get_node("/root/GameMode").set_mode("build")
var pd := AssetLibrary.personal_dir()
if pd.ends_with("_ci"):
for f: String in DirAccess.get_files_at(pd):
DirAccess.remove_absolute(ProjectSettings.globalize_path(pd.path_join(f)))
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
persist = map.get("_persist")
lib = map.asset_library()
await _run()
if checks < 8:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE asset_thumb: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _on_thumb(_n: String, t: String) -> void:
_thumb = t
func _run() -> void:
var O0 := Vector3(950.137, 0.31, 949.873)
persist.begin_gesture("room")
var a: String = persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, O0)), "size": [2, 2, 2], "material_id": 2, "level_id": 0})
persist.record("mod.create", {"target": a, "mod": "mirror", "params": {"axis": "x", "offset": 0.0}})
var st: String = persist.record("shape.create", {"type": "stairs", "gv": 1, "params": {},
"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, O0 + Vector3(0, 0, 4))), "size": [2, 2, 3],
"material_id": 2, "level_id": 0})
var kit: String = persist.record("col.create", {"name": "Kit", "parent": ""})
persist.record("col.link", {"target": kit, "members": ["g:" + a, "g:" + st]})
persist.end_gesture()
persist.refold()
lib.thumbnail_ready.connect(_on_thumb)
var r: Dictionary = lib.save_collection(kit, "Kit")
var oid := String(r.get("oid", ""))
for i in 120:
if _thumb != "":
break
await get_tree().process_frame
# ---- T1 / T2 ----
_ok("T1 a save yields a thumbnail", _thumb.length() == 64, _thumb)
var path: String = AssetLibrary.thumb_path(_thumb)
var img := Image.new()
var loaded := FileAccess.file_exists(path) and img.load_png_from_buffer(FileAccess.get_file_as_bytes(path)) == OK
_ok("T2 a 128 px PNG on disk", loaded and img.get_width() == 128 and img.get_height() == 128,
[img.get_width(), img.get_height()] if loaded else path)
var co: Node = get_tree().root.get_node("ContentOverlay")
_ok("T2 ... named by the overlay's own hash of it", loaded and String(co.content_hash_of_file(path)) == _thumb)
# ---- T3 ----
var colours := {}
var bg := Color(0.16, 0.17, 0.19)
var not_bg := 0
if loaded:
img.convert(Image.FORMAT_RGBA8)
for y in range(0, 128, 2):
for x in range(0, 128, 2):
var c := img.get_pixel(x, y)
colours[c.to_html(false)] = true
for y2 in range(40, 88):
for x2 in range(40, 88):
var c2 := img.get_pixel(x2, y2)
if absf(c2.r - bg.r) + absf(c2.g - bg.g) + absf(c2.b - bg.b) > 0.08:
not_bg += 1
_ok("T3 it is a render: many colours", colours.size() > 20, colours.size())
_ok("T3 ... and the middle is the thing, not background", not_bg > 48 * 48 / 4, not_bg)
# ---- T4 ----
var sync: Node = get_tree().root.get_node("AssetSync")
var e: Variant = sync.state.get_state("world:assets:asset:Kit", null)
_ok("T4 the room's catalogue entry carries it", e is Dictionary and String((e as Dictionary).get("thumb", "")) == _thumb, e)
_ok("T4 the personal index carries it", AssetLibrary.personal_index().any(func(x: Dictionary) -> bool:
return String(x["oid"]) == oid and String(x["thumb"]) == _thumb))
# ---- T5 ----
var ab: Node = map.get("_asset_browser")
ab.refresh()
_ok("T5 the browser's tile shows it", ab.tile("Kit") != null and ab.tile("Kit").icon != null)
# ---- T6 ----
var before: Variant = sync.state.get_state("world:assets:asset:Kit", null)
await lib._thumb_after_save("Kit", "0".repeat(64), lib.collection_brushes(kit), true, false)
_ok("T6 a thumbnail for another oid leaves the entry alone",
str(sync.state.get_state("world:assets:asset:Kit", null)) == str(before))