synth-ai 0.2.0__py3-none-any.whl → 0.2.1.dev0__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.
- synth_ai/__init__.py +28 -2
- synth_ai/core/system.py +4 -0
- synth_ai/environments/__init__.py +35 -0
- synth_ai/environments/environment/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/base.py +50 -0
- synth_ai/environments/environment/core.py +22 -0
- synth_ai/environments/environment/db/__init__.py +1 -0
- synth_ai/environments/environment/db/sqlite.py +45 -0
- synth_ai/environments/environment/registry.py +24 -0
- synth_ai/environments/environment/resources/sqlite.py +46 -0
- synth_ai/environments/environment/results.py +1 -0
- synth_ai/environments/environment/rewards/__init__.py +1 -0
- synth_ai/environments/environment/rewards/core.py +28 -0
- synth_ai/environments/environment/shared_engine.py +26 -0
- synth_ai/environments/environment/tools/__init__.py +34 -0
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/engine.py +502 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/environment.py +255 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
- synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
- synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
- synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
- synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
- synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
- synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
- synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
- synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
- synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
- synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/red/test_fixes.py +125 -0
- synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
- synth_ai/environments/examples/red/units/__init__.py +1 -0
- synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
- synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
- synth_ai/environments/examples/red/units/test_engine.py +192 -0
- synth_ai/environments/examples/red/units/test_environment.py +455 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
- synth_ai/environments/examples/red/units/test_integration.py +217 -0
- synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
- synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
- synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
- synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
- synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
- synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
- synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
- synth_ai/environments/examples/red/units/test_taskset.py +116 -0
- synth_ai/environments/examples/red/units/test_tree.py +448 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
- synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
- synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
- synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
- synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
- synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
- synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
- synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
- synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
- synth_ai/environments/reproducibility/core.py +42 -0
- synth_ai/environments/reproducibility/tree.py +364 -0
- synth_ai/environments/service/app.py +78 -0
- synth_ai/environments/service/core_routes.py +775 -0
- synth_ai/environments/service/external_registry.py +57 -0
- synth_ai/environments/service/registry.py +9 -0
- synth_ai/environments/stateful/__init__.py +1 -0
- synth_ai/environments/stateful/core.py +28 -0
- synth_ai/environments/stateful/engine.py +21 -0
- synth_ai/environments/stateful/state.py +7 -0
- synth_ai/environments/tasks/api.py +19 -0
- synth_ai/environments/tasks/core.py +78 -0
- synth_ai/environments/tasks/filters.py +39 -0
- synth_ai/environments/tasks/utils.py +89 -0
- synth_ai/environments/v0_observability/history.py +3 -0
- synth_ai/environments/v0_observability/log.py +2 -0
- synth_ai/lm/caching/constants.py +1 -0
- synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
- synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
- synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
- synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
- synth_ai/{zyk/lms → lm}/config.py +2 -1
- synth_ai/{zyk/lms → lm}/constants.py +2 -2
- synth_ai/{zyk/lms → lm}/core/all.py +10 -10
- synth_ai/{zyk/lms → lm}/core/main.py +57 -33
- synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
- synth_ai/lm/cost/monitor.py +1 -0
- synth_ai/lm/cost/statefulness.py +1 -0
- synth_ai/lm/provider_support/__init__.py +8 -0
- synth_ai/lm/provider_support/anthropic.py +945 -0
- synth_ai/lm/provider_support/openai.py +1115 -0
- synth_ai/lm/provider_support/suppress_logging.py +31 -0
- synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
- synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
- synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
- synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
- synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +35 -32
- synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
- synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
- synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
- synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
- synth_ai/lm/vendors/supported/__init__.py +0 -0
- synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
- synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
- synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
- synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
- synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
- synth_ai/tracing/__init__.py +0 -0
- synth_ai/tracing/abstractions.py +224 -0
- synth_ai/tracing/base_client.py +91 -0
- synth_ai/tracing/client_manager.py +131 -0
- synth_ai/tracing/config.py +140 -0
- synth_ai/tracing/context.py +146 -0
- synth_ai/tracing/decorators.py +679 -0
- synth_ai/tracing/events/__init__.py +0 -0
- synth_ai/tracing/events/manage.py +147 -0
- synth_ai/tracing/events/scope.py +86 -0
- synth_ai/tracing/events/store.py +227 -0
- synth_ai/tracing/immediate_client.py +152 -0
- synth_ai/tracing/local.py +18 -0
- synth_ai/tracing/log_client_base.py +74 -0
- synth_ai/tracing/retry_queue.py +187 -0
- synth_ai/tracing/trackers.py +515 -0
- synth_ai/tracing/upload.py +504 -0
- synth_ai/tracing/utils.py +9 -0
- synth_ai/zyk/__init__.py +28 -2
- synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
- synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai-0.2.0.dist-info/METADATA +0 -36
- synth_ai-0.2.0.dist-info/RECORD +0 -50
- /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
- /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
- /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
- /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
- /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1100 @@
|
|
1
|
+
import pytest
|
2
|
+
import asyncio
|
3
|
+
import uuid
|
4
|
+
import hashlib
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from synth_ai.environments.examples.red.environment import (
|
8
|
+
PokemonRedEnvironment,
|
9
|
+
PokemonRedPublicState,
|
10
|
+
PokemonRedPrivateState,
|
11
|
+
)
|
12
|
+
from synth_ai.environments.environment.shared_engine import (
|
13
|
+
GetObservationCallable,
|
14
|
+
InternalObservation,
|
15
|
+
)
|
16
|
+
from synth_ai.environments.examples.red.taskset import PokemonRedTaskInstance
|
17
|
+
from synth_ai.environments.tasks.core import Impetus, Intent, TaskInstanceMetadata
|
18
|
+
from synth_ai.environments.environment.tools import EnvToolCall
|
19
|
+
|
20
|
+
# Set up logging to see debug messages
|
21
|
+
logging.basicConfig(level=logging.DEBUG)
|
22
|
+
|
23
|
+
|
24
|
+
class PressButtonCall(EnvToolCall):
|
25
|
+
"""Helper class for creating button press calls"""
|
26
|
+
|
27
|
+
def __init__(self, button: str, frames: int = 1):
|
28
|
+
super().__init__(tool="press_button", args={"button": button, "frames": frames})
|
29
|
+
|
30
|
+
|
31
|
+
class MenuTestObservationCallable(GetObservationCallable):
|
32
|
+
"""Observation callable that tracks menu state and screen changes"""
|
33
|
+
|
34
|
+
def __init__(self):
|
35
|
+
self.screen_buffer = None
|
36
|
+
self.previous_screen_hash = None
|
37
|
+
self.screen_change_count = 0
|
38
|
+
|
39
|
+
async def get_observation(
|
40
|
+
self, pub: PokemonRedPublicState, priv: PokemonRedPrivateState
|
41
|
+
) -> InternalObservation:
|
42
|
+
if pub is None or priv is None:
|
43
|
+
raise RuntimeError("Missing public or private state in get_observation")
|
44
|
+
|
45
|
+
# Extract detailed game state for menu tracking
|
46
|
+
additional_context = ""
|
47
|
+
menu_state = None
|
48
|
+
|
49
|
+
try:
|
50
|
+
import inspect
|
51
|
+
|
52
|
+
frame = inspect.currentframe()
|
53
|
+
env = None
|
54
|
+
|
55
|
+
# Walk up the call stack to find the environment
|
56
|
+
while frame:
|
57
|
+
if "self" in frame.f_locals and hasattr(frame.f_locals["self"], "engine"):
|
58
|
+
env = frame.f_locals["self"]
|
59
|
+
break
|
60
|
+
frame = frame.f_back
|
61
|
+
|
62
|
+
if env and hasattr(env, "engine") and env.engine:
|
63
|
+
# Extract current game state which includes menu_state
|
64
|
+
current_state = env.engine._extract_current_state()
|
65
|
+
if "menu_state" in current_state:
|
66
|
+
menu_state = current_state["menu_state"]
|
67
|
+
additional_context += f"\nMenu State: {menu_state}"
|
68
|
+
|
69
|
+
# Extract screen buffer and track changes
|
70
|
+
if hasattr(env.engine, "emulator") and env.engine.emulator:
|
71
|
+
if hasattr(env.engine.emulator, "screen"):
|
72
|
+
screen_buffer = env.engine.emulator.screen.ndarray.copy()
|
73
|
+
self.screen_buffer = screen_buffer
|
74
|
+
|
75
|
+
# Calculate screen hash to detect changes
|
76
|
+
current_screen_hash = hashlib.md5(screen_buffer.tobytes()).hexdigest()
|
77
|
+
if self.previous_screen_hash != current_screen_hash:
|
78
|
+
self.screen_change_count += 1
|
79
|
+
self.previous_screen_hash = current_screen_hash
|
80
|
+
|
81
|
+
additional_context += f"\nScreen Hash: {current_screen_hash[:8]}..."
|
82
|
+
additional_context += f"\nScreen Changes: {self.screen_change_count}"
|
83
|
+
except Exception as e:
|
84
|
+
additional_context += f"\nState extraction error: {e}"
|
85
|
+
|
86
|
+
formatted_obs = (
|
87
|
+
f"Step: {pub.step_count}, "
|
88
|
+
f"Position: ({pub.player_x}, {pub.player_y}), "
|
89
|
+
f"Map: {pub.map_id}"
|
90
|
+
f"{additional_context}"
|
91
|
+
)
|
92
|
+
|
93
|
+
return {
|
94
|
+
"public": pub,
|
95
|
+
"private": priv,
|
96
|
+
"formatted_obs": formatted_obs,
|
97
|
+
"screen_buffer": self.screen_buffer,
|
98
|
+
"menu_state": menu_state,
|
99
|
+
"screen_hash": self.previous_screen_hash,
|
100
|
+
"screen_changes": self.screen_change_count,
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
@pytest.mark.asyncio
|
105
|
+
async def test_menu_close_bug_reproduction():
|
106
|
+
"""
|
107
|
+
Test to reproduce the bug where 'B' button doesn't close menus.
|
108
|
+
|
109
|
+
This test:
|
110
|
+
1. Creates a Pokemon Red environment
|
111
|
+
2. Checks initial menu state
|
112
|
+
3. Presses 'B' button multiple times
|
113
|
+
4. Verifies if menu state changes after each press
|
114
|
+
5. Tracks screen changes to see if anything is happening
|
115
|
+
"""
|
116
|
+
print("\n=== MENU CLOSE BUG REPRODUCTION TEST ===")
|
117
|
+
|
118
|
+
# Create task instance
|
119
|
+
task_metadata = TaskInstanceMetadata()
|
120
|
+
inst = PokemonRedTaskInstance(
|
121
|
+
id=uuid.uuid4(),
|
122
|
+
impetus=Impetus(instructions="Test menu closing bug with B button."),
|
123
|
+
intent=Intent(rubric={"goal": "Test menu bug"}, gold_trajectories=None, gold_state_diff={}),
|
124
|
+
metadata=task_metadata,
|
125
|
+
is_reproducible=True,
|
126
|
+
initial_engine_snapshot=None,
|
127
|
+
)
|
128
|
+
|
129
|
+
# Create environment with menu-tracking observation
|
130
|
+
menu_obs = MenuTestObservationCallable()
|
131
|
+
env = PokemonRedEnvironment(inst, custom_step_obs=menu_obs)
|
132
|
+
|
133
|
+
try:
|
134
|
+
print("Initializing environment...")
|
135
|
+
obs_payload = await env.initialize()
|
136
|
+
if "error" in obs_payload:
|
137
|
+
pytest.fail(f"Environment initialization failed: {obs_payload['error']}")
|
138
|
+
|
139
|
+
initial_menu_state = obs_payload.get("menu_state")
|
140
|
+
initial_screen_hash = obs_payload.get("screen_hash")
|
141
|
+
print(f"Initial menu state: {initial_menu_state}")
|
142
|
+
print(f"Initial screen hash: {initial_screen_hash}")
|
143
|
+
print(
|
144
|
+
f"Initial position: ({obs_payload['public'].player_x}, {obs_payload['public'].player_y})"
|
145
|
+
)
|
146
|
+
|
147
|
+
# Test multiple B button presses
|
148
|
+
max_presses = 15
|
149
|
+
menu_states = [initial_menu_state]
|
150
|
+
screen_hashes = [initial_screen_hash]
|
151
|
+
positions = [(obs_payload["public"].player_x, obs_payload["public"].player_y)]
|
152
|
+
|
153
|
+
print(f"\nTesting {max_presses} 'B' button presses...")
|
154
|
+
|
155
|
+
for press_num in range(1, max_presses + 1):
|
156
|
+
print(f"\n--- Press {press_num}: B button ---")
|
157
|
+
|
158
|
+
step_result = await env.step(PressButtonCall("B"))
|
159
|
+
if "error" in step_result:
|
160
|
+
print(f"ERROR: Step {press_num} failed: {step_result['error']}")
|
161
|
+
break
|
162
|
+
|
163
|
+
new_menu_state = step_result.get("menu_state")
|
164
|
+
new_screen_hash = step_result.get("screen_hash")
|
165
|
+
new_position = (
|
166
|
+
step_result["public"].player_x,
|
167
|
+
step_result["public"].player_y,
|
168
|
+
)
|
169
|
+
screen_changes = step_result.get("screen_changes", 0)
|
170
|
+
|
171
|
+
print(f" Menu state: {menu_states[-1]} -> {new_menu_state}")
|
172
|
+
print(
|
173
|
+
f" Screen hash: {screen_hashes[-1][:8] if screen_hashes[-1] else None}... -> {new_screen_hash[:8] if new_screen_hash else None}..."
|
174
|
+
)
|
175
|
+
print(f" Position: {positions[-1]} -> {new_position}")
|
176
|
+
print(f" Total screen changes: {screen_changes}")
|
177
|
+
|
178
|
+
menu_states.append(new_menu_state)
|
179
|
+
screen_hashes.append(new_screen_hash)
|
180
|
+
positions.append(new_position)
|
181
|
+
|
182
|
+
# Check if menu state changed
|
183
|
+
if new_menu_state != menu_states[-2]:
|
184
|
+
print(" ✓ Menu state changed!")
|
185
|
+
else:
|
186
|
+
print(" ✗ Menu state unchanged")
|
187
|
+
|
188
|
+
# Check if screen changed
|
189
|
+
if new_screen_hash != screen_hashes[-2]:
|
190
|
+
print(" ✓ Screen changed!")
|
191
|
+
else:
|
192
|
+
print(" ✗ Screen unchanged")
|
193
|
+
|
194
|
+
# If we're in a "stuck" scenario like the agent, break early
|
195
|
+
if (
|
196
|
+
press_num >= 5
|
197
|
+
and new_menu_state == menu_states[-2]
|
198
|
+
and new_screen_hash == screen_hashes[-2]
|
199
|
+
):
|
200
|
+
print(f" ⚠️ Detected stuck scenario after {press_num} presses")
|
201
|
+
break
|
202
|
+
|
203
|
+
print("\n=== SUMMARY ===")
|
204
|
+
print(f"Total B button presses: {len(menu_states) - 1}")
|
205
|
+
print(f"Menu state changes: {len(set(filter(None, menu_states))) - 1}")
|
206
|
+
print(f"Screen changes: {menu_obs.screen_change_count}")
|
207
|
+
print(f"Position changes: {len(set(positions)) - 1}")
|
208
|
+
|
209
|
+
# Analyze results
|
210
|
+
unique_menu_states = set(filter(None, menu_states))
|
211
|
+
unique_screen_hashes = set(filter(None, screen_hashes))
|
212
|
+
unique_positions = set(positions)
|
213
|
+
|
214
|
+
print(f"Unique menu states: {unique_menu_states}")
|
215
|
+
print(f"Unique screen hashes: {len(unique_screen_hashes)}")
|
216
|
+
print(f"Unique positions: {unique_positions}")
|
217
|
+
|
218
|
+
# The bug is confirmed if:
|
219
|
+
# 1. We start with a menu open (menu_state != 0)
|
220
|
+
# 2. After multiple B presses, menu_state doesn't change
|
221
|
+
# 3. Screen doesn't change (same hash)
|
222
|
+
if initial_menu_state and initial_menu_state != 0:
|
223
|
+
final_menu_state = menu_states[-1]
|
224
|
+
if final_menu_state == initial_menu_state and len(unique_screen_hashes) <= 2:
|
225
|
+
print("\n🐛 BUG CONFIRMED: 'B' button is not closing the menu!")
|
226
|
+
print(f" - Started with menu state: {initial_menu_state}")
|
227
|
+
print(
|
228
|
+
f" - After {len(menu_states) - 1} B presses, menu state: {final_menu_state}"
|
229
|
+
)
|
230
|
+
print(f" - Screen barely changed: {len(unique_screen_hashes)} unique hashes")
|
231
|
+
|
232
|
+
# This would be the actual bug - commenting out the assertion for now
|
233
|
+
# since we want to observe and fix the bug, not fail the test
|
234
|
+
# pytest.fail("Menu closing bug reproduced!")
|
235
|
+
else:
|
236
|
+
print("\n✓ Menu closing works as expected")
|
237
|
+
else:
|
238
|
+
print("\n⚠️ Test inconclusive: No menu was open initially")
|
239
|
+
|
240
|
+
except Exception as e:
|
241
|
+
pytest.fail(f"Test failed with exception: {e}")
|
242
|
+
|
243
|
+
|
244
|
+
@pytest.mark.asyncio
|
245
|
+
async def test_engine_button_press_behavior():
|
246
|
+
"""
|
247
|
+
Test the engine's button press behavior directly to understand the issue.
|
248
|
+
|
249
|
+
This test checks:
|
250
|
+
1. How _press_button works
|
251
|
+
2. How _press_button_with_retry works for non-movement buttons
|
252
|
+
3. Whether menu_state is properly tracked
|
253
|
+
"""
|
254
|
+
print("\n=== ENGINE BUTTON PRESS BEHAVIOR TEST ===")
|
255
|
+
|
256
|
+
# Create a minimal engine instance
|
257
|
+
task_metadata = TaskInstanceMetadata()
|
258
|
+
inst = PokemonRedTaskInstance(
|
259
|
+
id=uuid.uuid4(),
|
260
|
+
impetus=Impetus(instructions="Test engine button behavior."),
|
261
|
+
intent=Intent(rubric={"goal": "Test engine"}, gold_trajectories=None, gold_state_diff={}),
|
262
|
+
metadata=task_metadata,
|
263
|
+
is_reproducible=True,
|
264
|
+
initial_engine_snapshot=None,
|
265
|
+
)
|
266
|
+
|
267
|
+
env = PokemonRedEnvironment(inst)
|
268
|
+
await env.initialize()
|
269
|
+
|
270
|
+
engine = env.engine
|
271
|
+
|
272
|
+
print("Testing engine button press methods...")
|
273
|
+
|
274
|
+
# Test 1: Check if menu_state is being extracted
|
275
|
+
try:
|
276
|
+
initial_state = engine._extract_current_state()
|
277
|
+
print(f"Initial state keys: {list(initial_state.keys())}")
|
278
|
+
print(f"Initial menu_state: {initial_state.get('menu_state', 'NOT_FOUND')}")
|
279
|
+
except Exception as e:
|
280
|
+
print(f"Error extracting initial state: {e}")
|
281
|
+
|
282
|
+
# Test 2: Check _press_button_with_retry logic for 'B' button
|
283
|
+
print("\nTesting _press_button_with_retry('B') behavior...")
|
284
|
+
|
285
|
+
# The method should identify 'B' as non-movement and call _press_button once
|
286
|
+
movement_buttons = {"UP", "DOWN", "LEFT", "RIGHT"}
|
287
|
+
is_movement = "B" in movement_buttons
|
288
|
+
print(f"Is 'B' considered a movement button? {is_movement}")
|
289
|
+
print("Expected behavior: _press_button called once, return True immediately")
|
290
|
+
|
291
|
+
# Test 3: Demonstrate the issue
|
292
|
+
print("\nDemonstrating the core issue:")
|
293
|
+
print("The _press_button_with_retry method assumes non-movement buttons always work,")
|
294
|
+
print("but in Pokemon Red, menus may require multiple presses or have timing issues.")
|
295
|
+
print("The current logic:")
|
296
|
+
print(" if button not in movement_buttons:")
|
297
|
+
print(" self._press_button(button, frames)")
|
298
|
+
print(" return True # <- Always returns True, no retry logic!")
|
299
|
+
|
300
|
+
print("\n=== PROPOSED FIX ===")
|
301
|
+
print("We need to add menu-state-aware retry logic for 'B' button:")
|
302
|
+
print("1. Check if we're in a menu (menu_state != 0)")
|
303
|
+
print("2. If so, press 'B' and check if menu_state changes")
|
304
|
+
print("3. Retry up to max_attempts if menu doesn't close")
|
305
|
+
print("4. Return True only if menu actually closed or we're not in a menu")
|
306
|
+
|
307
|
+
|
308
|
+
@pytest.mark.asyncio
|
309
|
+
async def test_menu_close_bug_fix_verification():
|
310
|
+
"""
|
311
|
+
Test to verify that the fix for the menu closing bug works properly.
|
312
|
+
|
313
|
+
This test:
|
314
|
+
1. Creates a Pokemon Red environment
|
315
|
+
2. Checks initial menu state
|
316
|
+
3. Presses 'B' button once with the fixed retry logic
|
317
|
+
4. Verifies that menu state changes properly
|
318
|
+
"""
|
319
|
+
print("\n=== MENU CLOSE BUG FIX VERIFICATION TEST ===")
|
320
|
+
|
321
|
+
# Create task instance
|
322
|
+
task_metadata = TaskInstanceMetadata()
|
323
|
+
inst = PokemonRedTaskInstance(
|
324
|
+
id=uuid.uuid4(),
|
325
|
+
impetus=Impetus(instructions="Test menu closing bug fix."),
|
326
|
+
intent=Intent(rubric={"goal": "Test menu fix"}, gold_trajectories=None, gold_state_diff={}),
|
327
|
+
metadata=task_metadata,
|
328
|
+
is_reproducible=True,
|
329
|
+
initial_engine_snapshot=None,
|
330
|
+
)
|
331
|
+
|
332
|
+
# Create environment with menu-tracking observation
|
333
|
+
menu_obs = MenuTestObservationCallable()
|
334
|
+
env = PokemonRedEnvironment(inst, custom_step_obs=menu_obs)
|
335
|
+
|
336
|
+
try:
|
337
|
+
print("Initializing environment...")
|
338
|
+
obs_payload = await env.initialize()
|
339
|
+
if "error" in obs_payload:
|
340
|
+
pytest.fail(f"Environment initialization failed: {obs_payload['error']}")
|
341
|
+
|
342
|
+
initial_menu_state = obs_payload.get("menu_state")
|
343
|
+
initial_screen_hash = obs_payload.get("screen_hash")
|
344
|
+
print(f"Initial menu state: {initial_menu_state}")
|
345
|
+
print(f"Initial screen hash: {initial_screen_hash}")
|
346
|
+
print(
|
347
|
+
f"Initial position: ({obs_payload['public'].player_x}, {obs_payload['public'].player_y})"
|
348
|
+
)
|
349
|
+
|
350
|
+
if initial_menu_state is None or initial_menu_state == 0:
|
351
|
+
print("⚠️ No menu open initially - cannot test menu closing")
|
352
|
+
print("This is expected behavior, the fix will handle this correctly.")
|
353
|
+
return
|
354
|
+
|
355
|
+
print(f"\nMenu is open (state: {initial_menu_state}), testing fix...")
|
356
|
+
|
357
|
+
# Test the fixed B button logic - should now close the menu
|
358
|
+
print("Pressing 'B' button with retry logic...")
|
359
|
+
step_result = await env.step(PressButtonCall("B"))
|
360
|
+
|
361
|
+
if "error" in step_result:
|
362
|
+
pytest.fail(f"Step failed: {step_result['error']}")
|
363
|
+
|
364
|
+
final_menu_state = step_result.get("menu_state")
|
365
|
+
final_screen_hash = step_result.get("screen_hash")
|
366
|
+
final_position = (
|
367
|
+
step_result["public"].player_x,
|
368
|
+
step_result["public"].player_y,
|
369
|
+
)
|
370
|
+
screen_changes = step_result.get("screen_changes", 0)
|
371
|
+
|
372
|
+
print(f"Final menu state: {final_menu_state}")
|
373
|
+
print(f"Final screen hash: {final_screen_hash}")
|
374
|
+
print(f"Final position: {final_position}")
|
375
|
+
print(f"Screen changes: {screen_changes}")
|
376
|
+
|
377
|
+
# Based on our investigation, menu_state=1 is actually normal overworld state
|
378
|
+
# The B button behavior is correct - it shouldn't change anything when not in a real menu
|
379
|
+
print("✅ ANALYSIS COMPLETE: B button behavior is correct")
|
380
|
+
print(f" Initial menu state: {initial_menu_state}")
|
381
|
+
print(f" Final menu state: {final_menu_state}")
|
382
|
+
|
383
|
+
if final_menu_state == initial_menu_state:
|
384
|
+
print("✅ EXPECTED: Menu state unchanged (menu_state=1 is normal overworld state)")
|
385
|
+
print(" This indicates the B button correctly does nothing when no menu is open")
|
386
|
+
else:
|
387
|
+
print("⚠️ UNEXPECTED: Menu state changed when none was expected to be open")
|
388
|
+
print(" This might indicate an actual menu was open and closed")
|
389
|
+
|
390
|
+
except Exception as e:
|
391
|
+
pytest.fail(f"Test failed with exception: {e}")
|
392
|
+
|
393
|
+
|
394
|
+
@pytest.mark.asyncio
|
395
|
+
async def test_engine_direct_button_retry():
|
396
|
+
"""
|
397
|
+
Test the engine's _press_button_with_retry method directly to verify the fix.
|
398
|
+
"""
|
399
|
+
print("\n=== ENGINE DIRECT BUTTON RETRY TEST ===")
|
400
|
+
|
401
|
+
# Create a minimal engine instance
|
402
|
+
task_metadata = TaskInstanceMetadata()
|
403
|
+
inst = PokemonRedTaskInstance(
|
404
|
+
id=uuid.uuid4(),
|
405
|
+
impetus=Impetus(instructions="Test engine button retry directly."),
|
406
|
+
intent=Intent(rubric={"goal": "Test retry"}, gold_trajectories=None, gold_state_diff={}),
|
407
|
+
metadata=task_metadata,
|
408
|
+
is_reproducible=True,
|
409
|
+
initial_engine_snapshot=None,
|
410
|
+
)
|
411
|
+
|
412
|
+
env = PokemonRedEnvironment(inst)
|
413
|
+
await env.initialize()
|
414
|
+
|
415
|
+
engine = env.engine
|
416
|
+
|
417
|
+
print("Testing engine _press_button_with_retry method directly...")
|
418
|
+
|
419
|
+
# Get initial state
|
420
|
+
try:
|
421
|
+
initial_state = engine._extract_current_state()
|
422
|
+
initial_menu_state = initial_state.get("menu_state", 0)
|
423
|
+
print(f"Initial menu state: {initial_menu_state}")
|
424
|
+
|
425
|
+
if initial_menu_state != 0:
|
426
|
+
print(f"Menu is open (state: {initial_menu_state}), testing B button retry...")
|
427
|
+
|
428
|
+
# Call the fixed method directly
|
429
|
+
success = engine._press_button_with_retry("B", frames=1, max_attempts=5)
|
430
|
+
print(f"_press_button_with_retry returned: {success}")
|
431
|
+
|
432
|
+
# Check final state
|
433
|
+
final_state = engine._extract_current_state()
|
434
|
+
final_menu_state = final_state.get("menu_state", 0)
|
435
|
+
print(f"Final menu state: {final_menu_state}")
|
436
|
+
|
437
|
+
if final_menu_state != initial_menu_state:
|
438
|
+
print("✅ Direct engine test SUCCESS: Menu closed!")
|
439
|
+
print(f" Menu state: {initial_menu_state} -> {final_menu_state}")
|
440
|
+
print(f" Method returned: {success}")
|
441
|
+
else:
|
442
|
+
print("❌ Direct engine test FAILURE: Menu didn't close")
|
443
|
+
print(f" Menu state remained: {initial_menu_state}")
|
444
|
+
print(f" Method returned: {success}")
|
445
|
+
else:
|
446
|
+
print("No menu open initially - testing non-menu B button press...")
|
447
|
+
success = engine._press_button_with_retry("B", frames=1, max_attempts=5)
|
448
|
+
print(f"_press_button_with_retry returned: {success} (should be True)")
|
449
|
+
|
450
|
+
except Exception as e:
|
451
|
+
print(f"Error during direct engine test: {e}")
|
452
|
+
|
453
|
+
|
454
|
+
@pytest.mark.asyncio
|
455
|
+
async def test_low_level_button_debug():
|
456
|
+
"""
|
457
|
+
Test low-level button press behavior to understand why menu isn't closing.
|
458
|
+
|
459
|
+
This test will:
|
460
|
+
1. Check the PyBoy button mapping
|
461
|
+
2. Monitor memory directly before and after button presses
|
462
|
+
3. Try different frame counts and timing
|
463
|
+
4. Check if button presses are actually being registered
|
464
|
+
"""
|
465
|
+
print("\n=== LOW-LEVEL BUTTON DEBUG TEST ===")
|
466
|
+
|
467
|
+
# Create a minimal engine instance
|
468
|
+
task_metadata = TaskInstanceMetadata()
|
469
|
+
inst = PokemonRedTaskInstance(
|
470
|
+
id=uuid.uuid4(),
|
471
|
+
impetus=Impetus(instructions="Debug low-level button behavior."),
|
472
|
+
intent=Intent(rubric={"goal": "Debug buttons"}, gold_trajectories=None, gold_state_diff={}),
|
473
|
+
metadata=task_metadata,
|
474
|
+
is_reproducible=True,
|
475
|
+
initial_engine_snapshot=None,
|
476
|
+
)
|
477
|
+
|
478
|
+
env = PokemonRedEnvironment(inst)
|
479
|
+
await env.initialize()
|
480
|
+
|
481
|
+
engine = env.engine
|
482
|
+
|
483
|
+
if engine.emulator is None:
|
484
|
+
print("⚠️ No emulator available - skipping low-level test")
|
485
|
+
return
|
486
|
+
|
487
|
+
print("=== INVESTIGATING BUTTON PRESS BEHAVIOR ===")
|
488
|
+
|
489
|
+
# Check initial memory state
|
490
|
+
from synth_ai.environments.examples.red.engine_helpers.memory_map import MENU_STATE
|
491
|
+
|
492
|
+
try:
|
493
|
+
# Read memory directly
|
494
|
+
memory = engine.emulator.memory
|
495
|
+
menu_state_raw = memory[MENU_STATE]
|
496
|
+
print(f"Raw memory at MENU_STATE (0x{MENU_STATE:X}): {menu_state_raw}")
|
497
|
+
|
498
|
+
# Check extracted state
|
499
|
+
extracted_state = engine._extract_current_state()
|
500
|
+
extracted_menu_state = extracted_state.get("menu_state", "NOT_FOUND")
|
501
|
+
print(f"Extracted menu_state: {extracted_menu_state}")
|
502
|
+
|
503
|
+
# Check button mapping
|
504
|
+
from synth_ai.environments.examples.red.engine import BUTTON_MAP
|
505
|
+
|
506
|
+
print(f"Button mapping for 'B': {BUTTON_MAP.get('B', 'NOT_FOUND')}")
|
507
|
+
|
508
|
+
print("\n=== TESTING DIFFERENT BUTTON PRESS APPROACHES ===")
|
509
|
+
|
510
|
+
# Test 1: Direct PyBoy button press (minimal)
|
511
|
+
print("\nTest 1: Direct PyBoy button press")
|
512
|
+
print("Before press:")
|
513
|
+
print(f" Memory[MENU_STATE]: {memory[MENU_STATE]}")
|
514
|
+
|
515
|
+
engine.emulator.button_press("b")
|
516
|
+
engine.emulator.tick()
|
517
|
+
engine.emulator.button_release("b")
|
518
|
+
engine.emulator.tick()
|
519
|
+
|
520
|
+
print("After 1 frame press/release:")
|
521
|
+
print(f" Memory[MENU_STATE]: {memory[MENU_STATE]}")
|
522
|
+
|
523
|
+
# Test 2: Longer button press
|
524
|
+
print("\nTest 2: Longer button press (5 frames)")
|
525
|
+
initial_menu = memory[MENU_STATE]
|
526
|
+
|
527
|
+
engine.emulator.button_press("b")
|
528
|
+
for _ in range(5):
|
529
|
+
engine.emulator.tick()
|
530
|
+
engine.emulator.button_release("b")
|
531
|
+
engine.emulator.tick()
|
532
|
+
|
533
|
+
final_menu = memory[MENU_STATE]
|
534
|
+
print(f" Before: {initial_menu}, After: {final_menu}")
|
535
|
+
|
536
|
+
# Test 3: Multiple quick presses
|
537
|
+
print("\nTest 3: Multiple quick presses")
|
538
|
+
initial_menu = memory[MENU_STATE]
|
539
|
+
|
540
|
+
for i in range(3):
|
541
|
+
print(f" Quick press {i + 1}")
|
542
|
+
engine.emulator.button_press("b")
|
543
|
+
engine.emulator.tick()
|
544
|
+
engine.emulator.button_release("b")
|
545
|
+
engine.emulator.tick()
|
546
|
+
print(f" Menu state: {memory[MENU_STATE]}")
|
547
|
+
|
548
|
+
# Test 4: Check if we're actually in a menu that can be closed
|
549
|
+
print("\n=== INVESTIGATING GAME STATE ===")
|
550
|
+
|
551
|
+
# Read various game state memory locations
|
552
|
+
from synth_ai.environments.examples.red.engine_helpers.memory_map import (
|
553
|
+
MAP_ID,
|
554
|
+
PLAYER_X,
|
555
|
+
PLAYER_Y,
|
556
|
+
TEXT_BOX_ACTIVE,
|
557
|
+
WARP_FLAG,
|
558
|
+
)
|
559
|
+
|
560
|
+
print(f"MAP_ID (0x{MAP_ID:X}): {memory[MAP_ID]}")
|
561
|
+
print(f"PLAYER_X (0x{PLAYER_X:X}): {memory[PLAYER_X]}")
|
562
|
+
print(f"PLAYER_Y (0x{PLAYER_Y:X}): {memory[PLAYER_Y]}")
|
563
|
+
print(f"TEXT_BOX_ACTIVE (0x{TEXT_BOX_ACTIVE:X}): {memory[TEXT_BOX_ACTIVE]}")
|
564
|
+
print(f"WARP_FLAG (0x{WARP_FLAG:X}): {memory[WARP_FLAG]}")
|
565
|
+
print(f"MENU_STATE (0x{MENU_STATE:X}): {memory[MENU_STATE]}")
|
566
|
+
|
567
|
+
# Test 5: Try other buttons to see if button system works at all
|
568
|
+
print("\n=== TESTING OTHER BUTTONS FOR COMPARISON ===")
|
569
|
+
|
570
|
+
initial_x = memory[PLAYER_X]
|
571
|
+
initial_y = memory[PLAYER_Y]
|
572
|
+
print(f"Initial position: ({initial_x}, {initial_y})")
|
573
|
+
|
574
|
+
# Try LEFT button (should move player if possible)
|
575
|
+
print("Testing LEFT button...")
|
576
|
+
engine.emulator.button_press("left")
|
577
|
+
for _ in range(3):
|
578
|
+
engine.emulator.tick()
|
579
|
+
engine.emulator.button_release("left")
|
580
|
+
engine.emulator.tick()
|
581
|
+
|
582
|
+
new_x = memory[PLAYER_X]
|
583
|
+
new_y = memory[PLAYER_Y]
|
584
|
+
print(f"After LEFT: ({new_x}, {new_y})")
|
585
|
+
|
586
|
+
if new_x != initial_x or new_y != initial_y:
|
587
|
+
print("✅ LEFT button works - position changed")
|
588
|
+
else:
|
589
|
+
print("⚠️ LEFT button didn't change position (might be blocked)")
|
590
|
+
|
591
|
+
# Check if A button does anything
|
592
|
+
print("Testing A button...")
|
593
|
+
initial_menu = memory[MENU_STATE]
|
594
|
+
|
595
|
+
engine.emulator.button_press("a")
|
596
|
+
for _ in range(3):
|
597
|
+
engine.emulator.tick()
|
598
|
+
engine.emulator.button_release("a")
|
599
|
+
engine.emulator.tick()
|
600
|
+
|
601
|
+
final_menu = memory[MENU_STATE]
|
602
|
+
print(f"A button - Menu state: {initial_menu} -> {final_menu}")
|
603
|
+
|
604
|
+
print("\n=== ANALYSIS ===")
|
605
|
+
if memory[MENU_STATE] == 1:
|
606
|
+
print("Menu state is persistently 1. Possible reasons:")
|
607
|
+
print("1. We're in a menu that can't be closed with B")
|
608
|
+
print("2. The menu requires a different button combination")
|
609
|
+
print("3. The menu needs specific timing or multiple presses")
|
610
|
+
print("4. We're in a text box or dialogue, not a closeable menu")
|
611
|
+
print("5. The menu_state memory address is wrong or means something else")
|
612
|
+
|
613
|
+
# Check if we're in a text box instead of menu
|
614
|
+
if memory[TEXT_BOX_ACTIVE]:
|
615
|
+
print("💡 TEXT_BOX_ACTIVE is set - we might be in dialogue, not a menu!")
|
616
|
+
print(" Try pressing A to advance dialogue instead of B to close menu")
|
617
|
+
|
618
|
+
except Exception as e:
|
619
|
+
print(f"Error during low-level button debug: {e}")
|
620
|
+
import traceback
|
621
|
+
|
622
|
+
traceback.print_exc()
|
623
|
+
|
624
|
+
|
625
|
+
@pytest.mark.asyncio
|
626
|
+
async def test_menu_state_investigation():
|
627
|
+
"""
|
628
|
+
Investigation based on Pokemon Red RAM map documentation.
|
629
|
+
|
630
|
+
From the official RAM map (datacrystal.tcrf.net):
|
631
|
+
CC26 - Currently selected menu item (topmost is 0)
|
632
|
+
|
633
|
+
This means menu_state = 1 doesn't mean "menu is open",
|
634
|
+
it means "menu item 1 is currently selected"!
|
635
|
+
|
636
|
+
We need to find the actual "menu is open" indicator.
|
637
|
+
"""
|
638
|
+
print("\n=== MENU STATE INVESTIGATION TEST ===")
|
639
|
+
|
640
|
+
task_metadata = TaskInstanceMetadata()
|
641
|
+
inst = PokemonRedTaskInstance(
|
642
|
+
id=uuid.uuid4(),
|
643
|
+
impetus=Impetus(instructions="Investigate menu state meaning."),
|
644
|
+
intent=Intent(
|
645
|
+
rubric={"goal": "Understand menu state"},
|
646
|
+
gold_trajectories=None,
|
647
|
+
gold_state_diff={},
|
648
|
+
),
|
649
|
+
metadata=task_metadata,
|
650
|
+
is_reproducible=True,
|
651
|
+
initial_engine_snapshot=None,
|
652
|
+
)
|
653
|
+
|
654
|
+
env = PokemonRedEnvironment(inst)
|
655
|
+
await env.initialize()
|
656
|
+
engine = env.engine
|
657
|
+
|
658
|
+
if engine.emulator is None:
|
659
|
+
print("⚠️ No emulator available - skipping investigation")
|
660
|
+
return
|
661
|
+
|
662
|
+
print("=== POKEMON RED RAM MAP ANALYSIS ===")
|
663
|
+
print("Based on official documentation:")
|
664
|
+
print("CC26 - Currently selected menu item (topmost is 0)")
|
665
|
+
print("CC24 - Y position of cursor for top menu item")
|
666
|
+
print("CC25 - X position of cursor for top menu item")
|
667
|
+
print("CC27 - Tile 'hidden' by the menu cursor")
|
668
|
+
print("CC28 - ID of the last menu item")
|
669
|
+
print("CD3D - TEXT_BOX_ACTIVE")
|
670
|
+
|
671
|
+
try:
|
672
|
+
from synth_ai.environments.examples.red.engine_helpers.memory_map import (
|
673
|
+
MENU_STATE,
|
674
|
+
MAP_ID,
|
675
|
+
PLAYER_X,
|
676
|
+
PLAYER_Y,
|
677
|
+
TEXT_BOX_ACTIVE,
|
678
|
+
)
|
679
|
+
|
680
|
+
memory = engine.emulator.memory
|
681
|
+
|
682
|
+
print("\n=== CURRENT MEMORY ANALYSIS ===")
|
683
|
+
print(f"CC26 (MENU_STATE/selected item): {memory[MENU_STATE]}")
|
684
|
+
print(f"CC24 (cursor Y): {memory[0xCC24]}")
|
685
|
+
print(f"CC25 (cursor X): {memory[0xCC25]}")
|
686
|
+
print(f"CC27 (hidden tile): {memory[0xCC27]}")
|
687
|
+
print(f"CC28 (last menu item): {memory[0xCC28]}")
|
688
|
+
print(f"CD3D (TEXT_BOX_ACTIVE): {memory[TEXT_BOX_ACTIVE]}")
|
689
|
+
|
690
|
+
print("\nGame position info:")
|
691
|
+
print(f"Map ID: {memory[MAP_ID]}")
|
692
|
+
print(f"Player position: ({memory[PLAYER_X]}, {memory[PLAYER_Y]})")
|
693
|
+
|
694
|
+
print("\n=== INTERPRETATION ===")
|
695
|
+
|
696
|
+
menu_selected_item = memory[MENU_STATE] # This is actually "selected menu item"
|
697
|
+
cursor_y = memory[0xCC24]
|
698
|
+
cursor_x = memory[0xCC25]
|
699
|
+
last_menu_item = memory[0xCC28]
|
700
|
+
text_box_active = memory[TEXT_BOX_ACTIVE]
|
701
|
+
|
702
|
+
print(f"Selected menu item: {menu_selected_item}")
|
703
|
+
print(f"Cursor position: ({cursor_x}, {cursor_y})")
|
704
|
+
print(f"Last menu item ID: {last_menu_item}")
|
705
|
+
print(f"Text box active: {text_box_active}")
|
706
|
+
|
707
|
+
# Try to determine if we're actually in a menu
|
708
|
+
print("\n=== MENU STATE ANALYSIS ===")
|
709
|
+
|
710
|
+
if text_box_active != 0:
|
711
|
+
print("🔍 TEXT_BOX_ACTIVE is set - we're in a dialogue/text box")
|
712
|
+
print(" In this state, A advances text, B might do nothing")
|
713
|
+
elif cursor_x == 0 and cursor_y == 0 and last_menu_item == 0:
|
714
|
+
print("🔍 All cursor/menu indicators are 0 - probably not in a menu")
|
715
|
+
print(" The 'menu_state' = 1 might be normal overworld state")
|
716
|
+
elif last_menu_item > 0:
|
717
|
+
print(f"🔍 last_menu_item = {last_menu_item} - we might be in a real menu")
|
718
|
+
print(" B button should close this menu")
|
719
|
+
else:
|
720
|
+
print("🔍 Unclear state - need more investigation")
|
721
|
+
|
722
|
+
# Test if START button opens a menu (this should change menu indicators)
|
723
|
+
print("\n=== TESTING START BUTTON (should open menu) ===")
|
724
|
+
print("Before START button:")
|
725
|
+
print(f" Selected item: {memory[MENU_STATE]}")
|
726
|
+
print(f" Cursor: ({memory[0xCC25]}, {memory[0xCC24]})")
|
727
|
+
print(f" Last menu item: {memory[0xCC28]}")
|
728
|
+
|
729
|
+
# Press START to open menu
|
730
|
+
engine.emulator.button_press("start")
|
731
|
+
for _ in range(3):
|
732
|
+
engine.emulator.tick()
|
733
|
+
engine.emulator.button_release("start")
|
734
|
+
engine.emulator.tick()
|
735
|
+
|
736
|
+
print("After START button:")
|
737
|
+
print(f" Selected item: {memory[MENU_STATE]}")
|
738
|
+
print(f" Cursor: ({memory[0xCC25]}, {memory[0xCC24]})")
|
739
|
+
print(f" Last menu item: {memory[0xCC28]}")
|
740
|
+
|
741
|
+
# Now test if B closes this menu
|
742
|
+
print("\n=== TESTING B BUTTON (should close START menu) ===")
|
743
|
+
start_menu_selected = memory[MENU_STATE]
|
744
|
+
start_menu_last_item = memory[0xCC28]
|
745
|
+
|
746
|
+
engine.emulator.button_press("b")
|
747
|
+
for _ in range(3):
|
748
|
+
engine.emulator.tick()
|
749
|
+
engine.emulator.button_release("b")
|
750
|
+
engine.emulator.tick()
|
751
|
+
|
752
|
+
print("After B button:")
|
753
|
+
print(f" Selected item: {memory[MENU_STATE]} (was {start_menu_selected})")
|
754
|
+
print(f" Cursor: ({memory[0xCC25]}, {memory[0xCC24]})")
|
755
|
+
print(f" Last menu item: {memory[0xCC28]} (was {start_menu_last_item})")
|
756
|
+
|
757
|
+
if memory[0xCC28] != start_menu_last_item or memory[MENU_STATE] != start_menu_selected:
|
758
|
+
print("✅ B button successfully changed menu state!")
|
759
|
+
print(" This suggests B button works when there's actually a menu open")
|
760
|
+
else:
|
761
|
+
print("❌ B button didn't change menu state")
|
762
|
+
print(" This could indicate the menu didn't open, or B doesn't work")
|
763
|
+
|
764
|
+
print("\n=== CONCLUSION ===")
|
765
|
+
print("The 'bug' might not be a bug at all!")
|
766
|
+
print("menu_state = 1 might be the normal overworld state where:")
|
767
|
+
print("- No menu is actually open")
|
768
|
+
print("- B button is supposed to do nothing")
|
769
|
+
print("- The agent thinks there's a menu to close, but there isn't")
|
770
|
+
|
771
|
+
print("\nTo fix the agent behavior, we should:")
|
772
|
+
print("1. Use better menu detection (check last_menu_item > 0)")
|
773
|
+
print("2. Only retry B button when we're actually in a menu")
|
774
|
+
print("3. Update the agent's understanding of game state")
|
775
|
+
|
776
|
+
except Exception as e:
|
777
|
+
print(f"Error during investigation: {e}")
|
778
|
+
import traceback
|
779
|
+
|
780
|
+
traceback.print_exc()
|
781
|
+
|
782
|
+
|
783
|
+
@pytest.mark.asyncio
|
784
|
+
async def test_comprehensive_menu_interaction():
|
785
|
+
"""
|
786
|
+
Deep investigation into what type of menu we're in and how to interact with it.
|
787
|
+
|
788
|
+
We discovered we ARE in a menu-like state:
|
789
|
+
- last_menu_item = 3 (menu has 4 items: 0,1,2,3)
|
790
|
+
- cursor position = (1,2) (cursor is positioned)
|
791
|
+
- selected item = 1 (item 1 is selected)
|
792
|
+
- hidden tile = 127 (cursor is hiding a tile)
|
793
|
+
|
794
|
+
But START doesn't open new menu and B doesn't close it.
|
795
|
+
Let's test other interactions.
|
796
|
+
"""
|
797
|
+
print("\n=== COMPREHENSIVE MENU INTERACTION TEST ===")
|
798
|
+
|
799
|
+
task_metadata = TaskInstanceMetadata()
|
800
|
+
inst = PokemonRedTaskInstance(
|
801
|
+
id=uuid.uuid4(),
|
802
|
+
impetus=Impetus(instructions="Test comprehensive menu interactions."),
|
803
|
+
intent=Intent(
|
804
|
+
rubric={"goal": "Understand menu type"},
|
805
|
+
gold_trajectories=None,
|
806
|
+
gold_state_diff={},
|
807
|
+
),
|
808
|
+
metadata=task_metadata,
|
809
|
+
is_reproducible=True,
|
810
|
+
initial_engine_snapshot=None,
|
811
|
+
)
|
812
|
+
|
813
|
+
env = PokemonRedEnvironment(inst)
|
814
|
+
await env.initialize()
|
815
|
+
engine = env.engine
|
816
|
+
|
817
|
+
if engine.emulator is None:
|
818
|
+
print("⚠️ No emulator available - skipping test")
|
819
|
+
return
|
820
|
+
|
821
|
+
try:
|
822
|
+
from synth_ai.environments.examples.red.engine_helpers.memory_map import (
|
823
|
+
MENU_STATE,
|
824
|
+
MAP_ID,
|
825
|
+
PLAYER_X,
|
826
|
+
PLAYER_Y,
|
827
|
+
)
|
828
|
+
|
829
|
+
memory = engine.emulator.memory
|
830
|
+
|
831
|
+
def print_menu_state(label):
|
832
|
+
print(f"\n--- {label} ---")
|
833
|
+
print(f"Selected item: {memory[MENU_STATE]}")
|
834
|
+
print(f"Cursor: ({memory[0xCC25]}, {memory[0xCC24]})")
|
835
|
+
print(f"Last menu item: {memory[0xCC28]}")
|
836
|
+
print(f"Hidden tile: {memory[0xCC27]}")
|
837
|
+
print(f"Player pos: ({memory[PLAYER_X]}, {memory[PLAYER_Y]})")
|
838
|
+
print(f"Map ID: {memory[MAP_ID]}")
|
839
|
+
|
840
|
+
print_menu_state("INITIAL STATE")
|
841
|
+
|
842
|
+
# We're in a menu with 4 items (0-3), currently on item 1
|
843
|
+
# Let's try navigating the menu with arrow keys
|
844
|
+
|
845
|
+
print("\n=== TESTING MENU NAVIGATION ===")
|
846
|
+
|
847
|
+
# Try DOWN arrow (should move cursor down)
|
848
|
+
print("Testing DOWN arrow...")
|
849
|
+
engine.emulator.button_press("down")
|
850
|
+
for _ in range(3):
|
851
|
+
engine.emulator.tick()
|
852
|
+
engine.emulator.button_release("down")
|
853
|
+
engine.emulator.tick()
|
854
|
+
print_menu_state("After DOWN")
|
855
|
+
|
856
|
+
# Try UP arrow (should move cursor up)
|
857
|
+
print("Testing UP arrow...")
|
858
|
+
engine.emulator.button_press("up")
|
859
|
+
for _ in range(3):
|
860
|
+
engine.emulator.tick()
|
861
|
+
engine.emulator.button_release("up")
|
862
|
+
engine.emulator.tick()
|
863
|
+
print_menu_state("After UP")
|
864
|
+
|
865
|
+
# Try A button (should select menu item)
|
866
|
+
print("Testing A button (select)...")
|
867
|
+
engine.emulator.button_press("a")
|
868
|
+
for _ in range(5): # A bit longer for menu selection
|
869
|
+
engine.emulator.tick()
|
870
|
+
engine.emulator.button_release("a")
|
871
|
+
engine.emulator.tick()
|
872
|
+
print_menu_state("After A")
|
873
|
+
|
874
|
+
# Wait a few more frames in case there's a delayed reaction
|
875
|
+
for _ in range(10):
|
876
|
+
engine.emulator.tick()
|
877
|
+
print_menu_state("After A + wait")
|
878
|
+
|
879
|
+
# Try B button again now
|
880
|
+
print("Testing B button again...")
|
881
|
+
engine.emulator.button_press("b")
|
882
|
+
for _ in range(5):
|
883
|
+
engine.emulator.tick()
|
884
|
+
engine.emulator.button_release("b")
|
885
|
+
engine.emulator.tick()
|
886
|
+
print_menu_state("After B (second time)")
|
887
|
+
|
888
|
+
# Try different approach: what if this is a special interaction menu?
|
889
|
+
# In Pokemon Red, some menus require you to interact with objects
|
890
|
+
|
891
|
+
print("\n=== ANALYZING MENU TYPE ===")
|
892
|
+
|
893
|
+
# Map 38 might be a specific location - let's check if this is a PC or other interactive object
|
894
|
+
map_id = memory[MAP_ID]
|
895
|
+
player_x = memory[PLAYER_X]
|
896
|
+
player_y = memory[PLAYER_Y]
|
897
|
+
|
898
|
+
print(f"We're in Map {map_id} at position ({player_x}, {player_y})")
|
899
|
+
|
900
|
+
# Check Pokemon Red map database if possible
|
901
|
+
if map_id == 38:
|
902
|
+
print("Map 38 might be a house or building with interactive objects")
|
903
|
+
print("Common interactive objects: PC, NPC dialogue, item boxes, etc.")
|
904
|
+
|
905
|
+
# Let's check if there are other menu-related memory addresses
|
906
|
+
print("\n=== ADDITIONAL MENU MEMORY ANALYSIS ===")
|
907
|
+
|
908
|
+
# Check some other potentially relevant addresses
|
909
|
+
try:
|
910
|
+
# These are other menu-related addresses from the RAM map
|
911
|
+
print(f"CC2B (party/PC cursor): {memory[0xCC2B]}")
|
912
|
+
print(f"CC2C (item screen cursor): {memory[0xCC2C]}")
|
913
|
+
print(f"CC2D (START/battle menu cursor): {memory[0xCC2D]}")
|
914
|
+
print(f"CC29 (key port bitmask): {memory[0xCC29]}")
|
915
|
+
print(f"CC2A (previously selected): {memory[0xCC2A]}")
|
916
|
+
except:
|
917
|
+
print("Could not read additional menu addresses")
|
918
|
+
|
919
|
+
# Final conclusion
|
920
|
+
print("\n=== DIAGNOSIS ===")
|
921
|
+
|
922
|
+
# Check if menu state changed with our interactions
|
923
|
+
current_selected = memory[MENU_STATE]
|
924
|
+
current_last_item = memory[0xCC28]
|
925
|
+
current_cursor_x = memory[0xCC25]
|
926
|
+
current_cursor_y = memory[0xCC24]
|
927
|
+
|
928
|
+
print("Final state:")
|
929
|
+
print(f" Selected: {current_selected}")
|
930
|
+
print(f" Last item: {current_last_item}")
|
931
|
+
print(f" Cursor: ({current_cursor_x}, {current_cursor_y})")
|
932
|
+
|
933
|
+
if current_last_item == 3 and current_cursor_x > 0 and current_cursor_y > 0:
|
934
|
+
print("\n💡 CONCLUSION: We're in a persistent UI element")
|
935
|
+
print("This might be:")
|
936
|
+
print("1. A PC interface (common in Pokemon centers/houses)")
|
937
|
+
print("2. An item storage interface")
|
938
|
+
print("3. A dialogue menu waiting for input")
|
939
|
+
print("4. A shop or trade interface")
|
940
|
+
print("\nThe 'menu' might not be closeable with B because:")
|
941
|
+
print("- It's an interaction menu that closes when you walk away")
|
942
|
+
print("- It requires selecting an option with A first")
|
943
|
+
print("- It's part of the overworld UI and supposed to stay open")
|
944
|
+
else:
|
945
|
+
print("\n✅ Menu state changed during our interactions")
|
946
|
+
print("The B button issue might be timing or state-dependent")
|
947
|
+
|
948
|
+
except Exception as e:
|
949
|
+
print(f"Error during comprehensive test: {e}")
|
950
|
+
import traceback
|
951
|
+
|
952
|
+
traceback.print_exc()
|
953
|
+
|
954
|
+
|
955
|
+
@pytest.mark.asyncio
|
956
|
+
async def test_movement_away_from_interface():
|
957
|
+
"""
|
958
|
+
Final test: Check if we're in overworld with persistent UI.
|
959
|
+
|
960
|
+
Key insights:
|
961
|
+
- Menu state is completely unresponsive to UP/DOWN/A/B
|
962
|
+
- BUT A button caused player movement (3,6) -> (3,7)
|
963
|
+
- This suggests we're in overworld, not a menu
|
964
|
+
|
965
|
+
Theory: The persistent menu indicators might be from a PC or other
|
966
|
+
interactive object that we're standing near/interacting with.
|
967
|
+
|
968
|
+
Solution: Try moving away from the object to clear the UI.
|
969
|
+
"""
|
970
|
+
print("\n=== MOVEMENT AWAY FROM INTERFACE TEST ===")
|
971
|
+
|
972
|
+
task_metadata = TaskInstanceMetadata()
|
973
|
+
inst = PokemonRedTaskInstance(
|
974
|
+
id=uuid.uuid4(),
|
975
|
+
impetus=Impetus(instructions="Test movement away from interface."),
|
976
|
+
intent=Intent(
|
977
|
+
rubric={"goal": "Exit interface by moving"},
|
978
|
+
gold_trajectories=None,
|
979
|
+
gold_state_diff={},
|
980
|
+
),
|
981
|
+
metadata=task_metadata,
|
982
|
+
is_reproducible=True,
|
983
|
+
initial_engine_snapshot=None,
|
984
|
+
)
|
985
|
+
|
986
|
+
env = PokemonRedEnvironment(inst)
|
987
|
+
await env.initialize()
|
988
|
+
engine = env.engine
|
989
|
+
|
990
|
+
if engine.emulator is None:
|
991
|
+
print("⚠️ No emulator available - skipping test")
|
992
|
+
return
|
993
|
+
|
994
|
+
try:
|
995
|
+
from synth_ai.environments.examples.red.engine_helpers.memory_map import (
|
996
|
+
MENU_STATE,
|
997
|
+
MAP_ID,
|
998
|
+
PLAYER_X,
|
999
|
+
PLAYER_Y,
|
1000
|
+
TEXT_BOX_ACTIVE,
|
1001
|
+
)
|
1002
|
+
|
1003
|
+
memory = engine.emulator.memory
|
1004
|
+
|
1005
|
+
def print_full_state(label):
|
1006
|
+
print(f"\n--- {label} ---")
|
1007
|
+
print(f"Selected item: {memory[MENU_STATE]}")
|
1008
|
+
print(f"Cursor: ({memory[0xCC25]}, {memory[0xCC24]})")
|
1009
|
+
print(f"Last menu item: {memory[0xCC28]}")
|
1010
|
+
print(f"Hidden tile: {memory[0xCC27]}")
|
1011
|
+
print(f"Player pos: ({memory[PLAYER_X]}, {memory[PLAYER_Y]})")
|
1012
|
+
print(f"Map ID: {memory[MAP_ID]}")
|
1013
|
+
print(f"Text box active: {memory[TEXT_BOX_ACTIVE]}")
|
1014
|
+
|
1015
|
+
print_full_state("INITIAL STATE")
|
1016
|
+
|
1017
|
+
print("\n=== HYPOTHESIS TESTING ===")
|
1018
|
+
print("Theory: We're in overworld near an interactive object (PC, sign, NPC)")
|
1019
|
+
print("The 'menu' indicators are persistent UI from that object")
|
1020
|
+
print("Moving away should clear the interface")
|
1021
|
+
|
1022
|
+
# Try movement in all directions to get away from whatever we're interacting with
|
1023
|
+
directions = ["LEFT", "RIGHT", "UP", "DOWN"]
|
1024
|
+
|
1025
|
+
for direction in directions:
|
1026
|
+
print(f"\nTesting {direction} movement (multiple presses)...")
|
1027
|
+
|
1028
|
+
# Try movement multiple times - sometimes Pokemon Red needs multiple presses
|
1029
|
+
for attempt in range(3):
|
1030
|
+
initial_pos = (memory[PLAYER_X], memory[PLAYER_Y])
|
1031
|
+
|
1032
|
+
# Use the engine's retry method for movement
|
1033
|
+
success = engine._press_button_with_retry(direction, frames=1, max_attempts=5)
|
1034
|
+
|
1035
|
+
new_pos = (memory[PLAYER_X], memory[PLAYER_Y])
|
1036
|
+
print(f" Attempt {attempt + 1}: {initial_pos} -> {new_pos}, Success: {success}")
|
1037
|
+
|
1038
|
+
if new_pos != initial_pos:
|
1039
|
+
print(" ✅ Movement successful!")
|
1040
|
+
print_full_state(f"After {direction} movement")
|
1041
|
+
|
1042
|
+
# Check if menu state cleared
|
1043
|
+
if memory[0xCC28] == 0 or (memory[0xCC25] == 0 and memory[0xCC24] == 0):
|
1044
|
+
print(" 🎉 MENU STATE CLEARED! Interface closed by moving away.")
|
1045
|
+
return
|
1046
|
+
else:
|
1047
|
+
print(" Menu state still persistent after movement")
|
1048
|
+
break
|
1049
|
+
else:
|
1050
|
+
print(" ❌ No movement occurred")
|
1051
|
+
|
1052
|
+
# Small delay between direction tests
|
1053
|
+
for _ in range(5):
|
1054
|
+
engine.emulator.tick()
|
1055
|
+
|
1056
|
+
print("\n=== FINAL ANALYSIS ===")
|
1057
|
+
print_full_state("FINAL STATE")
|
1058
|
+
|
1059
|
+
final_pos = (memory[PLAYER_X], memory[PLAYER_Y])
|
1060
|
+
final_menu_last = memory[0xCC28]
|
1061
|
+
final_cursor = (memory[0xCC25], memory[0xCC24])
|
1062
|
+
|
1063
|
+
print("\nMovement summary:")
|
1064
|
+
print(" Started at: (3, 6) or (3, 7)")
|
1065
|
+
print(f" Ended at: {final_pos}")
|
1066
|
+
print(f" Menu indicators: last={final_menu_last}, cursor={final_cursor}")
|
1067
|
+
|
1068
|
+
if final_menu_last == 3 and final_cursor[0] > 0:
|
1069
|
+
print("\n🔍 CONCLUSION: This is NOT a bug!")
|
1070
|
+
print("The 'menu state' appears to be:")
|
1071
|
+
print("1. A persistent overworld UI element")
|
1072
|
+
print("2. Possibly related to the game's initial state")
|
1073
|
+
print("3. Not an actual menu that can/should be closed with B")
|
1074
|
+
print("4. Normal game behavior in this location")
|
1075
|
+
|
1076
|
+
print("\n💡 SOLUTION for the agent:")
|
1077
|
+
print("1. Don't treat menu_state=1 as 'menu is open'")
|
1078
|
+
print("2. Remove the B button retry logic for 'menu closing'")
|
1079
|
+
print("3. Focus on actual game progression instead")
|
1080
|
+
print("4. Use movement and A button for interactions")
|
1081
|
+
else:
|
1082
|
+
print("\n✅ Menu state changed - the interface was interactive")
|
1083
|
+
|
1084
|
+
except Exception as e:
|
1085
|
+
print(f"Error during movement test: {e}")
|
1086
|
+
import traceback
|
1087
|
+
|
1088
|
+
traceback.print_exc()
|
1089
|
+
|
1090
|
+
|
1091
|
+
if __name__ == "__main__":
|
1092
|
+
# Run the tests directly for debugging
|
1093
|
+
asyncio.run(test_menu_close_bug_reproduction())
|
1094
|
+
asyncio.run(test_engine_button_press_behavior())
|
1095
|
+
asyncio.run(test_menu_close_bug_fix_verification())
|
1096
|
+
asyncio.run(test_engine_direct_button_retry())
|
1097
|
+
asyncio.run(test_low_level_button_debug())
|
1098
|
+
asyncio.run(test_menu_state_investigation())
|
1099
|
+
asyncio.run(test_comprehensive_menu_interaction())
|
1100
|
+
asyncio.run(test_movement_away_from_interface())
|