OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_refold_skip.gd

99 lines · gd ref:
extends Node
## GATE — an edit that changes no BRUSH does not replay and re-mesh every brush
## (2026-10-05: "delete is extremely slow" — 11 s per delete). refold() always ran
## DCLevel.apply_journal + level.rebuild(), the rebuild alone 705 ms at 2000 brushes,
## even for deleting a teleporter. Now the brush half is SKIPPED when the brush records
## and the store are both unchanged since the last fold.
##
##   1. an object edit (create / delete / undo) -> brush_fold "skipped"
##   2. a brush edit -> "applied"
##   3. the store changed BEHIND the journal's back (a direct remove_brush) -> "applied",
##      and the fold puts the journal's brush back (the skip never hides drift)
##   4. with 1000 brushes, an object delete costs < 1/5 of a brush delete
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_refold_skip.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(x: float, z: float) -> Array:
	return [1, 0, 0, x + 2, -1, 0, 0, -x, 0, 1, 0, 2, 0, -1, 0, 0, 0, 0, 1, z + 2, 0, 0, -1, -z]


func _ready() -> void:
	var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
	gs.route("world:level:*", 1, "peer")
	var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	_run(map)
	if checks < 7:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE refold_skip: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _run(map: Node3D) -> void:
	var level: Node = map.get("level")
	var persist: Node = map.get("_persist")
	for i in 1000:
		persist.record("brush.create", {"planes": _box(60.0 + float(i % 40) * 3.0, float(i / 40) * 3.0),
			"proj": [], "material_id": 0, "level_id": 0, "sky": false})
	persist.refold()
	_ok("setup: the brush fold ran", String(persist.last_fold().get("brush_fold", "")) == "applied",
		str(persist.last_fold().get("brush_fold", "<none>")))

	# 1. object-only edits
	var dot: String = map.create_object("marker", Transform3D(Basis.IDENTITY, Vector3(0, 1, 0)))
	persist.refold()
	_ok("1. creating an object skips the brush fold", String(persist.last_fold().get("brush_fold", "")) == "skipped")
	map.delete_objects([dot])
	persist.refold()
	_ok("1. deleting it skips the brush fold", String(persist.last_fold().get("brush_fold", "")) == "skipped")

	# 2. a brush edit
	var bdot: String = persist.record("brush.create", {"planes": _box(-80.0, -80.0), "proj": [],
		"material_id": 0, "level_id": 0, "sky": false})
	persist.refold()
	# "tail" since the tail fold (only new records: applied on top, no rewind) — still a fold.
	_ok("2. a brush edit applies the brush fold", String(persist.last_fold().get("brush_fold", "")) in ["applied", "tail"],
		persist.last_fold().get("brush_fold", ""))

	# 3. drift behind the journal's back
	var id: int = level.brush_by_key(bdot)
	level.remove_brush(id)
	persist.refold()
	var b: Dictionary = level.get_brush(level.brush_by_key(bdot))
	_ok("3. a store changed behind the journal -> the brush fold runs", String(persist.last_fold().get("brush_fold", "")) == "applied")
	_ok("3. and puts the journal's brush back", not b.is_empty() and not bool(b.get("tombstoned", true)))

	# 4. cost
	var t0 := Time.get_ticks_usec()
	var d2: String = map.create_object("marker", Transform3D(Basis.IDENTITY, Vector3(0, 1, 0)))
	map.delete_objects([d2])
	var obj_us := float(Time.get_ticks_usec() - t0)
	# Against a FULL brush fold (the rewind + re-mesh this check is about), not the tail
	# fold a plain delete now takes.
	persist.set("_ix_tail_ok", false)
	t0 = Time.get_ticks_usec()
	map.delete_brushes([level.brush_by_key(bdot)])
	var br_us := float(Time.get_ticks_usec() - t0)
	# Before the skip an object create + delete (two folds) cost ~2x ONE brush delete
	# (430 vs 221 ms here): each fold re-meshed every brush. Measured after: ~92 vs ~240.
	# What is left per fold is linear (records() scan, outliner refresh), not the re-mesh.
	_ok("4. an object create+delete (2 folds) costs less than ONE brush delete", obj_us < br_us,
		"%.1f ms vs %.1f ms" % [obj_us / 1000.0, br_us / 1000.0])