feat: Add mcrfpy.step() and synchronous screenshot for headless mode (closes #153)
Implements Python-controlled simulation advancement for headless mode: - Add mcrfpy.step(dt) to advance simulation by dt seconds - step(None) advances to next scheduled event (timer/animation) - Timers use simulation_time in headless mode for deterministic behavior - automation.screenshot() now renders synchronously in headless mode (captures current state, not previous frame) This enables LLM agent orchestration (#156) by allowing: - Set perspective, take screenshot, query LLM - all synchronous - Deterministic simulation control without frame timing issues - Event-driven advancement with step(None) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f33e79a123
commit
60ffa68d04
7 changed files with 409 additions and 10 deletions
133
tests/unit/test_synchronous_screenshot.py
Normal file
133
tests/unit/test_synchronous_screenshot.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test synchronous screenshot in headless mode (#153)
|
||||
====================================================
|
||||
|
||||
Tests that automation.screenshot() captures the CURRENT state in headless mode,
|
||||
not the previous frame's buffer.
|
||||
|
||||
Key behavior:
|
||||
- In headless mode, screenshot() renders then captures (synchronous)
|
||||
- Changes made before screenshot() are visible in the captured image
|
||||
- No timer dance required to capture current state
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
def run_tests():
|
||||
"""Run synchronous screenshot tests"""
|
||||
print("=== Synchronous Screenshot Tests ===\n")
|
||||
|
||||
# Create a test scene with UI elements
|
||||
mcrfpy.createScene("screenshot_test")
|
||||
mcrfpy.setScene("screenshot_test")
|
||||
ui = mcrfpy.sceneUI("screenshot_test")
|
||||
|
||||
# Test 1: Basic screenshot works
|
||||
print("Test 1: Basic screenshot functionality")
|
||||
test_file = "/tmp/test_screenshot_basic.png"
|
||||
if os.path.exists(test_file):
|
||||
os.remove(test_file)
|
||||
|
||||
result = automation.screenshot(test_file)
|
||||
assert result == True, f"screenshot() should return True, got {result}"
|
||||
assert os.path.exists(test_file), "Screenshot file should exist"
|
||||
file_size = os.path.getsize(test_file)
|
||||
assert file_size > 0, "Screenshot file should not be empty"
|
||||
print(f" Screenshot saved: {test_file} ({file_size} bytes)")
|
||||
print()
|
||||
|
||||
# Test 2: Screenshot captures current state (not previous frame)
|
||||
print("Test 2: Screenshot captures current state immediately")
|
||||
|
||||
# Add a visible frame
|
||||
frame1 = mcrfpy.Frame(pos=(100, 100), size=(200, 200))
|
||||
frame1.fill_color = mcrfpy.Color(255, 0, 0) # Red
|
||||
ui.append(frame1)
|
||||
|
||||
# Take screenshot immediately - should show the red frame
|
||||
test_file2 = "/tmp/test_screenshot_state1.png"
|
||||
if os.path.exists(test_file2):
|
||||
os.remove(test_file2)
|
||||
|
||||
result = automation.screenshot(test_file2)
|
||||
assert result == True, "screenshot() should return True"
|
||||
assert os.path.exists(test_file2), "Screenshot file should exist"
|
||||
print(f" Screenshot with red frame: {test_file2}")
|
||||
|
||||
# Modify the frame color
|
||||
frame1.fill_color = mcrfpy.Color(0, 255, 0) # Green
|
||||
|
||||
# Take another screenshot - should show green, not red
|
||||
test_file3 = "/tmp/test_screenshot_state2.png"
|
||||
if os.path.exists(test_file3):
|
||||
os.remove(test_file3)
|
||||
|
||||
result = automation.screenshot(test_file3)
|
||||
assert result == True, "screenshot() should return True"
|
||||
assert os.path.exists(test_file3), "Screenshot file should exist"
|
||||
print(f" Screenshot with green frame: {test_file3}")
|
||||
print()
|
||||
|
||||
# Test 3: Multiple screenshots in succession
|
||||
print("Test 3: Multiple screenshots in succession")
|
||||
screenshot_files = []
|
||||
for i in range(3):
|
||||
frame1.fill_color = mcrfpy.Color(i * 80, i * 80, i * 80) # Varying gray
|
||||
test_file_n = f"/tmp/test_screenshot_seq{i}.png"
|
||||
if os.path.exists(test_file_n):
|
||||
os.remove(test_file_n)
|
||||
|
||||
result = automation.screenshot(test_file_n)
|
||||
assert result == True, f"screenshot() {i} should return True"
|
||||
assert os.path.exists(test_file_n), f"Screenshot {i} should exist"
|
||||
screenshot_files.append(test_file_n)
|
||||
|
||||
print(f" Created {len(screenshot_files)} sequential screenshots")
|
||||
|
||||
# Verify all files are different sizes or exist
|
||||
sizes = [os.path.getsize(f) for f in screenshot_files]
|
||||
print(f" File sizes: {sizes}")
|
||||
print()
|
||||
|
||||
# Test 4: Screenshot after step()
|
||||
print("Test 4: Screenshot works correctly after step()")
|
||||
mcrfpy.step(0.1) # Advance simulation
|
||||
|
||||
test_file4 = "/tmp/test_screenshot_after_step.png"
|
||||
if os.path.exists(test_file4):
|
||||
os.remove(test_file4)
|
||||
|
||||
result = automation.screenshot(test_file4)
|
||||
assert result == True, "screenshot() after step() should return True"
|
||||
assert os.path.exists(test_file4), "Screenshot after step() should exist"
|
||||
print(f" Screenshot after step(): {test_file4}")
|
||||
print()
|
||||
|
||||
# Clean up test files
|
||||
print("Cleaning up test files...")
|
||||
for f in [test_file, test_file2, test_file3, test_file4] + screenshot_files:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
|
||||
print()
|
||||
print("=== All Synchronous Screenshot Tests Passed! ===")
|
||||
return True
|
||||
|
||||
# Main execution
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if run_tests():
|
||||
print("\nPASS")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\nFAIL")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\nFAIL: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue