extends SceneTree
## IS TERRAIN REPLAY ORDERED? — hyper-games #42 P1.
##
## "$HYPER_TPS_GODOT" --headless --script scenes/test/terrain_order_gate.gd
##
## DCEditList composes with min (subtract) and max (add), and those do not commute
## (dc-terrain libdc/test/test_edit_order.cpp: base 0, add 5, subtract -3 gives -3
## one way and +5 the other). _load_existing_edits iterated keys_with_prefix() with
## NO SORT, so two peers holding the same journal could materialise different
## geometry, and nothing detected it.
##
## THE CONTROL IS THE POINT, as in arch_field_gate. Asserting only that the fixed
## path converges would pass just as happily if the two orderings had stopped
## overlapping, or if the probe had drifted somewhere neither edit reaches — a
## green light on a test that can no longer see the bug. So this asserts BOTH:
##
## CONTROL applied in raw order, the two permutations DISAGREE
## FIXED applied through sort_edit_keys, they AGREE
##
## If the control ever goes quiet, this gate has stopped measuring anything and the
## FIXED half means nothing.
##
## DCTerrain outside the tree keeps its edit list and evaluates get_distance on the
## CPU, so no display, no meshing and no GPU — same trick arch_field_gate uses.
const ORDER := preload("res://scenes/game/terrain_edit_order.gd")
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])
## One add and one overlapping subtract, as durable records would describe them.
## Hard CSG on both, because the smooth metaball is additive and commutes.
func _records() -> Array:
return [
{"key": "world:terrain:edit:alice:10",
"pos": Vector3(-0.5, 0, 0), "radius": 2.0, "str": 6.0, "op": 1},
{"key": "world:terrain:edit:bob:20",
"pos": Vector3(0.5, 0, 0), "radius": 2.0, "str": -6.0, "op": 1},
]
func _apply(terrain: Node, recs: Array) -> float:
terrain.clear_edits()
for r: Dictionary in recs:
terrain.edit_terrain(r["pos"], r["radius"], r["str"], r["op"], false)
return terrain.get_distance(Vector3.ZERO)
func _initialize() -> void:
print("terrain replay ordering — hyper-games #42 P1\n")
if not ClassDB.class_exists("DCTerrain"):
print(" DCTerrain not registered — GDExtension missing. SKIP")
quit(0)
return
var terrain: Node = ClassDB.instantiate("DCTerrain")
var recs: Array = _records()
var rev: Array = [recs[1], recs[0]]
# ---- CONTROL: raw order, the two permutations must disagree -------------
var raw_a: float = _apply(terrain, recs)
var raw_b: float = _apply(terrain, rev)
_check("CONTROL: raw order diverges", raw_a != raw_b,
"%+.4f vs %+.4f" % [raw_a, raw_b])
_check("CONTROL: and they disagree on solid vs air",
signf(raw_a) != signf(raw_b), "signs %+.0f / %+.0f" % [signf(raw_a), signf(raw_b)])
# ---- FIXED: through the real comparator, they must agree ----------------
var keys_fwd: Array[String] = []
var keys_rev: Array[String] = []
for r: Dictionary in recs:
keys_fwd.append(String(r["key"]))
for r: Dictionary in rev:
keys_rev.append(String(r["key"]))
var by_key: Dictionary = {}
for r: Dictionary in recs:
by_key[String(r["key"])] = r
var sorted_fwd: Array[String] = ORDER.sort_keys(keys_fwd)
var sorted_rev: Array[String] = ORDER.sort_keys(keys_rev)
_check("sort is a total order (permutations agree)", sorted_fwd == sorted_rev,
str(sorted_fwd))
var ord_a: Array = []
for k: String in sorted_fwd:
ord_a.append(by_key[k])
var ord_b: Array = []
for k: String in sorted_rev:
ord_b.append(by_key[k])
var fix_a: float = _apply(terrain, ord_a)
var fix_b: float = _apply(terrain, ord_b)
_check("FIXED: sorted replay converges", fix_a == fix_b,
"%+.4f vs %+.4f" % [fix_a, fix_b])
# The sorted answer must be one of the two the raw orders produce — sorting
# picks an existing composition, it does not invent a third.
_check("sorted result is one of the raw orders",
fix_a == raw_a or fix_a == raw_b, "%+.4f" % fix_a)
# ---- seq beats author, author only breaks ties --------------------------
var mixed: Array[String] = [
"world:terrain:edit:zoe:1",
"world:terrain:edit:alice:9",
"world:terrain:edit:alice:2",
"world:terrain:edit:bob:2",
]
var got: Array[String] = ORDER.sort_keys(mixed)
var want: Array[String] = [
"world:terrain:edit:zoe:1",
"world:terrain:edit:alice:2",
"world:terrain:edit:bob:2",
"world:terrain:edit:alice:9",
]
_check("seq first, author breaks ties", got == want, str(got))
print("")
if _failures.is_empty():
print("PASS")
quit(0)
else:
for f: String in _failures:
print("FAILED: %s" % f)
quit(1)