OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_map_change_keys.gd

85 lines · gd ref:
extends SceneTree
## GATE — the builder keys survive a MAP CHANGE (user 2026-10-05: "a weird race where
## keys do not do what they should" — N, Shift+Space, Delete dead).
##
## Every map pushes the `builder` keymap context in _ready and pops it in _exit_tree.
## game.change_map() queue_frees the old map and adds the new one AT ONCE, so the new
## map's push lands first (a no-op, it is already there) and the old map's pop runs at
## the END of the frame — taking `builder` with it. Every builder key is then dead
## until the next map load; mouse picks, which do not go through the keymap, still work.
##
## This does exactly what change_map does, with the real map scene, and asserts the
## context is still there afterwards. Plus the Keymap rule itself: holds are per owner.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_map_change_keys.gd

var ok := true
var _step := 0
var _a: Node = null
var _b: Node = null
var _km: Node = null
var _rules_done := false


func _p(c: bool, m: String) -> void:
	print(("ok   " if c else "FAIL: ") + m)
	if not c:
		ok = false


func _process(_delta: float) -> bool:
	_step += 1
	match _step:
		1:
			_km = root.get_node_or_null("Keymap")
			_p(_km != null, "Keymap autoload present")
			if _km == null:
				return _done()
			_rules()
			var scene := load("res://scenes/maps/map_test.tscn") as PackedScene
			_a = scene.instantiate()
			root.add_child(_a)
		4:
			_p(_km.get("_stack").has("builder"), "map A loaded: builder on the stack %s" % str(_km.get("_stack")))
			# change_map: the old map is queue_freed, the new one added immediately.
			_a.queue_free()
			_b = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
			root.add_child(_b)
		8:
			_p(not is_instance_valid(_a), "map A is gone")
			_p(_km.get("_stack").has("builder"),
					"after the change, builder is STILL on the stack %s" % str(_km.get("_stack")))
			_b.queue_free()
		11:
			_p(not _km.get("_stack").has("builder"), "and the last map leaving takes it %s" % str(_km.get("_stack")))
			return _done()
	return false


## The Keymap rule on its own: a context stays while ANY owner holds it; a push/pop
## with no owner behaves exactly as it always did.
func _rules() -> void:
	var o1 := Node.new()
	var o2 := Node.new()
	_km.push_context("menu", o1)
	_km.push_context("menu", o2)
	_km.pop_context("menu", o1)
	_p(_km.get("_stack").has("menu"), "owned: o2 still holds menu after o1 pops")
	_km.pop_context("menu", o2)
	_p(not _km.get("_stack").has("menu"), "owned: menu gone when the last holder pops")
	_km.push_context("menu")
	_km.push_context("menu")
	_km.pop_context("menu")
	_p(not _km.get("_stack").has("menu"), "unowned: one pop removes it, as before")
	o1.free()
	o2.free()
	_rules_done = true


func _done() -> bool:
	# A SCRIPT ERROR aborts _rules() without failing a check; only a full run passes.
	_p(_rules_done, "the owner rules ran to the end (no script error aborted them)")
	print("GATE map_change_keys: %s" % ("PASS" if ok else "FAIL"))
	quit(0 if ok else 1)
	return true