OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / journal_texture_gate.gd

179 lines · gd ref:
extends Node
## HEADLESS GATE — texture intent through the REAL journal path (hyper-games #94 P1
## step 2, design gate 5): record -> GameState -> LevelPersistence._to_record ->
## DCLevel._record_from -> fold -> get_brush / the derived projection table.
##
## #41 §4's trap is the reason this exists: a record kind or field missing from
## `_to_record` becomes {} (or is silently dropped) and VANISHES on the next fold,
## while the C++ oracle -- which never goes through GDScript -- stays green.
## That trap had already caught two fields: brush.create's `clearance` and
## `group` were written by the box operator, read by the C++ decode, and dropped by
## _to_record, so every refold rebuilt a clearance volume as a SOLID brush.
##
## RUN AS A SCENE (level_persistence.gd names GameState at compile time):
##   "$HYPER_TPS_GODOT" --headless res://scenes/test/journal_texture_gate.tscn

var fails := 0
var checks := 0


func _ok(what: String, cond: bool, note: String = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-64s %s  %s" % [what, "OK" if cond else "**FAIL**", note])


func _box(lo: Vector3, hi: Vector3) -> Array:
	return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]


func _tex(id: String, mode: int = 1) -> Dictionary:
	return {"tex": id, "mode": mode}


func _face_tex(level: Node, bkey: String, fkey: String) -> Dictionary:
	var id: int = level.brush_by_key(bkey)
	var b: Dictionary = level.get_brush(id)
	var ft: Array = b.get("face_tex", [])
	var f: int = level.face_by_key(id, fkey)
	return ft[f] if f >= 0 and f < ft.size() else {}


func _ready() -> void:
	await get_tree().process_frame
	_run()
	if checks < 15:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE journal_texture: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _run() -> void:
	if not ClassDB.class_exists("DCLevel"):
		_ok("DCLevel registered", false)
		return
	var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
	gs.route("world:level:*", 1, "peer")          # 1 == Channel.DURABLE
	var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"

	var level: Node3D = ClassDB.instantiate("DCLevel")
	add_child(level)
	var persist := LevelPersistence.new()
	persist.level = level
	add_child(persist)

	print("-- brush.create: face_tex, clearance and group survive the fold --")
	var ft: Array = []
	for i in 6:
		ft.append(_tex("p/a%d" % i))
	var bdot: String = persist.record("brush.create", {
		"planes": _box(Vector3(0, 0, 0), Vector3(2, 2, 2)), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "clearance": true, "group": "g1", "face_tex": ft})
	_ok("brush.create recorded", bdot != "")
	var st: Dictionary = persist.refold()
	_ok("applied, nothing skipped", int(st.get("applied", 0)) == 1 and int(st.get("skipped", 0)) == 0, str(st))
	var b: Dictionary = level.get_brush(level.brush_by_key(bdot))
	_ok("clearance survives the fold (was dropped by _to_record)", bool(b.get("clearance", false)))
	_ok("group survives the fold (was dropped by _to_record)", String(b.get("group", "")) == "g1", String(b.get("group", "")))
	var fts: Array = b.get("face_tex", [])
	_ok("face_tex survives the fold", fts.size() == 6 and String((fts[0] as Dictionary).get("tex", "")) == "p/a0",
		str(fts.size()))

	print("\n-- face.add carries its texture --")
	var adot: String = persist.record("face.add", {
		"brush_key": bdot, "n": [0.7071068, 0.7071068, 0.0], "d": 2.5, "tex": _tex("p/bevel", 3)})
	persist.refold()
	var nt: Dictionary = _face_tex(level, bdot, adot)
	_ok("the new face has its texture and mode", String(nt.get("tex", "")) == "p/bevel" and int(nt.get("mode", 0)) == 3, str(nt))

	print("\n-- face.tex with a field mask --")
	var c1: String = persist.record("face.tex", {
		"brush_key": bdot, "face_keys": ["i:0", "i:2"], "tex": _tex("p/brick"), "mask": 1})
	var c2: String = persist.record("face.tex", {
		"brush_key": bdot, "face_keys": ["i:0"], "tex": {"off_u": 0.25, "rot": 15.0}, "mask": 4 | 8})
	persist.refold()
	var t0: Dictionary = _face_tex(level, bdot, "i:0")
	_ok("texture change landed on both faces",
		String(t0.get("tex", "")) == "p/brick" and String(_face_tex(level, bdot, "i:2").get("tex", "")) == "p/brick")
	_ok("the later nudge did not clobber the texture", String(t0.get("tex", "")) == "p/brick", String(t0.get("tex", "")))
	_ok("the nudge landed (offset, rotation)", is_equal_approx(float(t0.get("off_u", 0)), 0.25) and is_equal_approx(float(t0.get("rot", 0)), 15.0), str(t0))
	_ok("an unnamed face is untouched", String(_face_tex(level, bdot, "i:1").get("tex", "")) == "p/a1")
	_ok("undo the texture change", persist.undo_op(c1))
	persist.refold()
	_ok("after undo the face is back to its create-time texture", String(_face_tex(level, bdot, "i:0").get("tex", "")) == "p/a0")
	_ok("and the nudge (still live) is kept", is_equal_approx(float(_face_tex(level, bdot, "i:0").get("off_u", 0)), 0.25))

	print("\n-- the derived projection table --")
	_ok("redo the texture change (p/brick back on two faces)", persist.redo_op(c1))
	persist.refold()
	var imp := PackedFloat32Array()
	imp.resize(2 * 16)                                # two import rows (START.MAP's table)
	for k in imp.size():
		imp[k] = 100.0 + k
	level.set_import_projection_table(imp)
	var info := {}
	for i in 6:
		info["p/a%d" % i] = {"layer": 7, "tile_u": 2.0, "tile_v": 2.0}
	info["p/bevel"] = {"layer": 9, "tile_u": 1.0, "tile_v": 1.0}
	level.set_texture_info(info, 31)                  # 31 = the "missing" layer
	level.rebuild()
	var tab: PackedFloat32Array = level.get_projection_table()
	var rows: int = tab.size() / 16
	_ok("the import rows come first, unchanged", rows > 2 and tab[0] == 100.0 and tab[31] == 131.0, "%d rows" % rows)
	var layers := {}
	var u_len := -1.0
	for r in range(2, rows):
		var lay := int(tab[r * 16])
		layers[lay] = true
		if lay == 7:
			u_len = Vector3(tab[r * 16 + 4], tab[r * 16 + 5], tab[r * 16 + 6]).length()
	_ok("derived rows carry the pack layer", layers.has(7) and layers.has(9), str(layers.keys()))
	_ok("p/brick (no info) falls back to the missing layer", layers.has(31))
	_ok("tile size is honoured (2 m tile -> |U| = 0.5)", is_equal_approx(u_len, 0.5), str(u_len))
	# DEDUP: a second brush elsewhere with IDENTICAL intent adds no rows -- offsets are
	# in tiles and axes depend only on the normal, so its faces map to the same rows.
	# (The FIRST brush no longer matches `ft` -- two faces were retextured -- so the
	# comparison is between two brushes that both carry `ft` exactly.)
	persist.record("brush.create", {
		"planes": _box(Vector3(10, 0, 0), Vector3(12, 2, 2)), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	persist.refold()
	level.rebuild()
	var rows2: int = (level.get_projection_table() as PackedFloat32Array).size() / 16
	persist.record("brush.create", {
		"planes": _box(Vector3(20, 0, 0), Vector3(22, 2, 2)), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	persist.refold()
	level.rebuild()
	var rows3: int = (level.get_projection_table() as PackedFloat32Array).size() / 16
	_ok("dedup: a third brush identical to the second adds no rows", rows3 == rows2, "%d -> %d" % [rows2, rows3])
	_ok("the version bumped when the table changed, not otherwise", level.get_projection_table_version() >= 1)

	print("\n-- texture lock rides brush.translate / brush.scale through _to_record (gate 4) --")
	# #41 §4's trap again: a tex_lock that _to_record drops refolds as an UNLOCKED
	# move — the texture slides, silently, on every peer and on every refold.
	var lk: String = persist.record("brush.create", {
		"planes": _box(Vector3(40, 0, 0), Vector3(42, 2, 2)), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	persist.refold()
	var off0 := float(_face_tex(level, lk, "i:2").get("off_u", 0.0))
	persist.record("brush.translate", {"brush_key": lk, "delta": [0.5, 0, 0.25], "tex_lock": true})
	persist.refold()
	var t2: Dictionary = _face_tex(level, lk, "i:2")
	_ok("a locked translate moved the texture offset", absf(float(t2.get("off_u", 0.0)) - off0) > 1e-4,
		"%.4f -> %.4f" % [off0, float(t2.get("off_u", 0.0))])
	persist.record("brush.scale", {"brush_key": lk, "scale": [2, 1, 1], "pivot": [40, 0, 0], "tex_lock": true})
	persist.refold()
	_ok("a locked non-uniform scale froze the faces to explicit", int(_face_tex(level, lk, "i:2").get("mode", 0)) == 2,
		str(_face_tex(level, lk, "i:2").get("mode")))
	var un: String = persist.record("brush.create", {
		"planes": _box(Vector3(50, 0, 0), Vector3(52, 2, 2)), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	persist.record("brush.translate", {"brush_key": un, "delta": [0.5, 0, 0.25]})
	persist.refold()
	_ok("without tex_lock the texture stays in world space", is_equal_approx(float(_face_tex(level, un, "i:2").get("off_u", 0.0)), 0.0))