synth-ai 0.2.12__py3-none-any.whl → 0.2.13.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- examples/multi_step/configs/crafter_rl_outcome.toml +74 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +186 -0
- examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +83 -0
- examples/multi_step/configs/crafter_rl_stepwise_simple.toml +78 -0
- examples/multi_step/crafter_rl_lora.md +51 -10
- examples/multi_step/sse_metrics_streaming_notes.md +357 -0
- examples/multi_step/task_app_config_notes.md +7 -1
- examples/swe/task_app/grpo_swe_mini.py +55 -26
- examples/swe/task_app/hosted/rollout.py +40 -0
- examples/swe/task_app/hosted/test_service.py +5 -6
- examples/task_apps/TESTING.md +275 -0
- examples/task_apps/__init__.py +0 -0
- examples/task_apps/crafter/__init__.py +0 -0
- examples/task_apps/crafter/task_app/__init__.py +2 -0
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/grpo_crafter.py +21 -46
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/grpo_crafter_task_app.py +1 -1
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/policy.py +60 -4
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/inference/openai_client.py +109 -45
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/policy_routes.py +67 -49
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/rollout.py +242 -193
- examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/test_service.py +5 -6
- examples/task_apps/dev/pokemon_emerald/__init__.py +2 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +811 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +120 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +160 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +155 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +69 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +96 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +1502 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +4 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +68 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +216 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +35 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +631 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +1544 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +1428 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +4848 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +41 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +298 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +95 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +204 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +2152 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +429 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +155 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +78 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +122 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +76 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +413 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +204 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +133 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +229 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +300 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +205 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +200 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +284 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +468 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +575 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +311 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +259 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +372 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +296 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +275 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +22 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +44 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +514 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +415 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +1763 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +33 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +106 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +334 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +1020 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +188 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +1481 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +862 -0
- examples/task_apps/dev/pokemon_emerald/modal_app.py +114 -0
- examples/task_apps/dev/pokemon_emerald/task_app/README.md +81 -0
- examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +6 -0
- examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +685 -0
- examples/task_apps/enron/__init__.py +1 -0
- examples/task_apps/enron/eval_groq_qwen32.toml +16 -0
- examples/task_apps/enron/task_app/README.md +14 -0
- examples/task_apps/enron/task_app/__init__.py +1 -0
- examples/task_apps/enron/task_app/grpo_enron.py +906 -0
- examples/task_apps/enron/task_app/grpo_enron_task_app.py +146 -0
- examples/task_apps/enron/tests/__init__.py +2 -0
- examples/task_apps/enron/tests/conftest.py +115 -0
- examples/task_apps/enron/tests/integration/__init__.py +2 -0
- examples/task_apps/enron/tests/integration/test_enron_eval.py +177 -0
- examples/task_apps/enron/tests/integration/test_enron_rollout.py +135 -0
- examples/task_apps/enron/tests/unit/__init__.py +2 -0
- examples/task_apps/enron/tests/unit/test_enron_environment.py +126 -0
- examples/task_apps/math/__init__.py +0 -0
- examples/{rl/task_app → task_apps/math}/math_single_step.py +19 -10
- examples/task_apps/pokemon_battle/__init__.py +2 -0
- examples/task_apps/pokemon_battle/modal_app.py +104 -0
- examples/task_apps/pokemon_battle/task_app/README.md +68 -0
- examples/task_apps/pokemon_battle/task_app/__init__.py +6 -0
- examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +932 -0
- examples/task_apps/pokemon_red/README.md +357 -0
- examples/task_apps/pokemon_red/__init__.py +3 -0
- examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +225 -0
- examples/task_apps/pokemon_red/pallet_town_rl_config.toml +73 -0
- examples/task_apps/pokemon_red/task_app.py +606 -0
- examples/task_apps/pokemon_red/test_pallet_town_rewards.py +191 -0
- examples/task_apps/sokoban/README.md +307 -0
- examples/task_apps/sokoban/__init__.py +3 -0
- examples/task_apps/sokoban/eval_groq_qwen32.toml +16 -0
- examples/task_apps/sokoban/eval_openai_gpt5.toml +16 -0
- examples/task_apps/sokoban/task_app.py +1058 -0
- examples/task_apps/sokoban/tests/__init__.py +2 -0
- examples/task_apps/sokoban/tests/conftest.py +113 -0
- examples/task_apps/sokoban/tests/integration/__init__.py +2 -0
- examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +57 -0
- examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +198 -0
- examples/task_apps/sokoban/tests/unit/__init__.py +2 -0
- examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +114 -0
- examples/task_apps/verilog/__init__.py +1 -0
- examples/task_apps/verilog/eval_groq_qwen32b.toml +20 -0
- examples/task_apps/verilog/task_app/README.md +12 -0
- examples/task_apps/verilog/task_app/__init__.py +1 -0
- examples/task_apps/verilog/task_app/grpo_verilog.py +931 -0
- examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +145 -0
- examples/task_apps/verilog/tests/__init__.py +2 -0
- examples/task_apps/verilog/tests/conftest.py +115 -0
- examples/task_apps/verilog/tests/integration/__init__.py +2 -0
- examples/task_apps/verilog/tests/integration/test_verilog_eval.py +179 -0
- examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +55 -0
- examples/task_apps/verilog/tests/unit/__init__.py +2 -0
- examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +118 -0
- examples/vlm/crafter_openai_vlm_agent.py +4 -4
- examples/vlm/run_crafter_vlm_benchmark.py +4 -4
- examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +4 -2
- examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +4 -2
- examples/warming_up_to_rl/run_eval.py +127 -18
- examples/workflows/__init__.py +0 -0
- examples/workflows/math_rl/__init__.py +0 -0
- examples/workflows/math_rl/download_dataset.py +80 -0
- synth_ai/__init__.py +41 -1
- synth_ai/api/train/builders.py +73 -29
- synth_ai/api/train/cli.py +12 -6
- synth_ai/api/train/configs/__init__.py +44 -0
- synth_ai/api/train/configs/rl.py +134 -0
- synth_ai/api/train/configs/sft.py +95 -0
- synth_ai/api/train/configs/shared.py +24 -0
- synth_ai/api/train/env_resolver.py +5 -2
- synth_ai/api/train/supported_algos.py +10 -5
- synth_ai/api/train/utils.py +7 -4
- synth_ai/cli/__init__.py +7 -51
- synth_ai/cli/_storage.py +4 -3
- synth_ai/cli/_validate_task_app.py +11 -0
- synth_ai/cli/balance.py +4 -3
- synth_ai/cli/calc.py +2 -2
- synth_ai/cli/demo.py +49 -43
- synth_ai/cli/legacy_root_backup.py +1 -1
- synth_ai/cli/rl_demo.py +86 -106
- synth_ai/cli/root.py +0 -97
- synth_ai/cli/task_apps.py +1710 -186
- synth_ai/demos/core/cli.py +121 -159
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +28 -16
- synth_ai/environments/examples/crafter_classic/environment.py +16 -0
- synth_ai/environments/examples/enron/engine.py +7 -2
- synth_ai/environments/examples/enron/environment.py +68 -0
- synth_ai/environments/examples/red/engine.py +27 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +7 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +477 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +32 -0
- synth_ai/environments/examples/red/environment.py +60 -0
- synth_ai/environments/examples/sokoban/taskset.py +116 -0
- synth_ai/environments/examples/verilog/engine.py +30 -4
- synth_ai/evals/__init__.py +15 -0
- synth_ai/evals/client.py +82 -0
- synth_ai/evals/types.py +42 -0
- synth_ai/jobs/client.py +16 -4
- synth_ai/judge_schemas.py +127 -0
- synth_ai/py.typed +0 -0
- synth_ai/task/__init__.py +14 -5
- synth_ai/task/contracts.py +124 -38
- synth_ai/task/proxy.py +48 -56
- synth_ai/task/rubrics/__init__.py +53 -0
- synth_ai/task/rubrics/loaders.py +133 -0
- synth_ai/task/rubrics/models.py +57 -0
- synth_ai/task/rubrics/scoring.py +113 -0
- synth_ai/task/rubrics/strict.py +149 -0
- synth_ai/task/server.py +8 -7
- synth_ai/task/validators.py +269 -6
- synth_ai/tracing_v3/decorators.py +7 -3
- synth_ai/tracing_v3/replica_sync.py +4 -4
- synth_ai/tracing_v3/serialization.py +130 -0
- synth_ai/tracing_v3/trace_utils.py +317 -0
- synth_ai/tracing_v3/turso/native_manager.py +3 -3
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/METADATA +4 -1
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/RECORD +228 -89
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/entry_points.txt +0 -1
- synth_ai/task/rubrics.py +0 -219
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/README.md +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/README.md +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/__init__.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/branching.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/environment_routes.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/__init__.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/__init__.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/app.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/environment.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/react_agent.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/shared.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/tools.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/hosted_app.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/inference/__init__.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/main.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/registry.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/storage/__init__.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/storage/volume.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/test_agents.py +0 -0
- /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/utils.py +0 -0
- /examples/{rl/task_app → task_apps/math}/README.md +0 -0
- /examples/{rl/task_app → task_apps/math}/math_task_app.py +0 -0
- /examples/{rl → workflows/math_rl}/configs/eval_base_qwen.toml +0 -0
- /examples/{rl → workflows/math_rl}/configs/eval_rl_qwen.toml +0 -0
- /examples/{rl → workflows/math_rl}/configs/rl_from_base_qwen.toml +0 -0
- /examples/{rl → workflows/math_rl}/configs/rl_from_base_qwen17.toml +0 -0
- /examples/{rl → workflows/math_rl}/configs/rl_from_ft_qwen.toml +0 -0
- /examples/{rl → workflows/math_rl}/run_eval.py +0 -0
- /examples/{rl → workflows/math_rl}/run_rl_and_save.py +0 -0
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Simple multiprocess client that connects to the server and runs the agent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import base64
|
|
10
|
+
import io
|
|
11
|
+
import requests
|
|
12
|
+
from PIL import Image
|
|
13
|
+
|
|
14
|
+
# Display-related imports (conditionally used)
|
|
15
|
+
try:
|
|
16
|
+
import pygame
|
|
17
|
+
import numpy as np
|
|
18
|
+
PYGAME_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
PYGAME_AVAILABLE = False
|
|
21
|
+
|
|
22
|
+
# Add parent directory to path for imports
|
|
23
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
24
|
+
|
|
25
|
+
from agent import Agent
|
|
26
|
+
from utils.state_formatter import format_state_for_llm
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def update_display_with_status(screen, font, mode, step_count, additional_info="", frame_surface=None):
|
|
30
|
+
"""
|
|
31
|
+
Update the display with frame (if provided) and status text overlay.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
screen: Pygame screen surface
|
|
35
|
+
font: Pygame font for rendering text
|
|
36
|
+
mode: Current mode (MANUAL/AGENT/AUTO)
|
|
37
|
+
step_count: Current step count
|
|
38
|
+
additional_info: Additional status information to display
|
|
39
|
+
frame_surface: Optional frame surface to display, if None fills with black
|
|
40
|
+
"""
|
|
41
|
+
if frame_surface:
|
|
42
|
+
# Display the frame
|
|
43
|
+
scaled_surface = pygame.transform.scale(frame_surface, (480, 320))
|
|
44
|
+
screen.blit(scaled_surface, (0, 0))
|
|
45
|
+
else:
|
|
46
|
+
# Fill with black if no frame
|
|
47
|
+
screen.fill((0, 0, 0))
|
|
48
|
+
|
|
49
|
+
# Create status text
|
|
50
|
+
status_text = f"{mode} | Steps: {step_count}"
|
|
51
|
+
if additional_info:
|
|
52
|
+
status_text += f" | {additional_info}"
|
|
53
|
+
|
|
54
|
+
# Render and display status text
|
|
55
|
+
text_surface = font.render(status_text, True, (255, 255, 255))
|
|
56
|
+
screen.blit(text_surface, (10, 290))
|
|
57
|
+
pygame.display.flip()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_multiprocess_client(server_port=8000, args=None):
|
|
61
|
+
"""
|
|
62
|
+
Simple client that gets state from server, processes with agent, sends action back.
|
|
63
|
+
Supports manual control when pygame display is enabled.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
server_port: Port the server is running on
|
|
67
|
+
args: Command line arguments with agent configuration
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
bool: True if ran successfully
|
|
71
|
+
"""
|
|
72
|
+
server_url = f"http://localhost:{server_port}"
|
|
73
|
+
|
|
74
|
+
# Initialize the agent (it handles VLM, simple vs 4-module, etc internally)
|
|
75
|
+
agent = Agent(args)
|
|
76
|
+
print(f"✅ Agent initialized")
|
|
77
|
+
print(f"🎮 Client connected to server at {server_url}")
|
|
78
|
+
|
|
79
|
+
# Display setup
|
|
80
|
+
headless = args and args.headless
|
|
81
|
+
screen = None
|
|
82
|
+
clock = None
|
|
83
|
+
font = None
|
|
84
|
+
|
|
85
|
+
# Control state - three modes: MANUAL, AGENT, AUTO
|
|
86
|
+
if args and args.manual:
|
|
87
|
+
mode = "MANUAL"
|
|
88
|
+
elif args and args.agent_auto:
|
|
89
|
+
mode = "AUTO"
|
|
90
|
+
else:
|
|
91
|
+
mode = "AGENT"
|
|
92
|
+
|
|
93
|
+
last_agent_time = time.time()
|
|
94
|
+
step_count = 0
|
|
95
|
+
|
|
96
|
+
# Initialize pygame if not headless
|
|
97
|
+
if not headless and PYGAME_AVAILABLE:
|
|
98
|
+
pygame.init()
|
|
99
|
+
screen = pygame.display.set_mode((480, 320))
|
|
100
|
+
pygame.display.set_caption("Pokemon Emerald")
|
|
101
|
+
font = pygame.font.Font(None, 24)
|
|
102
|
+
clock = pygame.time.Clock()
|
|
103
|
+
print("✅ Display initialized")
|
|
104
|
+
print("Controls: Tab=Cycle Mode (MANUAL/AGENT/AUTO), Space=Agent Step, M=Show State, Arrows/WASD=Move, Z=A, X=B")
|
|
105
|
+
elif not headless and not PYGAME_AVAILABLE:
|
|
106
|
+
print("⚠️ Pygame not available, running in headless mode")
|
|
107
|
+
headless = True
|
|
108
|
+
|
|
109
|
+
# Auto-display comprehensive state in manual mode for debugging
|
|
110
|
+
auto_state_timer = None
|
|
111
|
+
if args and args.manual:
|
|
112
|
+
auto_state_timer = time.time() + 5 # Display state after 5 seconds in manual mode (allow map to initialize)
|
|
113
|
+
|
|
114
|
+
# Main loop
|
|
115
|
+
running = True
|
|
116
|
+
while running:
|
|
117
|
+
try:
|
|
118
|
+
# Auto-display comprehensive state in manual mode (one time)
|
|
119
|
+
if auto_state_timer and time.time() >= auto_state_timer:
|
|
120
|
+
print("🔍 Auto-displaying comprehensive state in manual mode...")
|
|
121
|
+
try:
|
|
122
|
+
response = requests.get(f"{server_url}/state", timeout=5)
|
|
123
|
+
if response.status_code == 200:
|
|
124
|
+
state_data = response.json()
|
|
125
|
+
print("=" * 80)
|
|
126
|
+
print("📊 COMPREHENSIVE STATE (LLM View)")
|
|
127
|
+
print("=" * 80)
|
|
128
|
+
from utils.state_formatter import format_state_for_llm
|
|
129
|
+
formatted_state = format_state_for_llm(state_data)
|
|
130
|
+
print(formatted_state)
|
|
131
|
+
print("=" * 80)
|
|
132
|
+
else:
|
|
133
|
+
print(f"❌ Failed to get state: {response.status_code}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"❌ Error getting state: {e}")
|
|
136
|
+
auto_state_timer = None # Only display once
|
|
137
|
+
|
|
138
|
+
# Handle pygame events and display
|
|
139
|
+
if not headless:
|
|
140
|
+
# Process events
|
|
141
|
+
for event in pygame.event.get():
|
|
142
|
+
if event.type == pygame.QUIT:
|
|
143
|
+
running = False
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
elif event.type == pygame.KEYDOWN:
|
|
147
|
+
if event.key == pygame.K_ESCAPE:
|
|
148
|
+
running = False
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
# Mode cycle
|
|
152
|
+
elif event.key == pygame.K_TAB:
|
|
153
|
+
if mode == "MANUAL":
|
|
154
|
+
mode = "AGENT"
|
|
155
|
+
elif mode == "AGENT":
|
|
156
|
+
mode = "AUTO"
|
|
157
|
+
else: # AUTO
|
|
158
|
+
mode = "MANUAL"
|
|
159
|
+
print(f"🎮 Mode: {mode}")
|
|
160
|
+
|
|
161
|
+
# Manual agent step
|
|
162
|
+
elif event.key == pygame.K_SPACE and mode in ("AGENT", "AUTO"):
|
|
163
|
+
# Force an agent step
|
|
164
|
+
response = requests.get(f"{server_url}/state", timeout=5)
|
|
165
|
+
if response.status_code == 200:
|
|
166
|
+
state_data = response.json()
|
|
167
|
+
screenshot_base64 = state_data.get("visual", {}).get("screenshot_base64", "")
|
|
168
|
+
if screenshot_base64:
|
|
169
|
+
img_data = base64.b64decode(screenshot_base64)
|
|
170
|
+
screenshot = Image.open(io.BytesIO(img_data))
|
|
171
|
+
game_state = {
|
|
172
|
+
'frame': screenshot,
|
|
173
|
+
'player': state_data.get('player', {}),
|
|
174
|
+
'game': state_data.get('game', {}),
|
|
175
|
+
'map': state_data.get('map', {}),
|
|
176
|
+
'milestones': state_data.get('milestones', {}),
|
|
177
|
+
'visual': state_data.get('visual', {}),
|
|
178
|
+
'step_number': state_data.get('step_number', 0),
|
|
179
|
+
'status': state_data.get('status', ''),
|
|
180
|
+
'action_queue_length': state_data.get('action_queue_length', 0)
|
|
181
|
+
}
|
|
182
|
+
result = agent.step(game_state)
|
|
183
|
+
if result and result.get('action'):
|
|
184
|
+
# Convert action to buttons list format expected by server
|
|
185
|
+
action = result['action']
|
|
186
|
+
if isinstance(action, list):
|
|
187
|
+
buttons = action # Already a list of buttons
|
|
188
|
+
else:
|
|
189
|
+
# Single action string, convert to list
|
|
190
|
+
buttons = action.split(',') if ',' in action else [action]
|
|
191
|
+
buttons = [btn.strip() for btn in buttons]
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
response = requests.post(
|
|
195
|
+
f"{server_url}/action",
|
|
196
|
+
json={"buttons": buttons},
|
|
197
|
+
timeout=5
|
|
198
|
+
)
|
|
199
|
+
if response.status_code == 200:
|
|
200
|
+
print(f"🎮 Agent: {action} (sent successfully)")
|
|
201
|
+
else:
|
|
202
|
+
print(f"🎮 Agent: {action} (server error: {response.status_code})")
|
|
203
|
+
except requests.exceptions.RequestException as e:
|
|
204
|
+
print(f"🎮 Agent: {action} (connection error: {e})")
|
|
205
|
+
step_count += 1
|
|
206
|
+
print(f"🎮 Step {step_count}: {result['action']}")
|
|
207
|
+
|
|
208
|
+
# Manual controls (only in manual mode)
|
|
209
|
+
elif mode == "MANUAL":
|
|
210
|
+
action = None
|
|
211
|
+
if event.key in (pygame.K_UP, pygame.K_w):
|
|
212
|
+
action = "UP"
|
|
213
|
+
elif event.key in (pygame.K_DOWN, pygame.K_s):
|
|
214
|
+
action = "DOWN"
|
|
215
|
+
elif event.key in (pygame.K_LEFT, pygame.K_a):
|
|
216
|
+
action = "LEFT"
|
|
217
|
+
elif event.key in (pygame.K_RIGHT, pygame.K_d):
|
|
218
|
+
action = "RIGHT"
|
|
219
|
+
elif event.key == pygame.K_z:
|
|
220
|
+
action = "A"
|
|
221
|
+
elif event.key == pygame.K_x:
|
|
222
|
+
action = "B"
|
|
223
|
+
elif event.key == pygame.K_RETURN:
|
|
224
|
+
action = "START"
|
|
225
|
+
elif event.key == pygame.K_BACKSPACE:
|
|
226
|
+
action = "SELECT"
|
|
227
|
+
elif event.key == pygame.K_LSHIFT:
|
|
228
|
+
action = "L"
|
|
229
|
+
elif event.key == pygame.K_RSHIFT:
|
|
230
|
+
action = "R"
|
|
231
|
+
elif event.key == pygame.K_1:
|
|
232
|
+
# Save state
|
|
233
|
+
print("💾 Saving state...")
|
|
234
|
+
try:
|
|
235
|
+
response = requests.post(f"{server_url}/save_state",
|
|
236
|
+
json={"filepath": ".pokeagent_cache/manual_save.state"},
|
|
237
|
+
timeout=5)
|
|
238
|
+
if response.status_code == 200:
|
|
239
|
+
print("✅ State saved to .pokeagent_cache/manual_save.state")
|
|
240
|
+
else:
|
|
241
|
+
print(f"❌ Failed to save state: {response.status_code}")
|
|
242
|
+
except Exception as e:
|
|
243
|
+
print(f"❌ Error saving state: {e}")
|
|
244
|
+
elif event.key == pygame.K_2:
|
|
245
|
+
# Load state
|
|
246
|
+
print("📂 Loading state...")
|
|
247
|
+
try:
|
|
248
|
+
response = requests.post(f"{server_url}/load_state",
|
|
249
|
+
json={"filepath": ".pokeagent_cache/manual_save.state"},
|
|
250
|
+
timeout=5)
|
|
251
|
+
if response.status_code == 200:
|
|
252
|
+
print("✅ State loaded from .pokeagent_cache/manual_save.state")
|
|
253
|
+
else:
|
|
254
|
+
print(f"❌ Failed to load state: {response.status_code}")
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print(f"❌ Error loading state: {e}")
|
|
257
|
+
elif event.key == pygame.K_m:
|
|
258
|
+
# Display comprehensive state (what LLM sees)
|
|
259
|
+
print("🔍 Getting comprehensive state...")
|
|
260
|
+
try:
|
|
261
|
+
response = requests.get(f"{server_url}/state", timeout=5)
|
|
262
|
+
if response.status_code == 200:
|
|
263
|
+
state_data = response.json()
|
|
264
|
+
print("=" * 80)
|
|
265
|
+
print("📊 COMPREHENSIVE STATE (LLM View)")
|
|
266
|
+
print("=" * 80)
|
|
267
|
+
|
|
268
|
+
# Format and display state in a readable way (exactly what LLM sees)
|
|
269
|
+
formatted_state = format_state_for_llm(state_data)
|
|
270
|
+
print(formatted_state)
|
|
271
|
+
|
|
272
|
+
print("=" * 80)
|
|
273
|
+
else:
|
|
274
|
+
print(f"❌ Failed to get state: {response.status_code}")
|
|
275
|
+
except Exception as e:
|
|
276
|
+
print(f"❌ Error getting state: {e}")
|
|
277
|
+
|
|
278
|
+
if action:
|
|
279
|
+
# Send manual action to server using the same endpoint as agent actions
|
|
280
|
+
try:
|
|
281
|
+
response = requests.post(
|
|
282
|
+
f"{server_url}/action",
|
|
283
|
+
json={"buttons": [action]},
|
|
284
|
+
timeout=2
|
|
285
|
+
)
|
|
286
|
+
if response.status_code == 200:
|
|
287
|
+
print(f"🎮 Manual: {action} (sent successfully)")
|
|
288
|
+
else:
|
|
289
|
+
print(f"🎮 Manual: {action} (server error: {response.status_code})")
|
|
290
|
+
except requests.exceptions.RequestException as e:
|
|
291
|
+
print(f"🎮 Manual: {action} (connection error: {e})")
|
|
292
|
+
|
|
293
|
+
# Update display
|
|
294
|
+
try:
|
|
295
|
+
response = requests.get(f"{server_url}/screenshot", timeout=0.5)
|
|
296
|
+
if response.status_code == 200:
|
|
297
|
+
frame_data = response.json().get("screenshot_base64", "")
|
|
298
|
+
if frame_data:
|
|
299
|
+
img_data = base64.b64decode(frame_data)
|
|
300
|
+
img = Image.open(io.BytesIO(img_data))
|
|
301
|
+
frame_array = np.array(img)
|
|
302
|
+
frame_surface = pygame.surfarray.make_surface(frame_array.swapaxes(0, 1))
|
|
303
|
+
update_display_with_status(screen, font, mode, step_count, frame_surface=frame_surface)
|
|
304
|
+
else:
|
|
305
|
+
update_display_with_status(screen, font, mode, step_count, "No frame data")
|
|
306
|
+
else:
|
|
307
|
+
update_display_with_status(screen, font, mode, step_count, f"Server error: {response.status_code}")
|
|
308
|
+
except Exception as e:
|
|
309
|
+
update_display_with_status(screen, font, mode, step_count, f"Error: {str(e)[:30]}")
|
|
310
|
+
|
|
311
|
+
clock.tick(30) # 30 FPS for display
|
|
312
|
+
|
|
313
|
+
# Auto agent processing (both headless and display modes)
|
|
314
|
+
if mode == "AUTO":
|
|
315
|
+
current_time = time.time()
|
|
316
|
+
if current_time - last_agent_time > 3.0: # Every 3 seconds
|
|
317
|
+
# Check if action queue is ready
|
|
318
|
+
try:
|
|
319
|
+
queue_response = requests.get(f"{server_url}/queue_status", timeout=1)
|
|
320
|
+
if queue_response.status_code == 200:
|
|
321
|
+
queue_status = queue_response.json()
|
|
322
|
+
if queue_status.get("queue_empty", False):
|
|
323
|
+
# Get state and process
|
|
324
|
+
response = requests.get(f"{server_url}/state", timeout=5)
|
|
325
|
+
if response.status_code == 200:
|
|
326
|
+
state_data = response.json()
|
|
327
|
+
screenshot_base64 = state_data.get("visual", {}).get("screenshot_base64", "")
|
|
328
|
+
if screenshot_base64:
|
|
329
|
+
img_data = base64.b64decode(screenshot_base64)
|
|
330
|
+
screenshot = Image.open(io.BytesIO(img_data))
|
|
331
|
+
|
|
332
|
+
game_state = {
|
|
333
|
+
'frame': screenshot,
|
|
334
|
+
'player': state_data.get('player', {}),
|
|
335
|
+
'game': state_data.get('game', {}),
|
|
336
|
+
'map': state_data.get('map', {}),
|
|
337
|
+
'milestones': state_data.get('milestones', {}),
|
|
338
|
+
'visual': state_data.get('visual', {}),
|
|
339
|
+
'step_number': state_data.get('step_number', 0),
|
|
340
|
+
'status': state_data.get('status', ''),
|
|
341
|
+
'action_queue_length': state_data.get('action_queue_length', 0)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
result = agent.step(game_state)
|
|
345
|
+
if result and result.get('action'):
|
|
346
|
+
# Convert action to buttons list format expected by server
|
|
347
|
+
action = result['action']
|
|
348
|
+
if isinstance(action, list):
|
|
349
|
+
buttons = action # Already a list of buttons
|
|
350
|
+
else:
|
|
351
|
+
# Single action string, convert to list
|
|
352
|
+
buttons = action.split(',') if ',' in action else [action]
|
|
353
|
+
buttons = [btn.strip() for btn in buttons]
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
response = requests.post(
|
|
357
|
+
f"{server_url}/action",
|
|
358
|
+
json={"buttons": buttons},
|
|
359
|
+
timeout=5
|
|
360
|
+
)
|
|
361
|
+
if response.status_code == 200:
|
|
362
|
+
step_count += 1
|
|
363
|
+
print(f"🎮 Agent: {action} (sent successfully)")
|
|
364
|
+
print(f"🎮 Step {step_count}: {result['action']}")
|
|
365
|
+
last_agent_time = current_time
|
|
366
|
+
|
|
367
|
+
# Auto-save checkpoint after each step for persistence
|
|
368
|
+
try:
|
|
369
|
+
# Sync client's LLM metrics to server before saving checkpoint
|
|
370
|
+
try:
|
|
371
|
+
from utils.llm_logger import get_llm_logger
|
|
372
|
+
client_llm_logger = get_llm_logger()
|
|
373
|
+
if client_llm_logger:
|
|
374
|
+
sync_response = requests.post(
|
|
375
|
+
f"{server_url}/sync_llm_metrics",
|
|
376
|
+
json={"cumulative_metrics": client_llm_logger.cumulative_metrics},
|
|
377
|
+
timeout=5
|
|
378
|
+
)
|
|
379
|
+
if sync_response.status_code == 200:
|
|
380
|
+
if step_count % 10 == 0: # Log every 10 steps to avoid spam
|
|
381
|
+
print(f"🔄 LLM metrics synced to server")
|
|
382
|
+
except Exception as e:
|
|
383
|
+
print(f"⚠️ LLM metrics sync error: {e}")
|
|
384
|
+
|
|
385
|
+
# Save game state checkpoint
|
|
386
|
+
checkpoint_response = requests.post(
|
|
387
|
+
f"{server_url}/checkpoint",
|
|
388
|
+
json={"step_count": step_count},
|
|
389
|
+
timeout=10
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Save agent history to checkpoint_llm.txt
|
|
393
|
+
history_response = requests.post(
|
|
394
|
+
f"{server_url}/save_agent_history",
|
|
395
|
+
timeout=5
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if checkpoint_response.status_code == 200 and history_response.status_code == 200:
|
|
399
|
+
if step_count % 10 == 0: # Log every 10 steps to avoid spam
|
|
400
|
+
print(f"💾 Checkpoint and history saved at step {step_count}")
|
|
401
|
+
else:
|
|
402
|
+
print(f"⚠️ Save failed - Checkpoint: {checkpoint_response.status_code}, History: {history_response.status_code}")
|
|
403
|
+
except requests.exceptions.RequestException as e:
|
|
404
|
+
print(f"⚠️ Checkpoint/history save error: {e}")
|
|
405
|
+
else:
|
|
406
|
+
print(f"🎮 Agent: {action} (server error: {response.status_code})")
|
|
407
|
+
except requests.exceptions.RequestException as e:
|
|
408
|
+
print(f"🎮 Agent: {action} (connection error: {e})")
|
|
409
|
+
except Exception as e:
|
|
410
|
+
print(f"❌ AUTO mode error: {e}")
|
|
411
|
+
import traceback
|
|
412
|
+
traceback.print_exc()
|
|
413
|
+
|
|
414
|
+
# Small sleep to prevent CPU spinning
|
|
415
|
+
if headless:
|
|
416
|
+
time.sleep(0.1)
|
|
417
|
+
|
|
418
|
+
except KeyboardInterrupt:
|
|
419
|
+
print("\n👋 Shutdown requested")
|
|
420
|
+
break
|
|
421
|
+
except Exception as e:
|
|
422
|
+
print(f"❌ Error: {e}")
|
|
423
|
+
time.sleep(2)
|
|
424
|
+
|
|
425
|
+
# Cleanup
|
|
426
|
+
if not headless and PYGAME_AVAILABLE:
|
|
427
|
+
pygame.quit()
|
|
428
|
+
|
|
429
|
+
return True
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Lightweight frame server for stream.html
|
|
4
|
+
Serves only screenshot frames, separate from main game server
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import json
|
|
11
|
+
import base64
|
|
12
|
+
import threading
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from PIL import Image
|
|
15
|
+
import numpy as np
|
|
16
|
+
import argparse
|
|
17
|
+
|
|
18
|
+
# Add parent directory to path for imports
|
|
19
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from fastapi import FastAPI, Response
|
|
23
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
24
|
+
import uvicorn
|
|
25
|
+
except ImportError:
|
|
26
|
+
print("❌ FastAPI not available. Install with: pip install fastapi uvicorn")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
app = FastAPI(title="Pokemon Frame Server")
|
|
30
|
+
|
|
31
|
+
# Add CORS middleware
|
|
32
|
+
app.add_middleware(
|
|
33
|
+
CORSMiddleware,
|
|
34
|
+
allow_origins=["*"],
|
|
35
|
+
allow_credentials=True,
|
|
36
|
+
allow_methods=["*"],
|
|
37
|
+
allow_headers=["*"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Global state
|
|
41
|
+
current_frame = None
|
|
42
|
+
frame_lock = threading.Lock()
|
|
43
|
+
frame_counter = 0
|
|
44
|
+
last_update = time.time()
|
|
45
|
+
|
|
46
|
+
# Frame cache for shared memory communication
|
|
47
|
+
# Use cache directory instead of /tmp
|
|
48
|
+
CACHE_DIR = ".pokeagent_cache"
|
|
49
|
+
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
50
|
+
FRAME_CACHE_FILE = os.path.join(CACHE_DIR, "frame_cache.json")
|
|
51
|
+
FRAME_UPDATE_INTERVAL = 0.025 # 40 FPS
|
|
52
|
+
|
|
53
|
+
def load_frame_from_cache():
|
|
54
|
+
"""Load the latest frame from shared cache file"""
|
|
55
|
+
global current_frame, frame_counter, last_update
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
if os.path.exists(FRAME_CACHE_FILE):
|
|
59
|
+
with open(FRAME_CACHE_FILE, 'r') as f:
|
|
60
|
+
data = json.load(f)
|
|
61
|
+
|
|
62
|
+
# Check if frame is newer
|
|
63
|
+
cache_counter = data.get('frame_counter', 0)
|
|
64
|
+
if cache_counter > frame_counter:
|
|
65
|
+
with frame_lock:
|
|
66
|
+
current_frame = data.get('frame_data')
|
|
67
|
+
frame_counter = cache_counter
|
|
68
|
+
last_update = time.time()
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
pass # Silently handle cache read errors
|
|
72
|
+
|
|
73
|
+
def frame_updater():
|
|
74
|
+
"""Background thread to periodically check for new frames"""
|
|
75
|
+
while True:
|
|
76
|
+
try:
|
|
77
|
+
load_frame_from_cache()
|
|
78
|
+
time.sleep(FRAME_UPDATE_INTERVAL)
|
|
79
|
+
except Exception:
|
|
80
|
+
time.sleep(0.1)
|
|
81
|
+
|
|
82
|
+
@app.get("/health")
|
|
83
|
+
async def health_check():
|
|
84
|
+
"""Simple health check endpoint"""
|
|
85
|
+
return {"status": "ok", "server": "frame_server"}
|
|
86
|
+
|
|
87
|
+
@app.get("/frame")
|
|
88
|
+
async def get_frame():
|
|
89
|
+
"""Get the current game frame"""
|
|
90
|
+
global current_frame, frame_counter, last_update
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
load_frame_from_cache() # Try to get latest frame
|
|
94
|
+
|
|
95
|
+
with frame_lock:
|
|
96
|
+
if current_frame:
|
|
97
|
+
# Frame is already base64 encoded
|
|
98
|
+
return Response(
|
|
99
|
+
content=json.dumps({
|
|
100
|
+
"frame": current_frame,
|
|
101
|
+
"frame_count": frame_counter,
|
|
102
|
+
"timestamp": last_update,
|
|
103
|
+
"status": "ok"
|
|
104
|
+
}),
|
|
105
|
+
media_type="application/json"
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
# No frame available
|
|
109
|
+
return Response(
|
|
110
|
+
content=json.dumps({
|
|
111
|
+
"frame": None,
|
|
112
|
+
"frame_count": 0,
|
|
113
|
+
"timestamp": time.time(),
|
|
114
|
+
"status": "no_frame"
|
|
115
|
+
}),
|
|
116
|
+
media_type="application/json"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return Response(
|
|
121
|
+
content=json.dumps({
|
|
122
|
+
"frame": None,
|
|
123
|
+
"error": str(e),
|
|
124
|
+
"status": "error"
|
|
125
|
+
}),
|
|
126
|
+
media_type="application/json",
|
|
127
|
+
status_code=500
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@app.get("/status")
|
|
131
|
+
async def get_status():
|
|
132
|
+
"""Get frame server status"""
|
|
133
|
+
return {
|
|
134
|
+
"frame_count": frame_counter,
|
|
135
|
+
"last_update": last_update,
|
|
136
|
+
"cache_file": FRAME_CACHE_FILE,
|
|
137
|
+
"cache_exists": os.path.exists(FRAME_CACHE_FILE)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
|
|
142
|
+
parser = argparse.ArgumentParser(description="Pokemon Frame Server")
|
|
143
|
+
parser.add_argument("--port", type=int, default=8001, help="Port to run on")
|
|
144
|
+
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind to")
|
|
145
|
+
args = parser.parse_args()
|
|
146
|
+
|
|
147
|
+
print(f"🖼️ Starting Pokemon Frame Server on {args.host}:{args.port}")
|
|
148
|
+
print(f"📁 Frame cache: {FRAME_CACHE_FILE}")
|
|
149
|
+
|
|
150
|
+
# Start background frame updater
|
|
151
|
+
frame_thread = threading.Thread(target=frame_updater, daemon=True)
|
|
152
|
+
frame_thread.start()
|
|
153
|
+
|
|
154
|
+
# Start server
|
|
155
|
+
uvicorn.run(app, host=args.host, port=args.port, log_level="warning")
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Pokemon Emerald Emulator Tests
|
|
2
|
+
|
|
3
|
+
This directory contains tests for the Pokemon Emerald emulator FPS adjustment system.
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
tests/
|
|
9
|
+
├── states/ # Test state files
|
|
10
|
+
│ ├── simple_test.state # Base overworld state (30 FPS)
|
|
11
|
+
│ ├── dialog.state # Dialog state (120 FPS)
|
|
12
|
+
│ ├── dialog2.state # Dialog state 2 (120 FPS - currently failing)
|
|
13
|
+
│ ├── after_dialog.state # After dialog state (30 FPS)
|
|
14
|
+
│ └── torchic.state # Additional test state
|
|
15
|
+
├── test_fps_adjustment_pytest.py # Main FPS adjustment tests
|
|
16
|
+
├── run_tests.py # Test runner script
|
|
17
|
+
└── README.md # This file
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Test States
|
|
21
|
+
|
|
22
|
+
### `simple_test.state`
|
|
23
|
+
- **Expected FPS**: 30
|
|
24
|
+
- **Description**: Normal overworld state without dialog
|
|
25
|
+
- **Purpose**: Verify base FPS is working correctly
|
|
26
|
+
|
|
27
|
+
### `dialog.state`
|
|
28
|
+
- **Expected FPS**: 120 (4x speedup)
|
|
29
|
+
- **Description**: State with active dialog
|
|
30
|
+
- **Purpose**: Verify dialog detection and FPS boost
|
|
31
|
+
|
|
32
|
+
### `dialog2.state`
|
|
33
|
+
- **Expected FPS**: 120 (4x speedup)
|
|
34
|
+
- **Description**: Another dialog state (currently not detected as dialog)
|
|
35
|
+
- **Purpose**: Test dialog detection robustness
|
|
36
|
+
- **Status**: Currently failing - needs investigation
|
|
37
|
+
|
|
38
|
+
### `after_dialog.state`
|
|
39
|
+
- **Expected FPS**: 30
|
|
40
|
+
- **Description**: State after dialog has ended
|
|
41
|
+
- **Purpose**: Verify FPS reverts to normal after dialog timeout
|
|
42
|
+
|
|
43
|
+
## Running Tests
|
|
44
|
+
|
|
45
|
+
### Using pytest (Recommended)
|
|
46
|
+
```bash
|
|
47
|
+
# Run all FPS adjustment tests
|
|
48
|
+
conda activate mgba && python -m pytest tests/test_fps_adjustment_pytest.py -v
|
|
49
|
+
|
|
50
|
+
# Run specific test
|
|
51
|
+
conda activate mgba && python -m pytest tests/test_fps_adjustment_pytest.py::test_fps_adjustment_summary -v
|
|
52
|
+
|
|
53
|
+
# Run all tests in the tests directory
|
|
54
|
+
conda activate mgba && python -m pytest tests/ -v
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Using the test runner
|
|
58
|
+
```bash
|
|
59
|
+
# Run all tests
|
|
60
|
+
conda activate mgba && python tests/run_tests.py
|
|
61
|
+
|
|
62
|
+
# Run specific test
|
|
63
|
+
conda activate mgba && python tests/run_tests.py test_fps_adjustment_pytest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Test Results
|
|
67
|
+
|
|
68
|
+
The FPS adjustment system should:
|
|
69
|
+
1. ✅ Run at 30 FPS in normal overworld state
|
|
70
|
+
2. ✅ Speed up to 120 FPS (4x) when dialog is detected
|
|
71
|
+
3. ❌ Speed up to 120 FPS for dialog2.state (currently failing)
|
|
72
|
+
4. ✅ Revert to 30 FPS when dialog ends
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- The `dialog2.state` test is expected to fail until the dialog detection logic is improved
|
|
77
|
+
- All state files are now centralized in `tests/states/` for better organization
|
|
78
|
+
- Tests use a timeout-based approach where dialog FPS runs for 5 seconds then reverts
|
|
File without changes
|