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>
This commit is contained in:
@@ -175,6 +175,50 @@ func flush_queue() -> Array[Dictionary]:
|
||||
return queue
|
||||
|
||||
|
||||
# T-1088 click-to-move seam (design §9 / D-248): queue ONE 8-direction step from a
|
||||
# sim tile delta (e.g. Vector2i(1, 0) = East, Vector2i(1, 1) = Southeast), gated by
|
||||
# the SAME per-stance throttle held-WASD uses (_last_move_msec + MOVE_INTERVAL_MS).
|
||||
# The sandbox path follower calls this once per frame while walking a client-planned
|
||||
# path; the shared throttle paces it to the exact stance cadence (no duplicated
|
||||
# interval table) and interleaves cleanly with WASD — a held key and a queued step
|
||||
# can never both fire inside one window. Returns true when the step was queued,
|
||||
# false when throttled or input-suppressed. Ordinary Move* actions only: zero
|
||||
# protocol change, no client prediction, the server validates every step (D-010).
|
||||
func queue_move_step(tile_delta: Vector2i) -> bool:
|
||||
if GameState.dialogue_active or GameState.free_camera_mode:
|
||||
return false
|
||||
if tile_delta == Vector2i.ZERO:
|
||||
return false
|
||||
var now := Time.get_ticks_msec()
|
||||
var interval: int = MOVE_INTERVAL_MS.get(GameState.player_stance, 200)
|
||||
if now - _last_move_msec < interval:
|
||||
return false
|
||||
_last_move_msec = now
|
||||
input_queue.append(
|
||||
{
|
||||
"action": _dir_to_action(tile_delta),
|
||||
"timestamp_msec": now,
|
||||
}
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
# T-1088 double-RMB sprint-there seam: queue ONE ordinary stance toggle (up = toward
|
||||
# Sprint, down = toward Crouch) through the same input_queue movement rides. Discrete,
|
||||
# so no throttle — the server's handle_toggle_stance has no cooldown, so the sandbox
|
||||
# follower can burst the exact ladder distance and trust it (server validates). No-op
|
||||
# while input is suppressed (dialogue / free camera), matching movement.
|
||||
func queue_stance_toggle(up: bool) -> void:
|
||||
if GameState.dialogue_active or GameState.free_camera_mode:
|
||||
return
|
||||
input_queue.append(
|
||||
{
|
||||
"action": Action.TOGGLE_STANCE_UP if up else Action.TOGGLE_STANCE_DOWN,
|
||||
"timestamp_msec": Time.get_ticks_msec(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
## Reset facing state to default (North). Use in tests per D-030 testability.
|
||||
func reset_facing_state() -> void:
|
||||
facing_angle = -PI / 2.0
|
||||
|
||||
Reference in New Issue
Block a user