extends SceneTree
## Gate: erasing a DURABLE key deletes it on the daemon.
##
## hyper-games #88 Tier 1 #3. GameState.erase_state cleared the two local mirrors
## and emitted state_changed(key, null) -- and never told keet-daemon. The key was
## still in the room's StateBase, so it came back on the next load:
##
## creatures an erased registry row re-spawned its creature (ghosts)
## terrain undo_last_edit / clear_edits did not persist past a reload
## bots an explicitly retired bot's declaration survived
##
## keet-daemon has always had the path (state.del -> @state/del-entry ->
## _applyDel); KeetGameState.erase_value calls it. Nothing in GameState did.
##
## Driven through the REAL GameState -> KeetService.game_state wiring with a fake
## client, so every RPC is captured and none can reach a live daemon.
var ok := true
var _ran := false
var _rejected: Array = []
class FakeClient extends KeetUdsClient:
var calls: Array = [] # [method, params]
func call_rpc(method: String, params: Dictionary = {}, callback: Callable = Callable()) -> void:
calls.append([method, params.duplicate()])
if callback.is_valid():
callback.call({"ok": true}, null)
func methods_for(key: String) -> Array:
var out: Array = []
for c: Array in calls:
if String((c[1] as Dictionary).get("key", "")) == key:
out.append(c[0])
return out
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 erase_durable: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
func _run() -> void:
var gs: Node = root.get_node_or_null("GameState")
var ks: Node = root.get_node_or_null("KeetService")
_p(gs != null and ks != null and ks.game_state != null, "GameState + KeetService.game_state present")
if gs == null or ks == null or ks.game_state == null:
return
gs.install_routes()
gs.current_map_id = "gate_erase_map"
gs.write_rejected.connect(func(k: String, _r: String) -> void: _rejected.append(k))
var fake := FakeClient.new()
fake.is_connected = true
var kgs: Node = ks.game_state
kgs.client = fake
kgs.current_room = "gate-erase-room" # set directly: no watch(), no poll
kgs.current_role = "peer"
var me: String = gs._my_member_id()
var mine := "world:terrain:edit:%s:5" % me
var stored := "map:gate_erase_map:" + mine
# 1. A durable key I own: set, then erase -> state.set then state.del, scoped.
gs.set_state(mine, {"type": "sphere", "pos": [0, 0, 0], "radius": 1.0, "str": -1.0, "op": 1})
_p(fake.methods_for(stored) == ["state.set"], "set_state sent state.set for %s" % stored)
gs.erase_state(mine)
_p(fake.methods_for(stored) == ["state.set", "state.del"],
"erase_state sent state.del for the SCOPED key (got %s)" % str(fake.methods_for(stored)))
_p(not gs.has_state(mine), "and the local mirror no longer has it")
# 2. A realtime key: no daemon traffic at all.
fake.calls.clear()
gs.set_state("player:pos:7", [1.0, 2.0, 3.0])
gs.erase_state("player:pos:7")
_p(fake.calls.is_empty(), "a REALTIME erase sends nothing to the daemon (got %s)" % str(fake.calls))
# 3. Another peer's durable key, as a plain peer: refused, not sent.
fake.calls.clear()
var theirs := "world:terrain:edit:someone-else:9"
gs._on_durable_updated("gate-erase-room", "map:gate_erase_map:" + theirs,
{"type": "sphere", "pos": [0, 0, 0], "radius": 1.0, "str": -1.0, "op": 1})
gs.erase_state(theirs)
_p(fake.calls.is_empty(), "erasing ANOTHER peer's key as a peer sends nothing (got %s)" % str(fake.calls))
_p(_rejected.has(theirs), "and reports write_rejected for it")