extends SceneTree
## Gate: LevelPersistence refolds when the journal CHANGES, not when a record is
## re-delivered.
##
## hyper-games #88 Tier 1 #1 (level half). _on_state_changed set _dirty for ANY
## world:level: key, and a refold rebuilds the whole level from the journal. Every
## level edit arrives twice -- set_state emits it locally, then set_value's
## callback echoes the same value back (state_updated -> state_changed) -- so each
## edit refolded the level twice. Until the poll fix it was also once per 3 s.
##
## A real change must still refold: an undo rewrites the SAME key with a new value
## (a tombstone), and a peer's new record is a new key. Both asserted.
var ok := true
var _started := false
var _done := false
class FakeLevel extends Node:
var folds: int = 0
func apply_journal(_recs: Array) -> Dictionary:
folds += 1
return {"applied": 0, "skipped": 0, "undone": 0, "records": 0}
func rebuild() -> void:
pass
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 not _started:
_started = true
_run()
if _done:
print("GATE level_echo_no_refold: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return false
func _frames(n: int) -> void:
for i in n:
await process_frame
func _run() -> void:
var gs: Node = root.get_node_or_null("GameState")
_p(gs != null, "GameState autoload present")
if gs == null:
_done = true
return
gs.install_routes()
gs.current_map_id = "gate_level_echo_map"
# load(), not the class name -- naming it compiles it before GameState exists.
var lp: Node = (load("res://scenes/maps/level_persistence.gd") as GDScript).new()
var fake := FakeLevel.new()
root.add_child(fake)
root.add_child(lp)
lp.level = fake
await _frames(2)
fake.folds = 0
# A local edit: one refold.
var dot: String = lp.record("carve", {"type": "sphere", "pos": [1.0, 2.0, 3.0], "radius": 0.6})
_p(dot != "", "the record was written (dot %s)" % dot)
await _frames(2)
_p(fake.folds == 1, "a new record refolds once (folds %d)" % fake.folds)
# Its own write confirmation: the same key, the same value.
var cut: int = dot.rfind(":")
var key: String = lp._op_key(dot.substr(0, cut), int(dot.substr(cut + 1)))
gs._on_durable_updated("gate-room", "map:%s:%s" % [gs.current_map_id, key], gs.get_state(key))
await _frames(2)
_p(fake.folds == 1, "the echo of an unchanged record does NOT refold (folds %d)" % fake.folds)
# Undo rewrites the same key with a new value: must refold.
_p(lp.undo_op(dot), "undo wrote a tombstone")
await _frames(2)
_p(fake.folds == 2, "a changed value refolds (folds %d)" % fake.folds)
# A peer's new record: must refold.
gs._on_durable_updated("gate-room",
"map:%s:world:level:zed:op:5" % gs.current_map_id,
{"v": 2, "kind": "carve", "undone": false, "src": "",
"type": "sphere", "pos": [0.0, 0.0, 0.0], "radius": 0.6})
await _frames(2)
_p(fake.folds == 3, "a peer's new record refolds (folds %d)" % fake.folds)
# A map change must refold even with no key changing.
gs.map_changing.emit(gs.current_map_id)
await _frames(2)
_p(fake.folds == 4, "a map change refolds (folds %d)" % fake.folds)
_done = true