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)