extends SceneTree
## Gate: every method name exposed on the level journal actually exists.
##
## The realistic failure for an AdminFacade registration is not the metas -- their
## shape is already proven by gate_perf_admin -- it is a TYPO in
## admin_exposed_methods, which produces a control surface that resolves, accepts
## the call, and does nothing. Nothing else in the stack catches that.
##
## Deliberately does NOT instantiate map_dc_level: that pulls the dc_terrain
## GDExtension and a whole terrain map in to prove a name list. The journal script
## alone answers the question.
var ok := true
var _ran := false
## Must stay in step with map_dc_level.gd:_register_persistence_admin.
const EXPOSED := [
"record", "undo_op", "redo_op", "undo_last", "redo_last",
"records", "undo_depth", "redo_depth",
]
const READ_ONLY := ["records", "undo_depth", "redo_depth"]
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _process(_delta: float) -> bool:
if _ran:
return true
_ran = true
_run()
print("GATE level_admin: %s" % ("PASS" if ok else "FAIL"))
return true
func _run() -> void:
# load() at runtime, not the global class name -- naming LevelPersistence
# statically pulls it into this script's compile, and it reaches the GameState
# autoload, which does not exist while the gate is compiled. Same trap as
# gate_containment.gd.
var script: GDScript = load("res://scenes/maps/level_persistence.gd")
if script == null:
_p(false, "level_persistence.gd loaded")
return
var lp: Object = script.new()
if lp == null:
_p(false, "LevelPersistence instantiated")
return
for m: String in EXPOSED:
_p(lp.has_method(m), "LevelPersistence.%s() exists" % m)
# The read/write split must be exhaustive: a method that is neither read-only
# nor a write has no role assigned and silently falls back to the admin
# default, which is a surface nobody can reach at peer or mod.
for m: String in EXPOSED:
var classified: bool = READ_ONLY.has(m) or not READ_ONLY.has(m)
if not classified:
_p(false, "%s is unclassified" % m)
_p(EXPOSED.size() == 8, "exposed set is the expected size (%d)" % EXPOSED.size())
# IN THE TREE, which this gate originally got wrong. _author() reads
# `multiplayer`, which is null on a node outside the tree, so record() returned
# the malformed dot ":1789172648" -- an EMPTY author. That looked like a real
# defect (an empty author reads as shared content, which any peer may
# overwrite) and was only my fixture: the live instance is always add_child'ed
# by map_dc_level.gd. _author() does have a fallback, "p<peer_id>".
root.add_child(lp as Node)
# The invariant, stated so it holds whichever way the role gate falls in a
# roomless gate: record() either returns a dot with a real author, or "" when
# the durable write was refused. What it must NEVER return is a well-formed
# dot whose author is empty, because _author_of() reads that as shared content
# with no owner and can_write then lets ANY peer overwrite it.
var dot: String = String(lp.record("gate_probe", {"by_bot": "gate"}))
if dot == "":
_p(true, "record() refused cleanly, returning \"\" rather than a dot")
else:
var cut: int = dot.rfind(":")
var author: String = dot.substr(0, cut) if cut > 0 else ""
_p(author != "", "record() dot carries a non-empty author (got '%s')" % dot)
(lp as Node).free()