OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / terrain_order_gate.gd

133 lines · gd ref:
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)