OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_collection_persist.gd

136 lines · gd ref:
extends Node
## GATE — COLLECTIONS ARE ROOM STATE (#120 F1): col.* records through LevelPersistence.
##   P1  col.create / col.link recorded through the journal come back as the tree
##   P2  a collection edit does NOT re-fold the level (the brush half is skipped) and no
##       record is reported skipped (DCLevel never sees col.*)
##   P3  undo of the link gesture unlinks; undo of the create removes the collection
##   P4  the tree survives a full level fold (a brush edit after it)
##   P5  FLATTEN keeps the collections: same names and nesting, and every member still
##       names something live — a shape and a created object are re-minted under new
##       dots by the flatten, so their members must be remapped
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_collection_persist.tscn

var fails := 0
var checks := 0


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 = "gate-author"
	get_tree().root.get_node("/root/GameMode").set_mode("build")
	var map: Node3D = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	await _run(map)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 14:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE collection_persist: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _run(map: Node3D) -> void:
	var persist: Node = map.get("_persist")
	var level: Node = map.get("level")
	_ok("map_dev_test has a journal", persist != null and persist.has_method("collection_tree"))
	if persist == null or not persist.has_method("collection_tree"):
		return
	persist.refold()
	var g0 := "gate-group"                       # a brush object's group (members are "g:<group>")

	# ---- P1 ----
	persist.begin_gesture("New collection")
	var col: String = persist.record("col.create", {"name": "Rooms", "parent": ""})
	persist.end_gesture()
	persist.begin_gesture("Link")
	persist.record("col.link", {"target": col, "members": ["obj:scene:SpawnPoint1", "g:" + g0]})
	persist.end_gesture()
	var lf: Dictionary = persist.refold()
	var t: Dictionary = persist.collection_tree()
	_ok("P1 the collection is in the tree, named", t["cols"].has(col) and String(t["cols"][col]["name"]) == "Rooms",
		t["cols"].keys())
	_ok("P1 ... with both members", t["cols"].has(col) and (t["cols"][col]["members"] as Array).size() == 2,
		t["cols"][col]["members"] if t["cols"].has(col) else [])

	# ---- P2 ----
	_ok("P2 the collection edit skipped the brush fold", String(lf.get("brush_fold", "")) == "skipped", lf.get("brush_fold"))
	_ok("P2 no record reported skipped", int(lf.get("skipped", 0)) == 0, lf.get("skipped"))

	# ---- P4 (before undo): a brush edit forces a full fold ----
	var b0: int = (level.get_live_brush_ids() as PackedInt32Array)[0]
	persist.begin_gesture("Move")
	persist.record("brush.translate", {"brush_key": String(level.get_brush_key(b0)), "delta": [0, 0.5, 0]})
	persist.end_gesture()
	var lf2: Dictionary = persist.refold()
	_ok("P4 a brush edit folds the level", String(lf2.get("brush_fold", "")) != "skipped", lf2.get("brush_fold"))
	_ok("P4 ... and the tree is unchanged", str(persist.collection_tree()) == str(t))
	_ok("P4 ... with nothing skipped", int(lf2.get("skipped", 0)) == 0, lf2.get("skipped"))
	persist.undo_last()
	persist.refold()

	# ---- P3 ----
	persist.undo_last()                         # the link
	persist.refold()
	var t3: Dictionary = persist.collection_tree()
	_ok("P3 undoing the link empties the collection", t3["cols"].has(col) and (t3["cols"][col]["members"] as Array).is_empty(),
		t3["cols"].get(col, {}))
	persist.undo_last()                         # the create
	persist.refold()
	_ok("P3 undoing the create removes it", not persist.collection_tree()["cols"].has(col))

	# ---- P5 ----
	var shape: String = persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
		"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, Vector3(20, 0, 20))),
		"size": [2, 1, 2], "material_id": 2, "level_id": 0})
	var obj: String = map.create_object("marker", Transform3D(Basis(), Vector3(30, 1, 0)))
	persist.refold()
	var loose := -1
	for b: int in (level.get_live_brush_ids() as PackedInt32Array):
		if String(level.get_brush_group(b)) == "":
			loose = b
			break
	map.select_brushes(PackedInt32Array([loose]))
	var outer: String = CollectionOps.create_empty(map, "Outer")
	var inner: String = CollectionOps.create_from_selection(map, "Inner", outer)   # the loose brush, made an object
	persist.begin_gesture("Link")
	persist.record("col.link", {"target": outer, "members": ["g:" + shape, "obj:" + obj]})
	persist.end_gesture()
	persist.refold()
	var r5: Dictionary = persist.flatten_map()
	_ok("P5 the flatten ran", not r5.has("error"), r5)
	var t5: Dictionary = persist.collection_tree()["cols"]
	var by_name := {}
	for d: String in t5:
		by_name[String(t5[d]["name"])] = d
	_ok("P5 both collections survive", by_name.has("Outer") and by_name.has("Inner"), by_name.keys())
	if not (by_name.has("Outer") and by_name.has("Inner")):
		return
	_ok("P5 ... nested as before", String(t5[by_name["Inner"]]["parent"]) == String(by_name["Outer"]))
	var shapes: Dictionary = persist.live_shapes()
	var om: Array = t5[by_name["Outer"]]["members"]
	var shape_ok := false
	var obj_ok := false
	for m: String in om:
		if m.begins_with("g:") and shapes.has(m.substr(2)):
			shape_ok = true
		if m.begins_with("obj:") and map.object_node(m.substr(4)) != null:
			obj_ok = true
	_ok("P5 Outer's shape member names a live shape", shape_ok, om)
	_ok("P5 Outer's object member names a live object", obj_ok, om)
	var im: Array = t5[by_name["Inner"]]["members"]
	_ok("P5 Inner's brush object still has its brush", im.size() == 1
		and (level.get_group_brush_ids(String(im[0]).substr(2)) as PackedInt32Array).size() == 1, im)