# test_ping_states.py - Test VRM avatar state transitions and microexpressions # Updated with tests for new realistic head movement features import time import sys import requests BASE_URL = "http://localhost:8001" # ============================================================================= # HELPER FUNCTIONS # ============================================================================= def set_vrm_state(state): """ Set the VRM avatar's animation state. Valid states: - idle: Avatar looks around naturally, with chance to look at user - listening: Avatar focuses on user with occasional side glances - thinking: Avatar looks away with eyes leading head movement - talking: Avatar nods with variable intensity and head tilts """ url = f"{BASE_URL}/set_state" payload = {"state": state} resp = requests.post(url, json=payload) print(f"[set_state] Status: {resp.status_code}, State: {state}") return resp def set_movement_lock_duration(duration): """ Set the movement lock duration after state transitions. During lock, head stays centered before new state animations begin. Args: duration: Lock duration in seconds (default 1.0) """ url = f"{BASE_URL}/set_movement_lock_duration" payload = {"duration": duration} resp = requests.post(url, json=payload) print(f"[set_lock_duration] Status: {resp.status_code}, Duration: {duration}s") return resp def vrm_animate(animation_type, animate_url, play_once=False, lock_position=False, track_position=True): """Play a VRMA or Mixamo animation.""" url = f"{BASE_URL}/animate" payload = { "animate_type": animation_type, "animation_url": animate_url, "play_once": play_once, "crop_start": 0.0, "crop_end": 0.0, "lock_position": lock_position, "track_position": track_position, } resp = requests.post(url, json=payload) print(f"[animate] Status: {resp.status_code}") return resp def vrm_walk_to(x, y, z, speed=1.5): """Walk the VRM character to a position.""" url = f"{BASE_URL}/walk_to" payload = {"x": x, "y": y, "z": z, "speed": speed} resp = requests.post(url, json=payload) print(f"[walk_to] Status: {resp.status_code}, Target: ({x}, {y}, {z})") return resp def vrm_stop_movement(): """Stop VRM movement and return to idle.""" url = f"{BASE_URL}/stop_movement" resp = requests.post(url) print(f"[stop_movement] Status: {resp.status_code}") return resp def print_header(title, description=""): """Print a formatted test header.""" print("\n" + "=" * 70) print(f"TEST: {title}") print("=" * 70) if description: print(description) print() def print_step(step_num, total, description): """Print a formatted step indicator.""" print(f"\n[{step_num}/{total}] {description}") print("-" * 50) # ============================================================================= # INDIVIDUAL STATE TESTS # ============================================================================= def test_idle_state(): """Test: Idle state with look-at-user feature""" print_header( "IDLE STATE - Natural Looking Around with User Focus", "Features being tested:\n" " - Natural head movements with smooth acceleration/deceleration\n" " - Periodic chance (35%) to look back at user/camera\n" " - Adjustable user-look duration (1.5-3.5 seconds)\n" " - Eyes and head reset to center when looking at user" ) set_vrm_state("idle") print("\nWatch for:") print(" - Head should look around smoothly") print(" - Periodically returns to center (looking at you)") print(" - Movement should have natural acceleration/deceleration") time.sleep(12) print("\n[OK] Idle state test complete") def test_listening_state(): """Test: Listening state with side glances""" print_header( "LISTENING STATE - Focused Attention with Side Glances", "Features being tested:\n" " - Primary focus on user (eyes centered)\n" " - Occasional side glances (1-3 seconds)\n" " - Subtle nodding while listening\n" " - Micro eye movements while focused" ) set_vrm_state("listening") print("\nWatch for:") print(" - Avatar mainly focused on you (centered)") print(" - Occasional brief glances to left or right") print(" - Gentle nodding showing engagement") time.sleep(12) print("\n[OK] Listening state test complete") def test_thinking_state(): """Test: Thinking state with eye-leads-head behavior""" print_header( "THINKING STATE - Contemplative with Eyes Leading Head", "Features being tested:\n" " - Eyes move BEFORE head (0.25s lead time)\n" " - Eyes and head move in same direction (85% sync)\n" " - Look-up bias for contemplative appearance\n" " - Distant gaze while thinking" ) set_vrm_state("thinking") print("\nWatch for:") print(" - Eyes move first, then head follows") print(" - Tendency to look upward (thinking gesture)") print(" - Smooth, contemplative movements") time.sleep(12) print("\n[OK] Thinking state test complete") def test_talking_state(): """Test: Talking state with variable nodding""" print_header( "TALKING STATE - Variable Nodding with Head Tilts", "Features being tested:\n" " - Variable nod frequency (changes every 1.5s)\n" " - Variable nod intensity (+/- 40%)\n" " - Occasional head tilts for emphasis\n" " - Brief micro-pauses in nodding" ) set_vrm_state("talking") print("\nWatch for:") print(" - Nodding speed/intensity changes over time") print(" - Occasional head tilts (left/right)") print(" - Brief pauses between nod sequences") time.sleep(12) print("\n[OK] Talking state test complete") # ============================================================================= # TRANSITION TESTS # ============================================================================= def test_smooth_transitions(): """Test: Smooth state transitions with movement lock""" print_header( "SMOOTH TRANSITIONS - Reset to Center with Lock", "Features being tested:\n" " - Head smoothly resets to center on state change\n" " - Natural acceleration/deceleration during reset\n" " - 1 second movement lock after transition\n" " - Clean start for new state animations" ) print_step(1, 5, "Starting in IDLE state") set_vrm_state("idle") print("Avatar looking around...") time.sleep(15) print_step(2, 5, "Transition to LISTENING") set_vrm_state("listening") print("Watch: Head should smoothly return to center") print("Watch: 1 second pause before listening animations start") time.sleep(15) print_step(3, 5, "Transition to THINKING") set_vrm_state("thinking") print("Watch: Smooth reset, then thinking animations begin") time.sleep(15) print_step(4, 5, "Transition to TALKING") set_vrm_state("talking") print("Watch: Clean transition to talking animations") time.sleep(15) print_step(5, 5, "Return to IDLE") set_vrm_state("idle") print("Completing transition cycle") time.sleep(13) print("\n[OK] Smooth transitions test complete") def test_rapid_transitions(): """Test: Rapid state changes to verify smooth behavior""" print_header( "RAPID TRANSITIONS - Quick State Changes", "Testing smooth behavior under rapid state changes.\n" "Head should always reset smoothly, never snap." ) states = ["idle", "listening", "thinking", "talking", "idle", "talking", "listening"] for i, state in enumerate(states, 1): print_step(i, len(states), f"Switching to {state.upper()}") set_vrm_state(state) print("Head should smoothly center before new animations") time.sleep(2.5) print("\n[OK] Rapid transitions test complete") def test_variable_lock_duration(): """Test: Different movement lock durations""" print_header( "VARIABLE LOCK DURATION - Testing Different Lock Times", "Testing how different lock durations affect transitions." ) # Short lock (0.3 seconds) print_step(1, 3, "Testing SHORT lock duration (0.3s)") set_movement_lock_duration(0.3) set_vrm_state("thinking") print("Animations should start quickly after reset") time.sleep(4) # Medium lock (1.0 seconds - default) print_step(2, 3, "Testing DEFAULT lock duration (1.0s)") set_movement_lock_duration(1.0) set_vrm_state("talking") print("Standard 1 second pause after reset") time.sleep(4) # Long lock (2.0 seconds) print_step(3, 3, "Testing LONG lock duration (2.0s)") set_movement_lock_duration(2.0) set_vrm_state("idle") print("Longer pause before idle animations") time.sleep(5) # Reset to default set_movement_lock_duration(1.0) print("\n[OK] Variable lock duration test complete") # ============================================================================= # CONVERSATION FLOW TESTS # ============================================================================= def test_conversation_flow(): """Test: Full conversation flow with realistic timings""" print_header( "FULL CONVERSATION FLOW", "Simulating a realistic conversation cycle:\n" "User speaks -> Avatar listens -> Avatar thinks -> Avatar responds" ) # Start idle print_step(1, 6, "Avatar is IDLE, waiting") set_vrm_state("idle") print("Avatar naturally looking around, occasionally at user") time.sleep(6) # User starts speaking print_step(2, 6, "User starts speaking - LISTENING") set_vrm_state("listening") print("Avatar focuses on user, occasional side glances") print("Subtle nodding shows engagement") time.sleep(8) # User finishes, avatar processes print_step(3, 6, "User finishes - THINKING") set_vrm_state("thinking") print("Avatar looks away, eyes lead head") print("Contemplative upward gaze") time.sleep(6) # Avatar responds print_step(4, 6, "Avatar responds - TALKING") set_vrm_state("talking") print("Variable nodding with head tilts") print("Animated but natural speaking gestures") time.sleep(10) # Conversation pauses print_step(5, 6, "Brief pause - IDLE") set_vrm_state("idle") print("Waiting for user response") time.sleep(4) # Quick follow-up print_step(6, 6, "Quick follow-up - TALKING") set_vrm_state("talking") print("Quick response, different nod pattern") time.sleep(5) set_vrm_state("idle") print("\n[OK] Conversation flow test complete") def test_extended_states(): """Test: Extended time in each state to observe variations""" print_header( "EXTENDED STATE DURATION", "Spending longer in each state to observe animation variations.\n" "Each state runs for 15 seconds." ) test_states = [ ("idle", "Multiple look cycles, several returns to user"), ("listening", "Several side glances, multiple nod sequences"), ("thinking", "Multiple eye-lead-head movements, varied directions"), ("talking", "Nod pattern changes, multiple tilts"), ] for i, (state, expected) in enumerate(test_states, 1): print_step(i, len(test_states), f"Extended {state.upper()} state (15 seconds)") print(f"Expected: {expected}") set_vrm_state(state) time.sleep(15) print(f"[OK] {state} observations complete") print("\n[OK] Extended state duration tests complete") # ============================================================================= # COMBINED TESTS # ============================================================================= def test_state_with_movement(): """Test: States combined with walking movement""" print_header( "STATES WITH MOVEMENT", "Testing state animations while character is walking." ) print_step(1, 4, "Start walking while IDLE") set_vrm_state("idle") vrm_walk_to(2, 0, 2, speed=1.0) print("Head should look around while walking") time.sleep(4) print_step(2, 4, "Switch to LISTENING while moving") set_vrm_state("listening") print("Should focus forward more while walking") time.sleep(4) print_step(3, 4, "Stop movement, enter THINKING") vrm_stop_movement() set_vrm_state("thinking") print("Contemplative state after stopping") time.sleep(4) print_step(4, 4, "Walk back while TALKING") set_vrm_state("talking") vrm_walk_to(0, 0, 0, speed=1.0) print("Nodding while returning to center") time.sleep(4) vrm_stop_movement() set_vrm_state("idle") print("\n[OK] State with movement test complete") def test_all(): """Run all tests in sequence""" print("\n" + "=" * 70) print("RUNNING ALL STATE ANIMATION TESTS") print("=" * 70) tests = [ ("Individual States", [ test_idle_state, test_listening_state, test_thinking_state, test_talking_state, ]), ("Transitions", [ test_smooth_transitions, test_rapid_transitions, test_variable_lock_duration, ]), ("Conversation Flows", [ test_conversation_flow, test_extended_states, ]), ("Combined Tests", [ test_state_with_movement, ]), ] for category, test_funcs in tests: print(f"\n{'='*70}") print(f"CATEGORY: {category}") print(f"{'='*70}") for test_func in test_funcs: test_func() time.sleep(1) print("\n" + "=" * 70) print("ALL TESTS COMPLETE") print("=" * 70) # ============================================================================= # MAIN # ============================================================================= if __name__ == "__main__": # Dictionary of available tests tests = { # Individual states "idle": test_idle_state, "listening": test_listening_state, "thinking": test_thinking_state, "talking": test_talking_state, # Transitions "transitions": test_smooth_transitions, "rapid": test_rapid_transitions, "lock": test_variable_lock_duration, # Flows "conversation": test_conversation_flow, "extended": test_extended_states, # Combined "movement": test_state_with_movement, # All "all": test_all, } print("\n" + "=" * 70) print("VRM AVATAR STATE SYSTEM TEST SUITE") print("=" * 70) print("\nNew Features:") print(" - Idle: Look-at-user reset chance with adjustable duration") print(" - Listening: Side glances while focused on user") print(" - Thinking: Eyes lead head movement") print(" - Talking: Variable nodding with head tilts") print(" - Transitions: Smooth reset with movement lock") if len(sys.argv) > 1: for test_name in sys.argv[1:]: if test_name in tests: tests[test_name]() else: print(f"\nUnknown test: {test_name}") print(f"Available tests: {', '.join(tests.keys())}") sys.exit(1) else: print("\nUsage: python test_ping_states.py ") print("\nAvailable tests:") print(" Individual states: idle, listening, thinking, talking") print(" Transitions: transitions, rapid, lock") print(" Conversation: conversation, extended") print(" Combined: movement") print(" Run all: all") print("\nRunning default test (smooth transitions)...\n") test_smooth_transitions()