OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_map_scope_race.gd

61 lines · gd ref:
extends Node
## GATE — the map scope follows the LOADED map (user 2026-10-06: "we are saving map_test
## in user and also in the game directory and we are having link issues").
##
## The race: change_map -> GameState.enter_map(<new>) starts a room fetch; change_map
## writes world:settings:current_map = <new> only AFTER the map has loaded. The fetch's
## answer carries the OLD saved map, and _on_durable_loaded's "pass 1" adopted it —
## current_map_id flipped back to map_test while map_mountain was loaded, and every
## map-scoped write after that (the sky panel's world:sky:*, terrain edits, objects)
## landed in map_test's namespace. Seen in a settings dump: map_scene map_mountain.tscn,
## meta.map_id "map_test".
##   R1  after enter_map("map_mountain"), a room load whose saved current_map is
##       map_test does NOT move the scope: current_map_id stays map_mountain
##   R2  that load's map_mountain keys are un-scoped (visible), map_test's are not
##   R3  a COLD join (no map entered yet) still adopts the room's saved map
##
##   CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . \
##       res://scenes/test/gate_map_scope_race.tscn

var _fails := 0
var _checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	_checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		_fails += 1


func _ready() -> void:
	_run.call_deferred()


func _stale_load() -> Dictionary:
	return {
		"world:settings:current_map": {"path": "res://scenes/maps/map_test.tscn", "ts": 1.0},
		"map:map_mountain:world:sky:fog_end": 3000.0,
		"map:map_test:world:sky:fog_end": 1000.0,
	}


func _run() -> void:
	var gs: Node = get_node("/root/GameState")
	# ---- R3 first: a cold join (fresh scope state) adopts the room's map ----
	gs.set("_map_entered", false)
	gs.current_map_id = "map_test"
	gs._on_durable_loaded("gate-room", {"world:settings:current_map": {"path": "res://scenes/maps/map_flat.tscn"}})
	_p(String(gs.current_map_id) == "map_flat", "R3 a cold join adopts the room's saved map", gs.current_map_id)
	# ---- R1 / R2: an explicit map entry is not overridden by a stale load ----
	gs.enter_map("map_mountain")
	gs._on_durable_loaded("gate-room", _stale_load())
	_p(String(gs.current_map_id) == "map_mountain", "R1 a stale room load does not move the scope off the loaded map", gs.current_map_id)
	# The durable store directly: world:sky:* is routed DURABLE by game.gd at runtime,
	# which this gate does not load, so get_state would read the realtime store.
	var dur: Dictionary = gs.get("_durable")
	_p(is_equal_approx(float(dur.get("world:sky:fog_end", -1.0)), 3000.0),
		"R2 the loaded map's own keys are the ones loaded", dur.get("world:sky:fog_end", -1.0))
	print("GATE map_scope_race: %s (%d checks, %d failed)" % ["PASS" if _fails == 0 else "FAIL", _checks, _fails])
	get_tree().quit(0 if _fails == 0 else 1)