extends SceneTree
## Gate: a bulk durable load hands handlers a COMPLETE snapshot.
##
## _on_durable_loaded used to store and emit in one pass:
##
## for key in entries:
## _durable[local_key] = entries[key]
## state_changed.emit(local_key, entries[key], Channel.DURABLE)
##
## so every handler ran against a half-filled mirror, and which half depended on
## Dictionary order. That is invisible until a handler consults ANOTHER key to
## decide what to do -- which is exactly what AdminFacade does: it skips a command
## whose admin:result:<seq> marker is already present. "admin:cmd:" sorts before
## "admin:result:", so the marker was never in _durable yet when the command was
## emitted, the skip never fired, and EVERY command in the room re-executed on
## every cold load. That is hyper-games #77 item 5 -- ashatron replaying
## stortron's commands -- and the marker fix was correct all along; the fill order
## was defeating it.
##
## Measured in AI Testing Town: admin:result:pw-1789521468 EXISTS, and the
## pathwalker registration re-dispatched anyway, registering bots into whatever
## map happened to be current at boot.
##
## The invariant is not "admin keys sort a particular way" -- it is that a bulk
## load is ATOMIC from a handler's point of view. This gate asserts that directly,
## so any future handler that reads a sibling key is safe by construction.
var ok := true
var _ran := false
const SEQ := "gateorder1"
## Registering this is the side effect that proves the command executed.
const BOT_ID := "gate-replay-probe"
## A value no dispatch would ever produce. If AdminFacade re-runs the command it
## overwrites the marker with its own result, so the sentinel surviving IS the
## assertion that the command was skipped.
const SENTINEL := "gate-sentinel-do-not-overwrite"
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 durable_load_order: %s" % ("PASS" if ok else "FAIL"))
if not ok:
quit(1)
return true
func _run() -> void:
var gs: Node = root.get_node_or_null("GameState")
_p(gs != null, "GameState autoload present")
if gs == null:
return
_p(gs.has_method("_on_durable_loaded"), "GameState exposes the bulk-load entry point")
if not gs.has_method("_on_durable_loaded"):
return
var af: Node = root.get_node_or_null("AdminFacade")
_p(af != null, "AdminFacade is listening (it is the handler under test)")
if af == null:
return
# ⛔ WITHOUT THIS THE GATE TESTS A DIFFERENT GAME. install_routes() is called by
# game.gd, which a --script gate never loads, so _routes is empty and EVERY key
# falls back to REALTIME -- _on_durable_loaded then fills _durable while
# get_state reads _realtime, and the marker reads back null no matter what the
# code under test does. An earlier run of this gate reported exactly that and it
# was the gate's fault, not the product's.
gs.install_routes()
_p(int(gs._resolve("admin:result:x").channel) == gs.Channel.DURABLE,
"admin:result: routes DURABLE, so get_state reads the store the load fills")
# ⛔ THE OBSERVABLE CANNOT BE THE MARKER'S VALUE. Two earlier versions of this
# gate failed as gates. A state_changed handler is too late (AdminFacade is
# connected first, so it has already dispatched and rewritten the marker before
# a gate handler looks). And the marker's VALUE after the load is destroyed by
# the load itself: the unfixed code stores each key just before emitting it, so
# the seeded row lands AFTER the dispatch's own write and overwrites it --
# leaving the sentinel intact and the gate green against the bug.
#
# The honest observable is the command's SIDE EFFECT, which nothing in the load
# undoes. If the command ran, the bot exists.
var br: Node = root.get_node_or_null("BotRegistry")
_p(br != null, "BotRegistry is present to receive the side effect")
if br == null:
return
_p(_find_bot(br, BOT_ID) == false, "the probe bot does not exist before the load")
var entries := {
"admin:cmd:" + SEQ: "{\"op\": \"call\", \"path\": \"/global/bots.register\","
+ " \"args\": [\"" + BOT_ID + "\", [\"room\"], \"none\", {\"kind\": \"none\"}],"
+ " \"requester_role\": \"admin\"}",
"admin:result:" + SEQ: SENTINEL,
}
gs._on_durable_loaded("gate-room", entries)
_p(bool(gs.has_state("admin:cmd:" + SEQ)), "the command row is in the mirror afterwards")
_p(bool(gs.has_state("admin:result:" + SEQ)), "and so is the result marker")
_p(_find_bot(br, BOT_ID) == false,
"the ALREADY-APPLIED command did not run again (no '%s' was registered)" % BOT_ID)
func _find_bot(br: Node, id: String) -> bool:
for row: Variant in br.list():
if row is Dictionary and String((row as Dictionary).get("id", "")) == id:
return true
return false