Files
settled-reach/client/tests/unit/test_path_follower.gd
T
jpmschweitzerandClaude Fable 5 49c8994943 feat(client): move-here path preview + RMB gesture vocabulary (T-1088, Q-084)
Hover marker + optimal path line over the KNOWN tile store only — the
character plans through what they know; fog is unpathable (info boundary at
the planning layer; the follower additionally revalidates every remaining
tile per step). Pure static 8-dir A*: uniform cost 1 incl. diagonals (D-248
time-optimal, no sqrt2), no corner-cutting, terrain-cost provider seam for
Phase-4 terrain. Execution streams ordinary Move* steps through the existing
throttle — zero protocol change, server validates every step.

RMB vocabulary (live-session spec): click = walk there at current stance;
double-click = sprint there (ToggleStanceUp burst — server toggle handler
verified cooldown-free so bursts climb deterministically — with net-zero
restore on arrival; a double upgrades the active follow in place);
long-press >=400ms = go there then Crouch on arrival, no restore (input-
vocabulary prototype; real cover mechanics are future combat design).
Cancellation: WASD override (stance kept), invalidation, teleport,
suppression. Two additive InputMapper seams (queue_move_step,
queue_stance_toggle); mouse unproject shared with the facing provider
(ground_hit_local). 44 new gdUnit tests across finder + follower ladders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:52:03 +02:00

66 lines
3.2 KiB
GDScript

## PathFollower stance-ladder math (T-1088 double-RMB sprint-there): the number of
## ToggleStanceUp actions to reach Sprint from each stance, and the net-zero restore
## property. Pure static — the ladder helpers touch no autoloads, so they run
## headless (the instance follow/step behaviour is live-only, verified in the smoke).
class_name TestPathFollower
extends GdUnitTestSuite
func test_toggles_to_sprint_from_each_stance() -> void:
# Ladder: Crouch < Careful < Walk < Sprint (server MovementStance step_up).
assert_int(PathFollower.toggles_to_sprint("Sprint")).is_equal(0)
assert_int(PathFollower.toggles_to_sprint("Walk")).is_equal(1)
assert_int(PathFollower.toggles_to_sprint("Careful")).is_equal(2)
assert_int(PathFollower.toggles_to_sprint("Crouch")).is_equal(3)
func test_stance_rung_matches_ladder() -> void:
assert_int(PathFollower.stance_rung("Sprint")).is_equal(0)
assert_int(PathFollower.stance_rung("Walk")).is_equal(1)
assert_int(PathFollower.stance_rung("Careful")).is_equal(2)
assert_int(PathFollower.stance_rung("Crouch")).is_equal(3)
func test_unknown_stance_defaults_to_walk() -> void:
# Defensive: an unrecognized / empty stance is treated as Walk (GameState default).
assert_int(PathFollower.stance_rung("Bogus")).is_equal(1)
assert_int(PathFollower.toggles_to_sprint("")).is_equal(1)
func test_restore_count_equals_raise_count_net_zero() -> void:
# Restore bursts the SAME number of ToggleStanceDown as the raise bursted
# ToggleStanceUp. Since Sprint is rung 0, raising from a stance to Sprint climbs
# `rung` steps and restoring drops `rung` steps → back to the exact start rung,
# regardless of snapshot RTT. The single source of truth is toggles_to_sprint.
for stance: String in ["Sprint", "Walk", "Careful", "Crouch"]:
var raise_toggles := PathFollower.toggles_to_sprint(stance)
var restore_toggles := PathFollower.toggles_to_sprint(stance)
assert_int(restore_toggles).is_equal(raise_toggles)
# Net-zero: start rung, up to Sprint (0), then down `raise_toggles` == start.
assert_int(0 + restore_toggles).is_equal(PathFollower.stance_rung(stance))
# -- long-press crouch-on-arrival ------------------------------------------------------
func test_toggles_to_crouch_from_each_stance() -> void:
# Long-press lowers to Crouch on arrival: ToggleStanceDown from the arrival stance
# down to Crouch (rung 3). No restore.
assert_int(PathFollower.toggles_to_crouch("Sprint")).is_equal(3)
assert_int(PathFollower.toggles_to_crouch("Walk")).is_equal(2)
assert_int(PathFollower.toggles_to_crouch("Careful")).is_equal(1)
assert_int(PathFollower.toggles_to_crouch("Crouch")).is_equal(0)
func test_toggles_to_crouch_unknown_defaults_to_walk() -> void:
# Unknown/empty stance is treated as Walk (rung 1) → 2 down-toggles to Crouch.
assert_int(PathFollower.toggles_to_crouch("Bogus")).is_equal(2)
assert_int(PathFollower.toggles_to_crouch("")).is_equal(2)
func test_crouch_lands_on_bottom_rung() -> void:
# After the crouch burst the character sits exactly on Crouch's rung from any start.
for stance: String in ["Sprint", "Walk", "Careful", "Crouch"]:
var down := PathFollower.toggles_to_crouch(stance)
assert_int(PathFollower.stance_rung(stance) + down).is_equal(PathFollower.stance_rung("Crouch"))