OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_home_reset.gd

152 lines · gd ref:
extends Node
## GATE — RESET A HOME MAP TO THE RELEASE STARTER (docs/home-world-design.md §6;
## user 2026-10-05: "yes build reset that way" — the flatten pattern).
##
## A Home map is seeded from a starter, then edited (records, an object, a sky key),
## FLATTENED (its content becomes non-undoable base records — the case a tombstone
## reset could not clear) and edited again. Then Reset, through the History panel:
##
##   1. the panel offers "Reset to starter…" at Home when a starter exists
##   2. after: exactly the starter's content is back (its record live, its sky value),
##      the marker is set, and none of my edits, base records or objects remain
##   3. KEPT: the player position key and the level-source key
##   4. the backup holds every erased key
##   5. the level shows it: the starter's brush exists, my brushes do not
##   6. refused (nothing changed, button hidden) outside Home, and for a non-admin
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_home_reset.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 _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 _ready() -> void:
	var gs: Node = get_tree().root.get_node("/root/GameState")
	gs.install_routes()
	gs.current_map_id = "map_test"
	var ks: Node = get_tree().root.get_node("/root/KeetService")
	ks.current_member_id = "gate-author"
	ks.current_role = "admin"
	ks.home_room_id = "home-gate"
	ks.current_room_id = "home-gate"
	get_tree().root.get_node("/root/GameMode").set_mode("build")
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	_run(map, gs, ks)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 12:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE home_reset: %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, gs: Node, ks: Node) -> void:
	var persist: Node = map.get("_persist")
	var level: Node = map.get("level")
	var hist: Node = map.get("_history")
	var dev_rec := {"v": 2, "kind": "brush.create", "undone": false, "g": "dev:100",
		"planes": _box(Vector3(70, 0, 70), Vector3(72, 2, 72)), "proj": [], "material_id": 0,
		"level_id": 0, "sky": false}
	var starter := {"format": 1, "map_id": "map_test", "game_version": "gate", "made_at": "now",
		"entries": {"map:map_test:world:level:dev:op:100": var_to_str(dev_rec),
			"map:map_test:world:sky:time": var_to_str(0.25)}, "assets": {}, "content": []}
	hist.set("starter_override", starter)
	var seeded: Dictionary = gs.seed_home_starter("home-gate", starter)
	persist.refold()
	_ok("setup: the starter seeded the Home map", int(seeded.get("written", 0)) == 2, seeded)

	# ---- my edits, a flatten, more edits ----
	var mine: Array = []
	for i in 3:
		mine.append(persist.record("brush.create", {"planes": _box(Vector3(80 + i * 3, 0, 80), Vector3(81 + i * 3, 1, 81)),
			"proj": [], "material_id": 0, "level_id": 0, "sky": false}))
	persist.refold()
	map.create_object("marker", Transform3D(Basis.IDENTITY, Vector3(5, 1, 5)))
	gs.set_state("world:sky:time", 0.8)
	persist.persist_source("b".repeat(64), "gate.map", 1)
	gs.set_state("world:players:gate-author", {"pos": [1, 2, 3]})
	persist.flatten_map()
	persist.refold()
	var late: String = persist.record("brush.create", {"planes": _box(Vector3(90, 0, 90), Vector3(91, 1, 91)),
		"proj": [], "material_id": 0, "level_id": 0, "sky": false})
	persist.refold()
	var before: Array[String] = gs.map_scoped_keys()
	var base_n := 0
	for k: String in before:
		var v: Variant = gs.get_state(k)
		if v is Dictionary and bool((v as Dictionary).get("base", false)):
			base_n += 1
	_ok("setup: a flattened map (base records) with edits on top", base_n > 0 and level.brush_by_key(late) >= 0,
		"%d base record(s), %d keys" % [base_n, before.size()])

	# ---- 6. refused outside Home / for a non-admin ----
	ks.current_room_id = "other-room"
	hist.refresh()
	var btn: Button = hist.get("_reset_btn")
	_ok("6. not at Home: no Reset button", btn != null and not btn.visible)
	hist.ask_flatten("reset")
	_ok("6. ... and asking does nothing", String(hist.get("_confirm_action")) == "")
	ks.current_room_id = "home-gate"
	ks.current_role = "peer"
	var refused: Dictionary = hist.HomeReset.reset(gs, ks, starter)
	_ok("6. a non-admin is refused, nothing changed", refused.has("error") and gs.map_scoped_keys().size() == before.size(),
		refused.get("error", ""))
	ks.current_role = "admin"

	# ---- 1. the panel path ----
	hist.refresh()
	_ok("1. at Home with a starter: the panel offers Reset", btn.visible)
	hist.ask_flatten("reset")
	_ok("1. asking opens the confirmation", String(hist.get("_confirm_action")) == "reset"
		and String((hist.get("_confirm") as ConfirmationDialog).dialog_text).contains("reset_backups"))
	hist.confirm_flatten()
	persist.refold()

	# ---- 2, 3 ----
	var after: Array[String] = gs.map_scoped_keys()
	after.sort()
	var lvl: Array = after.filter(func(k: String) -> bool: return k.begins_with("world:level:") and not k.ends_with(":source"))
	_ok("2. the journal is exactly the starter's record", lvl == ["world:level:dev:op:100"], lvl)
	_ok("2. the starter's sky value is back", is_equal_approx(float(gs.get_state("world:sky:time", -1.0)), 0.25),
		gs.get_state("world:sky:time"))
	_ok("2. the starter marker is set", gs.has_state("world:starter:applied"))
	var objs: Array = after.filter(func(k: String) -> bool: return k.begins_with("world:objects:"))
	_ok("2. no object of mine remains", objs.is_empty() and (persist.gestures() as Array).is_empty(), objs)
	_ok("3. kept: the player position and the level source",
		gs.has_state("world:players:gate-author") and gs.has_state("world:level:gate-author:source"), after)

	# ---- 4 ----
	var bk_path := ""
	var d := DirAccess.open("user://reset_backups_ci")
	if d != null:
		for f: String in d.get_files():
			if f.begins_with("map_test-") and (bk_path == "" or f > bk_path.get_file()):
				bk_path = "user://reset_backups_ci/" + f
	var bk: Variant = JSON.parse_string(FileAccess.get_file_as_string(bk_path)) if bk_path != "" else null
	var erased_expected: Array[String] = hist.HomeReset.keys_to_reset(before)
	_ok("4. the backup holds every erased key", bk is Dictionary
		and (bk["entries"] as Dictionary).size() == erased_expected.size(),
		"%s: %d vs %d" % [bk_path, (bk["entries"] as Dictionary).size() if bk is Dictionary else -1, erased_expected.size()])

	# ---- 5 ----
	_ok("5. the starter's brush is in the level", level.brush_by_key("dev:100") >= 0)
	var gone := true
	for dot: String in mine + [late]:
		if level.brush_by_key(dot) >= 0 and (level.get_live_brush_ids() as PackedInt32Array).has(level.brush_by_key(dot)):
			gone = false
	_ok("5. my brushes are gone", gone)