extends Node3D
## Wall run one way, drop off, then run back the OTHER way. Does the facing follow?
##
## Reported from play: "I can wall run correctly one way but if I try the other
## way it points the first way." Every other wall-run gate runs a single direction
## in a single run, so none of them could see it — the fault is CARRY-OVER between
## runs, and it needs two runs on one player to exist at all.
##
## The facing is anchored to the wall tangent with a latched sign and a smoothed
## tangent (player_procedural.gd, update_model_orientation). Both are per-run
## state. If they survive into the next run, the second run starts pointing the
## way the first one went — and because a slerp between near-opposite vectors has
## no well-defined axis, it crawls instead of turning around.
##
## Run:
## "$HYPER_TPS_GODOT" --headless res://scenes/test/wall_run_reverse_gate.tscn
const WALL_X := 0.6
const PLAYER_X := WALL_X - 0.3
const SPAWN_Y := 60.0
const SETTLE := 25 ## let the lean arrive
const LEG := 55 ## frames of each run
const GAP := 30 ## sprint released between runs
var _player: CharacterBody3D = null
var _f := 0
var _leg := 0 ## 0 = first run (-Z), 1 = gap, 2 = reverse run (+Z)
var _active := 0
var _done := false
func _ready() -> void:
process_priority = 100
var wall := StaticBody3D.new()
var cs := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = Vector3(0.4, 400.0, 800.0)
cs.shape = box
wall.add_child(cs)
wall.position = Vector3(WALL_X + 0.2, 0.0, 0.0)
add_child(wall)
var scene: PackedScene = load("res://scenes/player/player_procedural.tscn")
_player = scene.instantiate()
add_child(_player)
_player.global_position = Vector3(PLAYER_X, SPAWN_Y, 0.0)
_player.camera_head.global_transform = Transform3D(Basis.IDENTITY, Vector3.ZERO)
_player.velocity = Vector3(3.0, 0.0, -8.0)
func _physics_process(_d: float) -> void:
if _done or _player == null:
return
_f += 1
# LEG 0: run toward -Z. GAP: sprint released, wall run ends. LEG 2: run +Z.
if _f <= SETTLE + LEG:
_leg = 0
Input.action_press("sprint")
_player.input_sync.input_direction = Vector2(0.0, -1.0)
elif _f <= SETTLE + LEG + GAP:
_leg = 1
Input.action_release("sprint")
_player.input_sync.input_direction = Vector2.ZERO
_player.velocity = Vector3(3.0, 0.0, 8.0) # turn the travel around
else:
_leg = 2
Input.action_press("sprint")
_player.input_sync.input_direction = Vector2(0.0, 1.0)
if _leg == 1:
return
if _f < SETTLE + 20:
return # first run still settling
if _leg == 2 and _f < SETTLE + LEG + GAP + 20:
return # second run still settling
if not _player.wall_run_active:
return
_active += 1
# Expected facing: the wall tangent, signed by which way we are travelling.
var want: Vector3 = Vector3(0.0, 0.0, -1.0) if _leg == 0 else Vector3(0.0, 0.0, 1.0)
var fwd: Vector3 = -(_player.get_node("Model") as Node3D).global_transform.basis.z
fwd.y = 0.0
if fwd.length_squared() < 0.01:
return
var dev: float = rad_to_deg(fwd.normalized().angle_to(want))
if _leg == 0:
_leg0_worst = maxf(_leg0_worst, dev)
else:
_leg2_worst = maxf(_leg2_worst, dev)
var _leg0_worst := 0.0
var _leg2_worst := 0.0
var _leg0_seen := 0
var _leg2_seen := 0
func _process(_d: float) -> void:
if _done or _player == null or _f < SETTLE + LEG + GAP + LEG:
return
_done = true
print("")
print("direction worst facing error vs the wall tangent")
print("---------------------------------------------------------")
print(" first (-Z) %6.1f deg" % _leg0_worst)
print(" reverse (+Z) %6.1f deg" % _leg2_worst)
print("")
if _active < 10:
print("RIG FAILED: wall_run_active was true on only %d sampled frames, so this" % _active)
print("says nothing about facing.")
get_tree().quit(1)
return
# The CONTROL is the pair. A first run that works and a reverse that does not is
# exactly the reported fault; both failing would be a different bug.
if _leg2_worst > 25.0 and _leg0_worst <= 25.0:
print("FAIL the first direction is fine (%.1f deg) and the reverse is not" % _leg0_worst)
print(" (%.1f deg) — per-run facing state is carrying over." % _leg2_worst)
get_tree().quit(1)
elif _leg0_worst > 25.0 or _leg2_worst > 25.0:
print("FAIL facing is wrong in both directions (%.1f / %.1f deg) — this is not"
% [_leg0_worst, _leg2_worst])
print(" the carry-over bug.")
get_tree().quit(1)
else:
print("PASS both directions face along the wall")
get_tree().quit(0)