extends SceneTree
## Gate: a re-delivery of an edit this peer already applied does not re-fold.
##
## hyper-games #88 Tier 1 #1 (terrain half). TerrainEditManager._on_state_changed
## ran the out-of-order check BEFORE asking whether the edit was already applied:
##
## if seq < _max_applied_seq: _request_rebuild() # clear_edits + full replay
##
## Carve twice quickly -- edit A (seq n), edit B (seq n+1), both applied locally --
## and A's own write confirmation (set_value's callback -> state_updated ->
## state_changed) then arrives with seq n < n+1. That threw away the whole journal
## and replayed it, for an edit already in the field. Carving fast is exactly when
## that happens, once per carve.
##
## A genuinely NEW edit that sorts below the high-water mark must still re-fold --
## min/max composition does not commute (hyper-games #42 P1). Both are asserted.
var ok := true
var _started := false
var _done := false
class FakeTerrain extends Node:
var clears: int = 0
var edits: int = 0
func clear_edits() -> void:
clears += 1
func edit_terrain(_p: Vector3, _r: float, _s: float, _op: int, _i: bool) -> void:
edits += 1
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 terrain_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_echo_map"
var fake := FakeTerrain.new()
root.add_child(fake)
# load(), not the class name: naming TerrainEditManager compiles it at parse
# time, before GameState is an identifier (see level_persistence.max_seq_in).
var tem_script: GDScript = load("res://scenes/game/terrain_edit_manager.gd")
var tem: Node = tem_script.new()
root.add_child(tem)
tem._terrain_node = fake # _find_terrain returns a valid cached node
# The 1 s retry nulls _terrain_node to re-search by class, and a fake is not a
# DCTerrain -- it would find nothing and every apply would silently no-op.
tem._retry_timer.stop()
await _frames(2) # let _ready's deferred load run and settle
fake.clears = 0
# Two local carves, as the player makes them.
tem.apply_sphere(Vector3(1, 2, 3), 1.5, -1.0, 1)
tem.apply_sphere(Vector3(4, 5, 6), 1.5, -1.0, 1)
var keys: Array[String] = tem_script.sort_edit_keys(
gs.keys_with_prefix("world:terrain:edit:"))
_p(keys.size() == 2, "two edits are in the mirror (got %d)" % keys.size())
if keys.size() != 2:
_done = true
return
_p(fake.edits == 2, "both were applied to the terrain (got %d)" % fake.edits)
# The FIRST carve's own write confirmation arrives after the second applied.
var first: String = keys[0]
var stored: String = "map:%s:%s" % [gs.current_map_id, first]
gs._on_durable_updated("gate-room", stored, gs.get_state(first))
await _frames(3) # _request_rebuild is deferred
_p(fake.clears == 0,
"the echo of an applied edit does NOT re-fold (clear_edits called %d times)" % fake.clears)
_p(fake.edits == 2, "and does not re-apply it (edits %d)" % fake.edits)
# A NEW edit from another peer that sorts BELOW what is applied must re-fold.
var lower := "world:terrain:edit:zed:1"
gs._on_durable_updated("gate-room", "map:%s:%s" % [gs.current_map_id, lower],
{"type": "sphere", "pos": [0, 0, 0], "radius": 1.0, "str": -1.0, "op": 1})
await _frames(3)
_p(fake.clears == 1,
"a new out-of-order edit still re-folds (clear_edits called %d times)" % fake.clears)
_done = true