OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / wall_run_reverse_gate.gd

127 lines · gd ref:
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)