extends SceneTree
## Gate: the 3 s room poll re-emits NOTHING that did not change.
##
## hyper-games #88 Tier 1 #1. KeetGameState._fetch_all ended every poll with
##
## state_loaded.emit(room_id, entries) # ALL entries, changed or not
##
## and GameState._on_durable_loaded answers that with state_changed for EVERY key.
## Downstream that is not idle chatter:
##
## TerrainEditManager every edit but the newest has seq < _max_applied_seq, so
## each one requests a re-fold -> clear_edits() and a replay
## of the WHOLE journal, every edited chunk remeshed
## LevelPersistence any world:level: key sets _dirty -> the level refolds
##
## every 3 s, on every peer, in a room. The first fetch after watch() had the
## opposite fault too: each key went out once as state_updated (stored and emitted
## one at a time, in Dictionary order) and then again in the bulk state_loaded --
## the order #77 item 5 made atomic in _on_durable_loaded, undone one signal early.
##
## The observable is GameState.state_changed, the bus every subsystem listens to,
## driven through the real KeetGameState -> GameState wiring with a fake daemon.
var ok := true
var _ran := false
const MAP := "gate_poll_map"
const ROOM := "gate-poll-room"
class FakeClient extends KeetUdsClient:
## What state.list answers with: stored key -> var_to_str value, as the daemon
## returns it (v3 flat shape).
var entries: Dictionary = {}
## hold = true: a state.list SNAPSHOTS `entries` now but answers only on
## release() -- the reply of a poll that was in flight while other RPCs landed.
var hold := false
var _held: Array = []
func call_rpc(method: String, params: Dictionary = {}, callback: Callable = Callable(), _timeout_sec: float = -1.0) -> void:
if not callback.is_valid():
return
if method == "state.list":
var snap := {"entries": entries.duplicate()}
if hold:
_held.append([callback, snap])
else:
callback.call(snap, null)
elif method == "state.set":
entries[String(params["key"])] = String(params["value"])
callback.call({}, null)
elif method == "state.del":
entries.erase(String(params["key"]))
callback.call({}, null)
else:
callback.call(null, "not faked")
func release() -> void:
hold = false
var h := _held
_held = []
for c: Array in h:
(c[0] as Callable).call(c[1], null)
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 poll_no_reemit: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
var _changed: Array = []
func _run() -> void:
var gs: Node = root.get_node_or_null("GameState")
_p(gs != null, "GameState autoload present")
if gs == null:
return
# game.gd installs the routes; a --script gate never loads it (see
# gate_durable_load_order for what happens without this).
gs.install_routes()
gs.current_map_id = MAP
var fake := FakeClient.new()
fake.is_connected = true
var e1 := {"pos": [1.0, 2.0, 3.0], "radius": 1.5, "str": -1.0, "type": "sphere", "op": 1}
var e2 := {"pos": [4.0, 5.0, 6.0], "radius": 2.0, "str": -1.0, "type": "sphere", "op": 1}
var k1 := "map:%s:world:terrain:edit:alice:100" % MAP
var k2 := "map:%s:world:terrain:edit:alice:101" % MAP
fake.entries = {k1: var_to_str(e1), k2: var_to_str(e2)}
var kgs := KeetGameState.new()
kgs.auto_poll = false # the gate drives _poll_state itself
kgs.client = fake
root.add_child(kgs) # _ready builds the poll timer + overlay
kgs.state_loaded.connect(gs._on_durable_loaded)
kgs.state_updated.connect(gs._on_durable_updated)
gs.state_changed.connect(func(key: String, value: Variant, _ch: int) -> void:
if key.begins_with("world:terrain:edit:"):
_changed.append([key, value]))
# 1. The first fetch after watch(): every key once, as one atomic load.
_changed.clear()
kgs.watch(ROOM)
_p(_changed.size() == 2,
"initial load emits each of the 2 keys exactly once (got %d)" % _changed.size())
_p(bool(gs.room_state_loaded), "and marks the room loaded")
# 2. A poll where nothing changed: silence.
_changed.clear()
kgs._poll_state()
_p(_changed.is_empty(),
"an UNCHANGED poll emits nothing (got %d state_changed)" % _changed.size())
# 3. A poll with one new key and one removed key: exactly those two.
var e3 := {"pos": [7.0, 8.0, 9.0], "radius": 1.0, "str": -1.0, "type": "sphere", "op": 1}
var k3 := "map:%s:world:terrain:edit:bob:102" % MAP
fake.entries = {k1: var_to_str(e1), k3: var_to_str(e3)}
_changed.clear()
kgs._poll_state()
var keys: Array = []
for c: Array in _changed:
keys.append(c[0])
_p(_changed.size() == 2, "a poll with 1 added + 1 removed emits exactly 2 (got %d: %s)"
% [_changed.size(), str(keys)])
_p(keys.has("world:terrain:edit:bob:102"), "the added key is emitted")
var removed_null := false
for c: Array in _changed:
if c[0] == "world:terrain:edit:alice:101" and c[1] == null:
removed_null = true
_p(removed_null, "the removed key is emitted as null")
# 4. A poll IN FLIGHT while a local write lands (hyper-games #108, 2026-10-07).
# poll sent -> carve written + confirmed -> the poll's snapshot, taken BEFORE
# the write, arrives without the key. That is not a deletion. Reported as one
# (null), TerrainEditManager re-folded: clear_edits + a full terrain rebuild +
# replay of the whole journal, after the player's own carve (4 in one session).
var e4 := {"pos": [1.0, 1.0, 1.0], "radius": 3.0, "str": -3.0, "type": "sphere", "op": 1}
var k4 := "map:%s:world:terrain:edit:alice:103" % MAP
fake.hold = true
kgs._poll_state() # snapshot WITHOUT k4, answer held
kgs.set_value(k4, e4) # the carve: written and confirmed
_changed.clear()
fake.release() # the stale answer arrives
var nulled := false
for c: Array in _changed:
if c[0] == "world:terrain:edit:alice:103" and c[1] == null:
nulled = true
_p(not nulled, "a poll sent BEFORE a local write does not report that key erased (got %s)"
% str(_changed))
_changed.clear()
kgs._poll_state() # a poll sent AFTER the write sees it
_p(_changed.is_empty(), "and the next poll, which includes it, emits nothing (got %s)"
% str(_changed))
# The mirror: a poll in flight while a local ERASE lands must not resurrect it.
fake.hold = true
kgs._poll_state() # snapshot WITH k4, answer held
kgs.erase_value(k4) # undo: erased and confirmed
_changed.clear()
fake.release()
var revived := false
for c: Array in _changed:
if c[0] == "world:terrain:edit:alice:103" and c[1] != null:
revived = true
_p(not revived, "a poll sent BEFORE a local erase does not bring the key back (got %s)"
% str(_changed))
# A REAL remote delete still arrives: k1 gone from a poll sent after any write.
fake.entries.erase(k1)
_changed.clear()
kgs._poll_state()
var k1_null := false
for c: Array in _changed:
if c[0] == "world:terrain:edit:alice:100" and c[1] == null:
k1_null = true
_p(k1_null, "a key deleted elsewhere is still reported erased (got %s)" % str(_changed))
# 5. A poll arriving for a room we no longer watch is dropped.
kgs.watch("other-room")
_changed.clear()
kgs._fetch_all(ROOM)
_p(_changed.is_empty(), "a stale fetch for a previous room emits nothing (got %d)" % _changed.size())