OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / saved_position_gate.gd

84 lines · gd ref:
extends Node3D
## Does a restore lift ONLY the saved positions the terrain actually stranded?
##
## The band shift on map_dc_erosion moved the surface ~9.9 km down, so every
## position saved before it is now kilometres of empty air above the ground. The
## old code only handled the opposite direction (BURIED), so a returning player
## free-fell. The fix has to put those players back on the ground WITHOUT
## snapping down a player who was legitimately in the air -- grappling, skiing off
## a lip, mid-jump -- because at restore time the two look identical: a point with
## nothing under it.
##
## The discriminator is the `grounded` flag written alongside the position. So the
## control is built into the table: rows 2, 3 and 4 are THE SAME POSITION and
## differ only in that flag. If they all come out the same, the flag is not being
## read and this gate is measuring nothing.
##
## Run:
##   "$HYPER_TPS_GODOT" --headless res://scenes/test/saved_position_gate.tscn

## Solid below y = 0. sample_density is POSITIVE below the surface and NEGATIVE in
## air -- the convention measured in player_position.gd's own header comment.
class FakeTerrain extends Node:
	func sample_density(p: Vector3) -> float:
		return -p.y
	func get_chunk_count() -> int:
		return 1


var _fails := 0


func _ready() -> void:
	var terrain := FakeTerrain.new()
	terrain.name = "FakeTerrain"
	add_child(terrain)

	var pp: Node = get_node_or_null("/root/PlayerPosition")
	if pp == null:
		print("RIG FAILED: PlayerPosition autoload not found")
		get_tree().quit(1)
		return
	if not pp.has_method("_reconcile_with_terrain"):
		print("RIG FAILED: PlayerPosition has no _reconcile_with_terrain")
		get_tree().quit(1)
		return

	# name, saved pos, grounded flag, expected y, tolerance
	var cases := [
		["buried 100 m under",      Vector3(0.0, -100.0, 0.0), true,  2.0,     0.5],
		["stranded 9.9 km up",      Vector3(0.0, 9900.0, 0.0), true,  0.5,     0.5],
		["airborne on purpose",     Vector3(0.0, 9900.0, 0.0), false, 9900.0,  0.001],
		["legacy record, no flag",  Vector3(0.0, 9900.0, 0.0), null,  9900.0,  0.001],
		["grounded, 2 m -- settling", Vector3(0.0, 2.0, 0.0),  true,  2.0,     0.001],
	]

	print("")
	print("case                        saved y    grounded   result y    expected   verdict")
	print("--------------------------------------------------------------------------------")
	for c in cases:
		var got: Vector3 = pp._reconcile_with_terrain(c[1], c[2])
		var ok: bool = absf(got.y - float(c[3])) <= float(c[4])
		if not ok:
			_fails += 1
		print("%-26s %9.1f   %-8s %9.2f   %9.2f   %s"
				% [c[0], c[1].y, str(c[2]), got.y, float(c[3]), "ok" if ok else "FAIL"])

	print("")
	# THE CONTROL. Rows 2/3/4 are the same point; only the flag differs. If the
	# stranded row does not move while the airborne row does not either, the flag
	# is being ignored and every "ok" above is an accident.
	var stranded: Vector3 = pp._reconcile_with_terrain(Vector3(0.0, 9900.0, 0.0), true)
	var airborne: Vector3 = pp._reconcile_with_terrain(Vector3(0.0, 9900.0, 0.0), false)
	if is_equal_approx(stranded.y, airborne.y):
		print("CONTROL FAILED: the grounded flag changed nothing — same position in,")
		print("same position out. This gate is not measuring the discriminator.")
		_fails += 1
	else:
		print("control ok: same position, grounded=true -> %.2f, grounded=false -> %.2f"
				% [stranded.y, airborne.y])

	print("")
	print("RESULT: %s (%d failing)" % ["PASS" if _fails == 0 else "FAIL", _fails])
	get_tree().quit(0 if _fails == 0 else 1)