extends SceneTree
## DOES A MAP'S OPENING SKY RESPECT WHAT THE ROOM SAVED? — hyper-games #42 P1.
##
## "$HYPER_TPS_GODOT" --headless --script scenes/test/sky_defaults_gate.gd
##
## The reported symptom: set the sky to noon, change map, get something else. Two
## causes, both closed by one rule.
##
## map_dc_terrain had NO guard — it set current_time and minutes_per_day
## unconditionally in _ready, so entering it overwrote the room's saved sky.
## map_dc_level guarded ONE property, so a map that also sets minutes_per_day had
## that half unguarded even where the check ran.
##
## NOT A RACE. The old framing in sky_debug_panel calls this arrival order and
## answers it with SETTLE_DELAY, a 1.5 s re-assert. But the panel restores at setup
## AND on state_changed, so a map whose _ready runs after that is not winning a
## race — it is a writer with no business writing. A longer timer would not fix it
## and a shorter one would not cause it.
##
## Reached with a runtime load(), not a preload: preload is a const and compiles
## the dependency while `GameState` is not yet an identifier, which is why the
## journal gates cannot load headless at all.
var SD: GDScript = null
var _failures: Array[String] = []
func _check(label: String, ok: bool, detail: String) -> void:
if not ok:
_failures.append("%s (%s)" % [label, detail])
print(" %-54s %s %s" % [label, "OK " if ok else "FAIL", detail])
func _initialize() -> void:
print("sky defaults vs saved state — hyper-games #42 P1\n")
SD = load("res://scenes/maps/sky_defaults.gd")
var wanted: Array[String] = ["current_time", "minutes_per_day"]
# A room nobody has dressed: the map applies its whole opening look.
var none: Array[String] = []
_check("undressed room — map applies everything",
SD.unsaved(wanted, none) == wanted, str(SD.unsaved(wanted, none)))
_check("is_undressed true when nothing saved", SD.is_undressed(none), "true")
# THE REPORTED BUG. The room saved a time; the map must not overwrite it.
var saved_time: Array[String] = ["world:sky:current_time"]
var got: Array[String] = SD.unsaved(wanted, saved_time)
_check("saved current_time is NOT overwritten",
not got.has("current_time"), str(got))
_check("but minutes_per_day still applies", got.has("minutes_per_day"), str(got))
_check("is_undressed false once anything is saved",
not SD.is_undressed(saved_time), "false")
# THE SECOND GAP: map_dc_level only ever checked current_time, so a map
# setting minutes_per_day clobbered it even where the guard ran.
var saved_both: Array[String] = [
"world:sky:current_time", "world:sky:minutes_per_day"]
_check("both saved — the map applies NOTHING",
SD.unsaved(wanted, saved_both).is_empty(), str(SD.unsaved(wanted, saved_both)))
# A saved sky property the map does not set must not suppress the ones it does.
var unrelated: Array[String] = ["world:sky:fog_density"]
_check("an unrelated saved key suppresses nothing",
SD.unsaved(wanted, unrelated) == wanted, str(SD.unsaved(wanted, unrelated)))
# Keys from other namespaces must not register as sky state at all.
var foreign: Array[String] = [
"world:terrain:edit:a:1", "world:level:a:op:2", "world:sky:current_time"]
var f: Array[String] = SD.unsaved(wanted, foreign)
_check("foreign namespaces are ignored",
not f.has("current_time") and f.has("minutes_per_day"), str(f))
# Order is the caller's, so a map's report reads in the order it asked.
_check("order is preserved", SD.unsaved(wanted, none)[0] == "current_time",
str(SD.unsaved(wanted, none)))
print("")
if _failures.is_empty():
print("PASS")
quit(0)
else:
for fl: String in _failures:
print("FAILED: %s" % fl)
quit(1)