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)