extends SceneTree
## Gate: a settings dump reports exactly what was changed at runtime.
##
## The dump exists so a look tuned live (sliders, the Remote inspector, a panel)
## can be turned into a map's DEFAULTS in source. Its map section compares every
## stored property of the running map against a FRESH, uncached load of the map's
## own scene file. What that must get right:
##
## - a changed NODE property is reported, with the live and the file value
## - a changed property INSIDE a resource is reported -- a material, a clouds
## resource -- which is where most of a map's look actually lives. A cached
## load would hand back the very instance that was edited, and the dump would
## say "nothing changed"; that is the reason for CACHE_MODE_IGNORE_DEEP
## - an untouched property is NOT reported, or the output is noise
##
## Instances the REAL map_flat.tscn: the comparison is against the scene file, so
## a fixture would only prove the fixture.
var ok := true
var _ran := false
## Set on the LAST line of _run. A script error aborts _run without failing a
## check, and the first run of this gate printed PASS exactly that way.
var _completed := false
func _p(c: bool, m: String, detail: String = "") -> void:
print(("ok " if c else "FAIL: ") + m + ("" if c or detail == "" else " [" + detail + "]"))
if not c:
ok = false
func _process(_delta: float) -> bool:
if _ran:
return true
_ran = true
_run()
_p(_completed, "the gate ran to the end (no script error cut it short)")
print("GATE settings_dump: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
func _find(changes: Array, node: String, prop: String) -> Dictionary:
for c: Dictionary in changes:
if String(c.get("node", "")) == node and String(c.get("prop", "")) == prop:
return c
return {}
func _run() -> void:
var dump_script: Script = load("res://scenes/game/settings_dump.gd")
_p(dump_script != null, "settings_dump.gd exists")
if dump_script == null:
return
var map: Node = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
root.add_child(map)
var surface := map.get_node(^"Ground/Surface") as MeshInstance3D
var mat := surface.get_surface_override_material(0) as StandardMaterial3D
surface.transparency = 0.25 # a node property (file: default 0.0)
mat.roughness = 0.4 # inside a resource (file: 0.9)
# albedo_color is left alone: it must NOT appear.
var path: String = dump_script.call("write", map)
_p(path != "" and FileAccess.file_exists(path), "the dump was written", path)
var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
_p(parsed is Dictionary, "and is JSON")
if not (parsed is Dictionary):
return
var d: Dictionary = parsed
_p(String(d.get("map_scene", "")) == "res://scenes/maps/map_flat.tscn", "it names the map scene",
str(d.get("map_scene")))
var changes: Array = d.get("map_changes", [])
var t := _find(changes, "Ground/Surface", "transparency")
_p(not t.is_empty(), "a changed node property is reported")
_p(is_equal_approx(float(t.get("live", -1)), 0.25) and is_equal_approx(float(t.get("file", -1)), 0.0),
"with its live and file values", str(t))
var r := _find(changes, "Ground/Surface", "surface_material_override/0/roughness")
_p(not r.is_empty(), "a changed property INSIDE a resource is reported")
_p(is_equal_approx(float(r.get("live", -1)), 0.4) and is_equal_approx(float(r.get("file", -1)), 0.9),
"with its live and file values", str(r))
_p(String(r.get("edit_in", "")) == "res://scenes/maps/map_flat.tscn",
"and says which file to edit (the material is embedded in the map)", str(r.get("edit_in")))
_p(_find(changes, "Ground/Surface", "surface_material_override/0/albedo_color").is_empty(),
"an untouched property is NOT reported")
print(" (%d change(s) reported in total)" % changes.size())
for c: Dictionary in changes:
print(" %s . %s : file=%s live=%s" % [c.get("node"), c.get("prop"), str(c.get("file")), str(c.get("live"))])
map.queue_free()
_completed = true