extends SceneTree
## IS THE JOURNAL CLOCK LAMPORT? — hyper-games #42 P1.
##
## "$HYPER_TPS_GODOT" --headless --script scenes/test/journal_lamport_gate.gd
##
## reserve_seq() seeds from max(wall clock, highest seq seen). That scan used to
## cover ONLY THE AUTHOR'S OWN KEYS, which makes it a per-author counter: the
## (seq, author) order then sorts by whose CLOCK ran ahead rather than by what
## happened. Convergence was never at risk — the order was already total and
## identical everywhere — but a peer could write an edit after seeing yours and
## have it sort BEFORE yours.
##
## PRELOAD IS THE TRAP, not the autoloads. preload is a const and forces the
## dependency to compile while `GameState` is still not an identifier — which is
## why level_journal_roundtrip_gate and operator_gate cannot load headless at all
## ("Compile Error: Identifier not found: GameState", verified against a clean
## tree, so it predates this work). A runtime load() from _initialize() compiles
## lazily and works fine. The failure mode is what makes this expensive: the gate
## HANGS to its timeout instead of reporting a parse error.
##
## THE OVERWRITE GUARD IS THE THING NOT TO BREAK. The own-max scan existed because
## the counter runs ahead of the clock, so a restart inside that window would
## re-mint seqs that already name records and silently overwrite them — the durable
## view is last-write-wins per key. A scan over ALL authors is strictly stronger, and
## the last case here pins that.
var LP: GDScript = null
var _failures: Array[String] = []
func _check(label: String, ok: bool, detail: String) -> void:
if not ok:
_failures.append("%s (%s)" % [label, detail])
print(" %-52s %s %s" % [label, "OK " if ok else "FAIL", detail])
func _initialize() -> void:
print("journal clock — hyper-games #42 P1\n")
LP = load("res://scenes/maps/level_persistence.gd")
var mine: Array[String] = [
"world:level:alice:op:100",
"world:level:alice:op:140",
]
var theirs: Array[String] = [
"world:level:bob:op:900",
]
var both: Array[String] = mine + theirs
_check("scans my own keys", LP.max_seq_in(mine) == 140, str(LP.max_seq_in(mine)))
# THE BEHAVIOUR CHANGE. Before, another author's higher seq was skipped, so my
# next seq could land below a record I had already seen.
_check("another author's higher seq is SEEN", LP.max_seq_in(both) == 900,
str(LP.max_seq_in(both)))
# Causality, stated as the thing a user would notice: I see bob's edit, then I
# write. Mine must sort after his.
var next_seq: int = LP.max_seq_in(both) + 1
_check("an edit made after seeing bob's sorts after it", next_seq > 900,
"next=%d vs bob 900" % next_seq)
# THE OVERWRITE GUARD, preserved: the all-author max is never below the own max.
_check("never below my own max (no re-minted seqs)",
LP.max_seq_in(both) >= LP.max_seq_in(mine),
"%d >= %d" % [LP.max_seq_in(both), LP.max_seq_in(mine)])
# A memberId containing colons must not confuse the seq parse — the infix is
# ":op:", so the split is on that and not on the last colon.
var colony: Array[String] = ["world:level:peer:a:b:op:77"]
_check("author with colons parses", LP.max_seq_in(colony) == 77,
str(LP.max_seq_in(colony)))
# Junk is skipped, not counted as 0 and not crashing.
var junk: Array[String] = ["world:level:broken", "world:level:carol:op:5"]
_check("malformed keys are skipped", LP.max_seq_in(junk) == 5, str(LP.max_seq_in(junk)))
_check("empty journal is 0", LP.max_seq_in([] as Array[String]) == 0, "0")
print("")
if _failures.is_empty():
print("PASS")
quit(0)
else:
for f: String in _failures:
print("FAILED: %s" % f)
quit(1)