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,300 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test agent and emulator running directly without separate server process
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from pokemon_env.emulator import EmeraldEmulator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDirectAgentEmulator:
|
|
14
|
+
"""Test agent functionality by running emulator directly"""
|
|
15
|
+
|
|
16
|
+
@pytest.fixture(scope="class")
|
|
17
|
+
def output_dir(self):
|
|
18
|
+
"""Create output directory for test results"""
|
|
19
|
+
output_path = Path("test_outputs/direct_agent_maps")
|
|
20
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
return output_path
|
|
22
|
+
|
|
23
|
+
def format_map_for_comparison(self, tiles, title, location, position):
|
|
24
|
+
"""Format map tiles for comparison"""
|
|
25
|
+
if not tiles:
|
|
26
|
+
return f"=== {title} ===\nNo tiles available\n"
|
|
27
|
+
|
|
28
|
+
output = []
|
|
29
|
+
output.append(f"=== {title} ===")
|
|
30
|
+
output.append(f"Format: (MetatileID, Behavior, X, Y)")
|
|
31
|
+
output.append(f"Map dimensions: {len(tiles)}x{len(tiles[0]) if tiles else 0}")
|
|
32
|
+
output.append("")
|
|
33
|
+
output.append("--- TRAVERSABILITY MAP ---")
|
|
34
|
+
|
|
35
|
+
# Header with column numbers
|
|
36
|
+
header = " " + " ".join(f"{i:2}" for i in range(len(tiles[0]) if tiles else 0))
|
|
37
|
+
output.append(header)
|
|
38
|
+
output.append(" " + "-" * (len(header) - 4))
|
|
39
|
+
|
|
40
|
+
# Map rows
|
|
41
|
+
for row_idx, row in enumerate(tiles):
|
|
42
|
+
traversability_row = []
|
|
43
|
+
for col_idx, tile in enumerate(row):
|
|
44
|
+
if len(tile) >= 4:
|
|
45
|
+
tile_id, behavior, collision, elevation = tile
|
|
46
|
+
behavior_val = behavior if not hasattr(behavior, 'value') else behavior.value
|
|
47
|
+
|
|
48
|
+
# Convert to traversability symbol
|
|
49
|
+
if behavior_val == 0: # NORMAL
|
|
50
|
+
symbol = "." if collision == 0 else "#"
|
|
51
|
+
elif behavior_val == 1: # SECRET_BASE_WALL
|
|
52
|
+
symbol = "#"
|
|
53
|
+
elif behavior_val == 51: # IMPASSABLE_SOUTH
|
|
54
|
+
symbol = "IM"
|
|
55
|
+
elif behavior_val == 96: # NON_ANIMATED_DOOR
|
|
56
|
+
symbol = "D"
|
|
57
|
+
elif behavior_val == 101: # SOUTH_ARROW_WARP
|
|
58
|
+
symbol = "SO"
|
|
59
|
+
elif behavior_val == 105: # ANIMATED_DOOR
|
|
60
|
+
symbol = "D"
|
|
61
|
+
elif behavior_val == 134: # TELEVISION
|
|
62
|
+
symbol = "TE"
|
|
63
|
+
else:
|
|
64
|
+
symbol = "." # Default to walkable for other behaviors
|
|
65
|
+
|
|
66
|
+
# Mark player position
|
|
67
|
+
if position and len(position) >= 2:
|
|
68
|
+
# Player is at center of 15x15 map (position 7,7)
|
|
69
|
+
if row_idx == 7 and col_idx == 7:
|
|
70
|
+
symbol = "P"
|
|
71
|
+
|
|
72
|
+
traversability_row.append(symbol)
|
|
73
|
+
else:
|
|
74
|
+
traversability_row.append("?")
|
|
75
|
+
|
|
76
|
+
# Format row with row number
|
|
77
|
+
row_str = f"{row_idx:2}: " + " ".join(f"{symbol:1}" for symbol in traversability_row)
|
|
78
|
+
output.append(row_str)
|
|
79
|
+
|
|
80
|
+
return "\n".join(output)
|
|
81
|
+
|
|
82
|
+
def save_map_output(self, tiles, output_file, title, location, position):
|
|
83
|
+
"""Save map output to file"""
|
|
84
|
+
formatted_output = self.format_map_for_comparison(tiles, title, location, position)
|
|
85
|
+
|
|
86
|
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
87
|
+
with open(output_file, 'w') as f:
|
|
88
|
+
f.write(formatted_output)
|
|
89
|
+
|
|
90
|
+
return formatted_output
|
|
91
|
+
|
|
92
|
+
def test_direct_emulator_house_to_outside(self, output_dir):
|
|
93
|
+
"""Test direct emulator movement from house to outside"""
|
|
94
|
+
print("🏠➡️🌳 DIRECT EMULATOR: House to Outside Movement Test")
|
|
95
|
+
|
|
96
|
+
# Initialize emulator directly
|
|
97
|
+
rom_path = "Emerald-GBAdvance/rom.gba"
|
|
98
|
+
if not os.path.exists(rom_path):
|
|
99
|
+
pytest.skip(f"ROM not found at {rom_path}")
|
|
100
|
+
|
|
101
|
+
emulator = EmeraldEmulator(rom_path=rom_path, headless=True, sound=False)
|
|
102
|
+
emulator.initialize()
|
|
103
|
+
emulator.load_state('tests/states/house.state')
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# Get initial house map
|
|
107
|
+
print("\n1️⃣ Getting initial house map...")
|
|
108
|
+
house_state = emulator.get_comprehensive_state()
|
|
109
|
+
house_location = house_state['player']['location']
|
|
110
|
+
house_position = (house_state['player']['position']['x'], house_state['player']['position']['y'])
|
|
111
|
+
house_tiles = house_state['map']['tiles']
|
|
112
|
+
|
|
113
|
+
print(f"House state: {house_location} at {house_position}")
|
|
114
|
+
|
|
115
|
+
# Save house map
|
|
116
|
+
house_output_file = output_dir / "direct_emulator_house.txt"
|
|
117
|
+
house_content = self.save_map_output(
|
|
118
|
+
house_tiles, house_output_file,
|
|
119
|
+
f"Direct Emulator House - {house_location}", house_location, house_position
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Analyze house map
|
|
123
|
+
house_corruption = self.analyze_map_corruption(house_tiles)
|
|
124
|
+
print(f"House map: {house_corruption['total']} tiles, {house_corruption['im_count']} IM tiles")
|
|
125
|
+
|
|
126
|
+
# Move to outside area
|
|
127
|
+
print("\n2️⃣ Moving to outside area...")
|
|
128
|
+
moves_made = 0
|
|
129
|
+
max_moves = 8
|
|
130
|
+
|
|
131
|
+
for move_num in range(max_moves):
|
|
132
|
+
print(f"Move {move_num + 1}: Pressing DOWN")
|
|
133
|
+
emulator.press_buttons(['down'], hold_frames=25, release_frames=25)
|
|
134
|
+
time.sleep(0.1) # Small delay for transition
|
|
135
|
+
moves_made += 1
|
|
136
|
+
|
|
137
|
+
# Check if we've left the house
|
|
138
|
+
current_state = emulator.get_comprehensive_state()
|
|
139
|
+
current_location = current_state['player']['location']
|
|
140
|
+
current_position = (current_state['player']['position']['x'], current_state['player']['position']['y'])
|
|
141
|
+
|
|
142
|
+
print(f" Position: {current_position}, Location: {current_location}")
|
|
143
|
+
|
|
144
|
+
if "HOUSE" not in current_location:
|
|
145
|
+
print(f"✅ Reached outside area after {moves_made} moves!")
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
print(f"❌ Still in house after {max_moves} moves")
|
|
149
|
+
|
|
150
|
+
# Wait for any transition effects to complete
|
|
151
|
+
time.sleep(0.5)
|
|
152
|
+
|
|
153
|
+
# Get final outside map
|
|
154
|
+
print("\n3️⃣ Getting final outside map...")
|
|
155
|
+
outside_state = emulator.get_comprehensive_state()
|
|
156
|
+
outside_location = outside_state['player']['location']
|
|
157
|
+
outside_position = (outside_state['player']['position']['x'], outside_state['player']['position']['y'])
|
|
158
|
+
outside_tiles = outside_state['map']['tiles']
|
|
159
|
+
|
|
160
|
+
print(f"Outside state: {outside_location} at {outside_position}")
|
|
161
|
+
|
|
162
|
+
# Save outside map
|
|
163
|
+
outside_output_file = output_dir / "direct_emulator_outside.txt"
|
|
164
|
+
outside_content = self.save_map_output(
|
|
165
|
+
outside_tiles, outside_output_file,
|
|
166
|
+
f"Direct Emulator Outside - {outside_location}", outside_location, outside_position
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Analyze outside map
|
|
170
|
+
outside_corruption = self.analyze_map_corruption(outside_tiles)
|
|
171
|
+
print(f"Outside map: {outside_corruption['total']} tiles, {outside_corruption['im_count']} IM tiles")
|
|
172
|
+
|
|
173
|
+
print(f"\n4️⃣ Map Analysis:")
|
|
174
|
+
print(f"House map saved to: {house_output_file}")
|
|
175
|
+
print(f"Outside map saved to: {outside_output_file}")
|
|
176
|
+
|
|
177
|
+
# Test assertions
|
|
178
|
+
assert house_tiles is not None, "House map should exist"
|
|
179
|
+
assert outside_tiles is not None, "Outside map should exist"
|
|
180
|
+
assert len(house_tiles) == 15, "House map should be 15x15"
|
|
181
|
+
assert len(outside_tiles) == 15, "Outside map should be 15x15"
|
|
182
|
+
|
|
183
|
+
# Check that we actually moved to outside area
|
|
184
|
+
assert "HOUSE" not in outside_location, f"Should be outside, but location is: {outside_location}"
|
|
185
|
+
|
|
186
|
+
# Check for reasonable corruption levels
|
|
187
|
+
if outside_corruption['im_count'] > 50:
|
|
188
|
+
print(f"⚠️ WARNING: High corruption in outside map ({outside_corruption['im_count']} IM tiles)")
|
|
189
|
+
print("This indicates area transition detection may not be working properly")
|
|
190
|
+
else:
|
|
191
|
+
print(f"✅ Outside map looks clean ({outside_corruption['im_count']} IM tiles is acceptable)")
|
|
192
|
+
|
|
193
|
+
print("✅ DIRECT EMULATOR TEST PASSED!")
|
|
194
|
+
|
|
195
|
+
finally:
|
|
196
|
+
emulator.stop()
|
|
197
|
+
|
|
198
|
+
def analyze_map_corruption(self, tiles):
|
|
199
|
+
"""Analyze map for corruption (IM tiles)"""
|
|
200
|
+
if not tiles:
|
|
201
|
+
return {'total': 0, 'im_count': 0, 'corruption_ratio': 0.0}
|
|
202
|
+
|
|
203
|
+
total_tiles = sum(len(row) for row in tiles)
|
|
204
|
+
im_count = 0
|
|
205
|
+
behavior_distribution = {}
|
|
206
|
+
|
|
207
|
+
for row in tiles:
|
|
208
|
+
for tile in row:
|
|
209
|
+
if len(tile) >= 2:
|
|
210
|
+
behavior = tile[1].value if hasattr(tile[1], 'value') else tile[1]
|
|
211
|
+
behavior_distribution[behavior] = behavior_distribution.get(behavior, 0) + 1
|
|
212
|
+
|
|
213
|
+
if behavior == 51: # IMPASSABLE_SOUTH
|
|
214
|
+
im_count += 1
|
|
215
|
+
|
|
216
|
+
corruption_ratio = im_count / total_tiles if total_tiles > 0 else 0.0
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
'total': total_tiles,
|
|
220
|
+
'im_count': im_count,
|
|
221
|
+
'corruption_ratio': corruption_ratio,
|
|
222
|
+
'behavior_distribution': behavior_distribution
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def test_direct_agent_simulation(self, output_dir):
|
|
226
|
+
"""Test simulating agent decision-making with direct emulator access"""
|
|
227
|
+
print("🤖 DIRECT AGENT SIMULATION: Testing agent-like behavior")
|
|
228
|
+
|
|
229
|
+
rom_path = "Emerald-GBAdvance/rom.gba"
|
|
230
|
+
if not os.path.exists(rom_path):
|
|
231
|
+
pytest.skip(f"ROM not found at {rom_path}")
|
|
232
|
+
|
|
233
|
+
emulator = EmeraldEmulator(rom_path=rom_path, headless=True, sound=False)
|
|
234
|
+
emulator.initialize()
|
|
235
|
+
emulator.load_state('tests/states/house.state')
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
print("\n🎯 Goal: Navigate from house to outside using agent-like logic")
|
|
239
|
+
|
|
240
|
+
steps = 0
|
|
241
|
+
max_steps = 10
|
|
242
|
+
|
|
243
|
+
while steps < max_steps:
|
|
244
|
+
# Get current state (like agent perception)
|
|
245
|
+
current_state = emulator.get_comprehensive_state()
|
|
246
|
+
location = current_state['player']['location']
|
|
247
|
+
position = (current_state['player']['position']['x'], current_state['player']['position']['y'])
|
|
248
|
+
|
|
249
|
+
print(f"\nStep {steps + 1}: {location} at {position}")
|
|
250
|
+
|
|
251
|
+
# Simple agent logic: if in house, move down
|
|
252
|
+
if "HOUSE" in location:
|
|
253
|
+
print(" 🤖 Agent decision: In house, moving DOWN")
|
|
254
|
+
emulator.press_buttons(['down'], hold_frames=25, release_frames=25)
|
|
255
|
+
time.sleep(0.1)
|
|
256
|
+
else:
|
|
257
|
+
print(" 🎉 Agent goal achieved: Reached outside area!")
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
steps += 1
|
|
261
|
+
|
|
262
|
+
# Get final state for analysis
|
|
263
|
+
final_state = emulator.get_comprehensive_state()
|
|
264
|
+
final_location = final_state['player']['location']
|
|
265
|
+
final_position = (final_state['player']['position']['x'], final_state['player']['position']['y'])
|
|
266
|
+
final_tiles = final_state['map']['tiles']
|
|
267
|
+
|
|
268
|
+
print(f"\n📊 Final Result:")
|
|
269
|
+
print(f"Location: {final_location}")
|
|
270
|
+
print(f"Position: {final_position}")
|
|
271
|
+
print(f"Steps taken: {steps}")
|
|
272
|
+
|
|
273
|
+
# Save agent simulation result
|
|
274
|
+
agent_output_file = output_dir / "direct_agent_simulation.txt"
|
|
275
|
+
self.save_map_output(
|
|
276
|
+
final_tiles, agent_output_file,
|
|
277
|
+
f"Direct Agent Simulation - {final_location}", final_location, final_position
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
corruption = self.analyze_map_corruption(final_tiles)
|
|
281
|
+
print(f"Map quality: {corruption['total']} tiles, {corruption['im_count']} IM tiles")
|
|
282
|
+
print(f"Agent simulation saved to: {agent_output_file}")
|
|
283
|
+
|
|
284
|
+
# Test passed if agent successfully navigated
|
|
285
|
+
success = "HOUSE" not in final_location
|
|
286
|
+
if success:
|
|
287
|
+
print("✅ AGENT SIMULATION SUCCESSFUL!")
|
|
288
|
+
else:
|
|
289
|
+
print("❌ Agent failed to navigate out of house")
|
|
290
|
+
|
|
291
|
+
assert steps < max_steps, "Agent should complete navigation within step limit"
|
|
292
|
+
|
|
293
|
+
finally:
|
|
294
|
+
emulator.stop()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
# Allow running as script for manual testing
|
|
299
|
+
import sys
|
|
300
|
+
sys.exit(pytest.main([__file__, "-v", "-s"]))
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Pytest version of FPS adjustment system test for Pokemon Emerald emulator
|
|
4
|
+
|
|
5
|
+
This test verifies that the FPS adjustment system correctly:
|
|
6
|
+
1. Runs at 30 FPS in normal overworld state
|
|
7
|
+
2. Speeds up to 120 FPS (4x) when dialog is detected
|
|
8
|
+
3. Reverts to 30 FPS when dialog ends
|
|
9
|
+
|
|
10
|
+
Test States:
|
|
11
|
+
- Base overworld: Emerald-GBAdvance/simple_test.state (expected: 30 FPS)
|
|
12
|
+
- Dialog state: server/dialog.state (expected: 120 FPS)
|
|
13
|
+
- Dialog state 2: server/dialog2.state (expected: 120 FPS)
|
|
14
|
+
- After dialog: server/after_dialog.state (expected: 30 FPS)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
import subprocess
|
|
19
|
+
import time
|
|
20
|
+
import requests
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
# Test data
|
|
25
|
+
TEST_CASES = [
|
|
26
|
+
{
|
|
27
|
+
"state_file": "tests/states/simple_test.state",
|
|
28
|
+
"expected_fps": 30,
|
|
29
|
+
"test_name": "Base Overworld State"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"state_file": "tests/states/dialog.state",
|
|
33
|
+
"expected_fps": 120,
|
|
34
|
+
"test_name": "Dialog State"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"state_file": "tests/states/dialog2.state",
|
|
38
|
+
"expected_fps": 120,
|
|
39
|
+
"test_name": "Dialog State 2"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"state_file": "tests/states/after_dialog.state",
|
|
43
|
+
"expected_fps": 30,
|
|
44
|
+
"test_name": "After Dialog Ends"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
class ServerManager:
|
|
49
|
+
"""Manages server startup and shutdown for tests"""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
self.server_process = None
|
|
53
|
+
|
|
54
|
+
def start_server(self, state_file):
|
|
55
|
+
"""Start the server with a specific state file"""
|
|
56
|
+
print(f"🚀 Starting server with state: {state_file}")
|
|
57
|
+
cmd = ["python", "-m", "server.app", "--manual", "--load-state", state_file]
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
self.server_process = subprocess.Popen(
|
|
61
|
+
cmd,
|
|
62
|
+
stdout=subprocess.PIPE,
|
|
63
|
+
stderr=subprocess.PIPE
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Wait for server to start
|
|
67
|
+
print("⏳ Waiting for server to start...")
|
|
68
|
+
time.sleep(5)
|
|
69
|
+
|
|
70
|
+
# Test if server is responding
|
|
71
|
+
response = requests.get("http://localhost:8000/status", timeout=5)
|
|
72
|
+
if response.status_code == 200:
|
|
73
|
+
print("✅ Server started successfully")
|
|
74
|
+
return True
|
|
75
|
+
else:
|
|
76
|
+
print(f"❌ Server not responding: {response.status_code}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"❌ Failed to start server: {e}")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def stop_server(self):
|
|
84
|
+
"""Stop the server cleanly"""
|
|
85
|
+
if self.server_process:
|
|
86
|
+
print("🛑 Stopping server...")
|
|
87
|
+
try:
|
|
88
|
+
# Try graceful shutdown first
|
|
89
|
+
requests.post("http://localhost:8000/stop", timeout=2)
|
|
90
|
+
time.sleep(1)
|
|
91
|
+
except:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
# Force terminate if still running
|
|
95
|
+
try:
|
|
96
|
+
self.server_process.terminate()
|
|
97
|
+
self.server_process.wait(timeout=5)
|
|
98
|
+
print("✅ Server stopped gracefully")
|
|
99
|
+
except subprocess.TimeoutExpired:
|
|
100
|
+
print("⚠️ Server didn't stop gracefully, force killing...")
|
|
101
|
+
self.server_process.kill()
|
|
102
|
+
self.server_process.wait()
|
|
103
|
+
print("✅ Server force killed")
|
|
104
|
+
|
|
105
|
+
def check_fps(expected_fps, test_name):
|
|
106
|
+
"""Check if the current FPS matches the expected value"""
|
|
107
|
+
try:
|
|
108
|
+
response = requests.get("http://localhost:8000/status", timeout=5)
|
|
109
|
+
if response.status_code != 200:
|
|
110
|
+
print(f"❌ Server not responding: {response.status_code}")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
status = response.json()
|
|
114
|
+
base_fps = status.get('base_fps')
|
|
115
|
+
current_fps = status.get('current_fps')
|
|
116
|
+
is_dialog = status.get('is_dialog')
|
|
117
|
+
fps_multiplier = status.get('fps_multiplier')
|
|
118
|
+
|
|
119
|
+
print(f" Base FPS: {base_fps}")
|
|
120
|
+
print(f" Current FPS: {current_fps}")
|
|
121
|
+
print(f" Is Dialog: {is_dialog}")
|
|
122
|
+
print(f" FPS Multiplier: {fps_multiplier}")
|
|
123
|
+
|
|
124
|
+
if current_fps == expected_fps:
|
|
125
|
+
print(f"✅ {test_name}: {current_fps} FPS (expected: {expected_fps}) - PASS")
|
|
126
|
+
return True
|
|
127
|
+
else:
|
|
128
|
+
print(f"❌ {test_name}: {current_fps} FPS (expected: {expected_fps}) - FAIL")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"❌ Error checking FPS: {e}")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
136
|
+
def check_environment():
|
|
137
|
+
"""Check that we're in the right environment before running tests"""
|
|
138
|
+
# Check if we're in the right directory
|
|
139
|
+
if not os.path.exists("server/app.py"):
|
|
140
|
+
pytest.fail("❌ Error: This test must be run from the project root directory")
|
|
141
|
+
|
|
142
|
+
# Check if state files exist
|
|
143
|
+
required_files = [
|
|
144
|
+
"tests/states/simple_test.state",
|
|
145
|
+
"tests/states/dialog.state",
|
|
146
|
+
"tests/states/dialog2.state",
|
|
147
|
+
"tests/states/after_dialog.state"
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
missing_files = [f for f in required_files if not os.path.exists(f)]
|
|
151
|
+
if missing_files:
|
|
152
|
+
pytest.fail(f"❌ Error: Missing required state files: {missing_files}")
|
|
153
|
+
|
|
154
|
+
@pytest.mark.parametrize("test_case", TEST_CASES)
|
|
155
|
+
def test_fps_adjustment(test_case):
|
|
156
|
+
"""Test FPS adjustment for a specific state"""
|
|
157
|
+
state_file = test_case["state_file"]
|
|
158
|
+
expected_fps = test_case["expected_fps"]
|
|
159
|
+
test_name = test_case["test_name"]
|
|
160
|
+
|
|
161
|
+
print(f"\n🎮 Testing {test_name}")
|
|
162
|
+
print("=" * 50)
|
|
163
|
+
print(f"State file: {state_file}")
|
|
164
|
+
print(f"Expected FPS: {expected_fps}")
|
|
165
|
+
|
|
166
|
+
# Check if state file exists
|
|
167
|
+
if not os.path.exists(state_file):
|
|
168
|
+
pytest.fail(f"❌ State file not found: {state_file}")
|
|
169
|
+
|
|
170
|
+
# Start server
|
|
171
|
+
server = ServerManager()
|
|
172
|
+
if not server.start_server(state_file):
|
|
173
|
+
pytest.fail("Failed to start server")
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
# For after_dialog state, wait for the 5-second timeout to expire
|
|
177
|
+
if "after_dialog" in state_file:
|
|
178
|
+
print("⏳ Waiting 6 seconds for dialog FPS timeout to expire...")
|
|
179
|
+
time.sleep(6)
|
|
180
|
+
|
|
181
|
+
# Check FPS
|
|
182
|
+
result = check_fps(expected_fps, test_name)
|
|
183
|
+
|
|
184
|
+
# Assert the result
|
|
185
|
+
assert result, f"FPS check failed for {test_name}"
|
|
186
|
+
|
|
187
|
+
finally:
|
|
188
|
+
# Stop server
|
|
189
|
+
server.stop_server()
|
|
190
|
+
|
|
191
|
+
# Wait between tests
|
|
192
|
+
time.sleep(2)
|
|
193
|
+
|
|
194
|
+
def test_fps_adjustment_summary():
|
|
195
|
+
"""Test summary - this will run after all individual tests"""
|
|
196
|
+
print("\n📋 FPS Adjustment System Test Summary")
|
|
197
|
+
print("=" * 50)
|
|
198
|
+
print("This test verifies the FPS adjustment system:")
|
|
199
|
+
print("1. Base overworld state: 30 FPS")
|
|
200
|
+
print("2. Dialog state: 120 FPS (4x speedup)")
|
|
201
|
+
print("3. Dialog state 2: 120 FPS (4x speedup) - Currently failing, needs investigation")
|
|
202
|
+
print("4. After dialog ends: 30 FPS (reverted)")
|
|
203
|
+
print()
|
|
204
|
+
print("🎉 All individual FPS tests completed!")
|
|
205
|
+
print("Note: Dialog State 2 is expected to fail until the dialog detection is improved")
|