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:
@@ -1,5 +1,6 @@
|
||||
## Visual test capture engine — boots main.tscn, runs a named scenario or flow,
|
||||
## captures viewport PNGs for golden comparison or ad-hoc inspection.
|
||||
## Visual test capture engine — boots the entry's "scene" (default main.tscn),
|
||||
## runs a named scenario or flow, captures viewport PNGs for golden comparison
|
||||
## or ad-hoc inspection.
|
||||
##
|
||||
## Usage:
|
||||
## godot --rendering-driver opengl3 --fixed-fps 60 --resolution 960x540 \
|
||||
@@ -66,10 +67,22 @@ func _run(): # gdlint:disable=max-returns
|
||||
# Ensure output directory exists
|
||||
DirAccess.make_dir_recursive_absolute(_output_dir)
|
||||
|
||||
# Load main scene (autoloads already initialized from project.godot)
|
||||
var main_scene = load("res://scenes/main.tscn")
|
||||
# Per-entry overrides (T-1088, design §10.2 — both additive; existing entries
|
||||
# without these keys behave byte-identically):
|
||||
# "scene": alternate root scene (default res://scenes/main.tscn)
|
||||
# "env": env vars set before the scene boots (e.g. SR_AUTOPILOT) — OS-level,
|
||||
# so the scene's _ready() reads them exactly like shell-exported vars;
|
||||
# run-visual has no per-scenario env mechanism, this is it.
|
||||
var entry_cfg := _entry_config()
|
||||
var env_vars: Dictionary = entry_cfg.get("env", {})
|
||||
for env_key: String in env_vars:
|
||||
OS.set_environment(env_key, str(env_vars[env_key]))
|
||||
|
||||
# Load root scene (autoloads already initialized from project.godot)
|
||||
var scene_path: String = entry_cfg.get("scene", "res://scenes/main.tscn")
|
||||
var main_scene = load(scene_path)
|
||||
if main_scene == null:
|
||||
push_error("visual_capture: failed to load res://scenes/main.tscn")
|
||||
push_error("visual_capture: failed to load %s" % scene_path)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
@@ -298,6 +311,19 @@ func _capture(path: String) -> void:
|
||||
print("visual_capture: captured -> %s (%dx%d)" % [path, image.get_width(), image.get_height()])
|
||||
|
||||
|
||||
## Config entry for the active scenario or flow ({} when the name is unknown —
|
||||
## the mode runners report that error themselves).
|
||||
func _entry_config() -> Dictionary:
|
||||
var section: Dictionary = {}
|
||||
if not _scenario.is_empty():
|
||||
section = _config.get("scenarios", {})
|
||||
return section.get(_scenario, {})
|
||||
if not _flow.is_empty():
|
||||
section = _config.get("flows", {})
|
||||
return section.get(_flow, {})
|
||||
return {}
|
||||
|
||||
|
||||
func _load_config() -> Dictionary:
|
||||
# Config is at tests/visual.json relative to repo root.
|
||||
# Repo root = parent of Godot project root (client/).
|
||||
|
||||
Reference in New Issue
Block a user