feat(client): 3D locomotion sandbox — character walks the live Gauntlet (T-1088)

New SR_LIVE sandbox scene: CharacterVisual composited in a 3D greybox world
derived from server snapshots. Per-leg constant-velocity interpolation keyed
to the stance throttle, 'server feet / client eyes' facing (wire octant while
moving, client aim octant idle), cadence-synced gait state machine on
AnimationPlayer custom blends, D-148 orthographic follow camera (-30deg
default, T-cycle presets), sim-space grid shader, camera-side wall cutaway,
accumulating never-evict tile store with four-state visibility tint.

Additive seams only: InputMapper.facing_angle_provider (2D path unchanged),
CharacterVisual.play_animation blend_time param + get_animation_player().
Visual harness gains per-scenario scene field + SR_AUTOPILOT input scripting.
210 new gdUnit assertions across five suites; verified live (230/230 total,
clean smoke, screenshot at .cache/screenshots/locomotion_idle_live.png).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 13:23:16 +02:00
co-authored by Claude Fable 5
parent ce1811b5f9
commit a7801a942a
21 changed files with 3555 additions and 8 deletions
+18 -3
View File
@@ -24,6 +24,7 @@ extends Node3D
## get_accessory_node_count() — number of accessory BoneAttachment3D nodes
## get_skin_tone_texture_name(index) — skin tone texture filename key for index
## get_overhead_anchor() — Marker3D above Head bone for floating UI (#712)
## get_animation_player() — AnimationPlayer for external anim drivers (T-1088)
##
## D-159 (11 body types), D-160 (18 segments), D-161 (head separate),
## D-162 (clothing pre-fitted), D-163 (heads via BoneAttachment3D), D-164 (skeleton fork)
@@ -771,7 +772,10 @@ func _load_animations() -> void:
## Play a named animation. Searches all libraries for a matching name.
func play_animation(anim_name: String) -> void:
## blend_time >= 0.0 is passed to AnimationPlayer.play() as custom_blend (crossfade
## seconds); the -1.0 default preserves the original hard-cut behavior for existing
## callers (T-1088 design §6.3 — the locomotion gait machine is the first blend user).
func play_animation(anim_name: String, blend_time: float = -1.0) -> void:
if _anim_player == null:
return
# Search across all libraries for the animation
@@ -779,12 +783,15 @@ func play_animation(anim_name: String) -> void:
var lib: AnimationLibrary = _anim_player.get_animation_library(lib_name)
if lib.has_animation(anim_name):
var full_name: String = lib_name + "/" + anim_name if lib_name != "" else anim_name
_anim_player.play(full_name)
if blend_time >= 0.0:
_anim_player.play(full_name, blend_time)
else:
_anim_player.play(full_name)
return
# Try common idle variants
for variant in ["Idle", "idle_01", "Idle_01", "breathing_idle", "Breathing_Idle"]:
if anim_name == "idle" and variant != anim_name:
play_animation(variant)
play_animation(variant, blend_time)
return
push_warning("CharacterVisual: animation '%s' not found in any library" % anim_name)
@@ -795,6 +802,14 @@ func stop_animation() -> void:
_anim_player.stop()
## Return the internal AnimationPlayer ("AnimPlayer") — speed_scale and clip-phase
## access for external animation drivers (T-1088 gait machine; Q-063 footstep seam).
## Null until load_descriptor() has run; every load_descriptor() destroys and
## recreates the player, so callers must re-fetch — never cache across reloads.
func get_animation_player() -> AnimationPlayer:
return _anim_player
# =============================================================================
# Static helpers
# =============================================================================