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
examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Singleton manager for MapStitcher to ensure all components use the same instance.
|
|
4
|
+
This prevents multiple MapStitcher instances from being created and ensures
|
|
5
|
+
consistent map data across the application.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from utils.map_stitcher import MapStitcher
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# The single global MapStitcher instance
|
|
14
|
+
_map_stitcher_instance = None
|
|
15
|
+
|
|
16
|
+
def get_instance():
|
|
17
|
+
"""Get the singleton MapStitcher instance."""
|
|
18
|
+
global _map_stitcher_instance
|
|
19
|
+
if _map_stitcher_instance is None:
|
|
20
|
+
_map_stitcher_instance = MapStitcher()
|
|
21
|
+
logger.info(f"Created singleton MapStitcher with {len(_map_stitcher_instance.map_areas)} areas")
|
|
22
|
+
elif len(_map_stitcher_instance.map_areas) == 0:
|
|
23
|
+
# If we have no data, reload from cache file in case it was updated
|
|
24
|
+
_map_stitcher_instance.load_from_file()
|
|
25
|
+
if len(_map_stitcher_instance.map_areas) > 0:
|
|
26
|
+
logger.info(f"Reloaded MapStitcher from cache, now has {len(_map_stitcher_instance.map_areas)} areas")
|
|
27
|
+
return _map_stitcher_instance
|
|
28
|
+
|
|
29
|
+
def reset_instance():
|
|
30
|
+
"""Reset the singleton instance (mainly for testing)."""
|
|
31
|
+
global _map_stitcher_instance
|
|
32
|
+
_map_stitcher_instance = None
|
|
33
|
+
logger.info("Reset MapStitcher singleton instance")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Map Trimming Utility
|
|
4
|
+
|
|
5
|
+
Removes unnecessary padding from maps - rows/columns that are all walls (#)
|
|
6
|
+
with no meaningful content should be trimmed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def trim_map_padding(grid_dict):
|
|
10
|
+
"""
|
|
11
|
+
Trim unnecessary padding from a map grid.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
grid_dict: Dictionary mapping (x, y) to symbols
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Trimmed grid dictionary
|
|
18
|
+
"""
|
|
19
|
+
if not grid_dict:
|
|
20
|
+
return grid_dict
|
|
21
|
+
|
|
22
|
+
# Get bounds
|
|
23
|
+
all_coords = list(grid_dict.keys())
|
|
24
|
+
if not all_coords:
|
|
25
|
+
return grid_dict
|
|
26
|
+
|
|
27
|
+
min_x = min(x for x, y in all_coords)
|
|
28
|
+
max_x = max(x for x, y in all_coords)
|
|
29
|
+
min_y = min(y for x, y in all_coords)
|
|
30
|
+
max_y = max(y for x, y in all_coords)
|
|
31
|
+
|
|
32
|
+
# Check each edge to see if it can be trimmed
|
|
33
|
+
# A row/column can be trimmed if it's all # or empty
|
|
34
|
+
|
|
35
|
+
# Check top rows
|
|
36
|
+
trim_top = 0
|
|
37
|
+
for y in range(min_y, max_y + 1):
|
|
38
|
+
row_values = [grid_dict.get((x, y), ' ') for x in range(min_x, max_x + 1)]
|
|
39
|
+
# Skip if all walls or empty
|
|
40
|
+
if all(v in ['#', ' ', None] for v in row_values):
|
|
41
|
+
trim_top += 1
|
|
42
|
+
else:
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
# Check bottom rows
|
|
46
|
+
trim_bottom = 0
|
|
47
|
+
for y in range(max_y, min_y - 1, -1):
|
|
48
|
+
row_values = [grid_dict.get((x, y), ' ') for x in range(min_x, max_x + 1)]
|
|
49
|
+
if all(v in ['#', ' ', None] for v in row_values):
|
|
50
|
+
trim_bottom += 1
|
|
51
|
+
else:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
# Check left columns
|
|
55
|
+
trim_left = 0
|
|
56
|
+
for x in range(min_x, max_x + 1):
|
|
57
|
+
col_values = [grid_dict.get((x, y), ' ') for y in range(min_y, max_y + 1)]
|
|
58
|
+
if all(v in ['#', ' ', None] for v in col_values):
|
|
59
|
+
trim_left += 1
|
|
60
|
+
else:
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
# Check right columns
|
|
64
|
+
trim_right = 0
|
|
65
|
+
for x in range(max_x, min_x - 1, -1):
|
|
66
|
+
col_values = [grid_dict.get((x, y), ' ') for y in range(min_y, max_y + 1)]
|
|
67
|
+
if all(v in ['#', ' ', None] for v in col_values):
|
|
68
|
+
trim_right += 1
|
|
69
|
+
else:
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
# But keep at least one row of walls around the actual content
|
|
73
|
+
# Don't trim if it would remove actual room walls
|
|
74
|
+
if trim_top > 1:
|
|
75
|
+
trim_top -= 1 # Keep one row of walls
|
|
76
|
+
if trim_bottom > 1:
|
|
77
|
+
trim_bottom -= 1
|
|
78
|
+
if trim_left > 1:
|
|
79
|
+
trim_left -= 1
|
|
80
|
+
if trim_right > 1:
|
|
81
|
+
trim_right -= 1
|
|
82
|
+
|
|
83
|
+
# Create trimmed grid
|
|
84
|
+
trimmed = {}
|
|
85
|
+
new_min_x = min_x + trim_left
|
|
86
|
+
new_max_x = max_x - trim_right
|
|
87
|
+
new_min_y = min_y + trim_top
|
|
88
|
+
new_max_y = max_y - trim_bottom
|
|
89
|
+
|
|
90
|
+
for y in range(new_min_y, new_max_y + 1):
|
|
91
|
+
for x in range(new_min_x, new_max_x + 1):
|
|
92
|
+
if (x, y) in grid_dict:
|
|
93
|
+
# Adjust coordinates to start from 0
|
|
94
|
+
new_x = x - new_min_x
|
|
95
|
+
new_y = y - new_min_y
|
|
96
|
+
trimmed[(new_x, new_y)] = grid_dict[(x, y)]
|
|
97
|
+
|
|
98
|
+
return trimmed
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_padding_row(row_values):
|
|
102
|
+
"""Check if a row is just padding (all walls with no content)"""
|
|
103
|
+
# A padding row is one that's all # or empty spaces
|
|
104
|
+
# But not if it contains doors, NPCs, items, etc.
|
|
105
|
+
meaningful_symbols = ['.', 'D', 'N', 'T', 'P', 'S', 'W', '?', '~']
|
|
106
|
+
return not any(v in meaningful_symbols for v in row_values)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Map Stitching Visualization System
|
|
4
|
+
|
|
5
|
+
Creates visual representations of the stitched world map showing
|
|
6
|
+
connections between different areas, routes, towns, and buildings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
|
11
|
+
from utils.map_stitcher import MapStitcher, WarpConnection, MapArea
|
|
12
|
+
from utils.map_formatter import format_map_for_display, get_symbol_legend
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class MapVisualizer:
|
|
17
|
+
"""Visualizes stitched map connections and layouts"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, map_stitcher: MapStitcher):
|
|
20
|
+
self.stitcher = map_stitcher
|
|
21
|
+
|
|
22
|
+
def generate_world_map_summary(self) -> str:
|
|
23
|
+
"""Generate a text summary of the entire stitched world"""
|
|
24
|
+
stats = self.stitcher.get_stats()
|
|
25
|
+
lines = [
|
|
26
|
+
"=== STITCHED WORLD MAP SUMMARY ===",
|
|
27
|
+
"",
|
|
28
|
+
f"📍 Total Areas Discovered: {stats['total_areas']}",
|
|
29
|
+
f" 🏠 Indoor Areas: {stats['indoor_areas']}",
|
|
30
|
+
f" 🌍 Outdoor Areas: {stats['outdoor_areas']}",
|
|
31
|
+
f"",
|
|
32
|
+
f"🔗 Total Connections: {stats['total_connections']}",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Show warp type breakdown
|
|
36
|
+
if stats['warp_types']:
|
|
37
|
+
lines.append(" Warp Types:")
|
|
38
|
+
for warp_type, count in stats['warp_types'].items():
|
|
39
|
+
lines.append(f" {warp_type}: {count}")
|
|
40
|
+
|
|
41
|
+
lines.extend([
|
|
42
|
+
"",
|
|
43
|
+
f"⭐ Most Visited: {stats.get('most_visited', 'None')}",
|
|
44
|
+
""
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
return "\n".join(lines)
|
|
48
|
+
|
|
49
|
+
def generate_area_connections_map(self, focus_area_id: Optional[int] = None) -> str:
|
|
50
|
+
"""Generate a connection map showing how areas link together"""
|
|
51
|
+
lines = ["=== AREA CONNECTIONS MAP ===", ""]
|
|
52
|
+
|
|
53
|
+
if focus_area_id:
|
|
54
|
+
# Show connections for a specific area
|
|
55
|
+
area = self.stitcher.map_areas.get(focus_area_id)
|
|
56
|
+
if not area:
|
|
57
|
+
return "Area not found"
|
|
58
|
+
|
|
59
|
+
lines.append(f"🎯 FOCUS: {area.location_name} (ID: {focus_area_id:04X})")
|
|
60
|
+
lines.append("")
|
|
61
|
+
|
|
62
|
+
connections = self.stitcher.get_connected_areas(focus_area_id)
|
|
63
|
+
if connections:
|
|
64
|
+
lines.append("Connected Areas:")
|
|
65
|
+
for to_id, to_name, direction in connections:
|
|
66
|
+
direction_symbol = self._get_direction_symbol(direction)
|
|
67
|
+
lines.append(f" {direction_symbol} {to_name} (ID: {to_id:04X})")
|
|
68
|
+
else:
|
|
69
|
+
lines.append(" No connections found")
|
|
70
|
+
else:
|
|
71
|
+
# Show all areas and their connection counts
|
|
72
|
+
lines.append("All Areas (with connection counts):")
|
|
73
|
+
lines.append("")
|
|
74
|
+
|
|
75
|
+
for area_id, area in self.stitcher.map_areas.items():
|
|
76
|
+
connections = self.stitcher.get_connected_areas(area_id)
|
|
77
|
+
connection_count = len(connections)
|
|
78
|
+
|
|
79
|
+
area_type = "🏠" if area.location_name and "HOUSE" in area.location_name.upper() else "🌍"
|
|
80
|
+
lines.append(f"{area_type} {area.location_name} ({area_id:04X}) - {connection_count} connections")
|
|
81
|
+
|
|
82
|
+
if connections:
|
|
83
|
+
for to_id, to_name, direction in connections[:3]: # Show first 3
|
|
84
|
+
direction_symbol = self._get_direction_symbol(direction)
|
|
85
|
+
lines.append(f" {direction_symbol} {to_name}")
|
|
86
|
+
if len(connections) > 3:
|
|
87
|
+
lines.append(f" ... and {len(connections) - 3} more")
|
|
88
|
+
|
|
89
|
+
return "\n".join(lines)
|
|
90
|
+
|
|
91
|
+
def generate_route_network_map(self) -> str:
|
|
92
|
+
"""Generate a network view of route connections"""
|
|
93
|
+
lines = ["=== ROUTE NETWORK MAP ===", ""]
|
|
94
|
+
|
|
95
|
+
# Group areas by type
|
|
96
|
+
routes = []
|
|
97
|
+
towns = []
|
|
98
|
+
buildings = []
|
|
99
|
+
|
|
100
|
+
for area_id, area in self.stitcher.map_areas.items():
|
|
101
|
+
name = area.location_name.upper() if area.location_name else "UNKNOWN"
|
|
102
|
+
if "ROUTE" in name:
|
|
103
|
+
routes.append((area_id, area))
|
|
104
|
+
elif any(keyword in name for keyword in ["TOWN", "CITY"]):
|
|
105
|
+
towns.append((area_id, area))
|
|
106
|
+
elif any(keyword in name for keyword in ["HOUSE", "ROOM", "CENTER", "MART", "GYM"]):
|
|
107
|
+
buildings.append((area_id, area))
|
|
108
|
+
|
|
109
|
+
if routes:
|
|
110
|
+
lines.append("🛤️ ROUTES:")
|
|
111
|
+
for area_id, area in sorted(routes, key=lambda x: x[1].location_name):
|
|
112
|
+
connections = self.stitcher.get_connected_areas(area_id)
|
|
113
|
+
connection_names = [name for _, name, _ in connections]
|
|
114
|
+
lines.append(f" {area.location_name} → {', '.join(connection_names[:3])}")
|
|
115
|
+
|
|
116
|
+
if towns:
|
|
117
|
+
lines.append("")
|
|
118
|
+
lines.append("🏘️ TOWNS & CITIES:")
|
|
119
|
+
for area_id, area in sorted(towns, key=lambda x: x[1].location_name):
|
|
120
|
+
connections = self.stitcher.get_connected_areas(area_id)
|
|
121
|
+
route_connections = [name for _, name, _ in connections if "ROUTE" in name.upper()]
|
|
122
|
+
if route_connections:
|
|
123
|
+
lines.append(f" {area.location_name} ↔ {', '.join(route_connections)}")
|
|
124
|
+
else:
|
|
125
|
+
lines.append(f" {area.location_name} (isolated)")
|
|
126
|
+
|
|
127
|
+
if buildings:
|
|
128
|
+
lines.append("")
|
|
129
|
+
lines.append("🏢 BUILDINGS:")
|
|
130
|
+
building_count_by_area = {}
|
|
131
|
+
for area_id, area in buildings:
|
|
132
|
+
# Group buildings by their location area
|
|
133
|
+
connections = self.stitcher.get_connected_areas(area_id)
|
|
134
|
+
parent_areas = [name for _, name, _ in connections
|
|
135
|
+
if not any(kw in name.upper() for kw in ["HOUSE", "ROOM", "CENTER"])]
|
|
136
|
+
parent = parent_areas[0] if parent_areas else "Unknown"
|
|
137
|
+
if parent not in building_count_by_area:
|
|
138
|
+
building_count_by_area[parent] = []
|
|
139
|
+
building_count_by_area[parent].append(area.location_name)
|
|
140
|
+
|
|
141
|
+
for parent, buildings_list in building_count_by_area.items():
|
|
142
|
+
lines.append(f" {parent}: {len(buildings_list)} buildings")
|
|
143
|
+
for building in buildings_list[:3]: # Show first 3
|
|
144
|
+
lines.append(f" • {building}")
|
|
145
|
+
if len(buildings_list) > 3:
|
|
146
|
+
lines.append(f" • ... and {len(buildings_list) - 3} more")
|
|
147
|
+
|
|
148
|
+
return "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
def generate_warp_details_report(self) -> str:
|
|
151
|
+
"""Generate detailed warp connection information"""
|
|
152
|
+
lines = ["=== WARP CONNECTIONS DETAILS ===", ""]
|
|
153
|
+
|
|
154
|
+
# Group connections by type
|
|
155
|
+
warp_by_type = {}
|
|
156
|
+
for conn in self.stitcher.warp_connections:
|
|
157
|
+
if conn.warp_type not in warp_by_type:
|
|
158
|
+
warp_by_type[conn.warp_type] = []
|
|
159
|
+
warp_by_type[conn.warp_type].append(conn)
|
|
160
|
+
|
|
161
|
+
for warp_type, connections in warp_by_type.items():
|
|
162
|
+
type_symbol = self._get_warp_type_symbol(warp_type)
|
|
163
|
+
lines.append(f"{type_symbol} {warp_type.upper()} CONNECTIONS ({len(connections)}):")
|
|
164
|
+
|
|
165
|
+
for conn in connections[:10]: # Show first 10 of each type
|
|
166
|
+
from_area = self.stitcher.map_areas.get(conn.from_map_id)
|
|
167
|
+
to_area = self.stitcher.map_areas.get(conn.to_map_id)
|
|
168
|
+
|
|
169
|
+
if from_area and to_area:
|
|
170
|
+
direction_symbol = self._get_direction_symbol(conn.direction)
|
|
171
|
+
lines.append(f" {direction_symbol} {from_area.location_name} → {to_area.location_name}")
|
|
172
|
+
lines.append(f" Position: ({conn.from_position[0]}, {conn.from_position[1]}) → ({conn.to_position[0]}, {conn.to_position[1]})")
|
|
173
|
+
|
|
174
|
+
if len(connections) > 10:
|
|
175
|
+
lines.append(f" ... and {len(connections) - 10} more {warp_type} connections")
|
|
176
|
+
|
|
177
|
+
lines.append("")
|
|
178
|
+
|
|
179
|
+
return "\n".join(lines)
|
|
180
|
+
|
|
181
|
+
def generate_navigation_hints(self, current_area_id: int, target_area_name: str) -> str:
|
|
182
|
+
"""Generate navigation hints to reach a target area"""
|
|
183
|
+
lines = [f"=== NAVIGATION: TO {target_area_name.upper()} ===", ""]
|
|
184
|
+
|
|
185
|
+
current_area = self.stitcher.map_areas.get(current_area_id)
|
|
186
|
+
if not current_area:
|
|
187
|
+
return "Current area not found in stitched map"
|
|
188
|
+
|
|
189
|
+
# Find target area
|
|
190
|
+
target_area = None
|
|
191
|
+
target_id = None
|
|
192
|
+
for area_id, area in self.stitcher.map_areas.items():
|
|
193
|
+
if area.location_name and target_area_name.upper() in area.location_name.upper():
|
|
194
|
+
target_area = area
|
|
195
|
+
target_id = area_id
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
if not target_area:
|
|
199
|
+
lines.append(f"❌ Target area '{target_area_name}' not found in discovered areas")
|
|
200
|
+
lines.append("")
|
|
201
|
+
lines.append("Available areas:")
|
|
202
|
+
for area in self.stitcher.map_areas.values():
|
|
203
|
+
lines.append(f" • {area.location_name}")
|
|
204
|
+
return "\n".join(lines)
|
|
205
|
+
|
|
206
|
+
lines.append(f"📍 Current: {current_area.location_name}")
|
|
207
|
+
lines.append(f"🎯 Target: {target_area.location_name}")
|
|
208
|
+
lines.append("")
|
|
209
|
+
|
|
210
|
+
# Simple pathfinding - direct connections first
|
|
211
|
+
direct_connections = self.stitcher.get_connected_areas(current_area_id)
|
|
212
|
+
direct_targets = [conn for conn in direct_connections if conn[0] == target_id]
|
|
213
|
+
|
|
214
|
+
if direct_targets:
|
|
215
|
+
conn = direct_targets[0]
|
|
216
|
+
direction_symbol = self._get_direction_symbol(conn[2])
|
|
217
|
+
lines.append(f"🔗 Direct connection available!")
|
|
218
|
+
lines.append(f" {direction_symbol} Go {conn[2]} to reach {target_area.location_name}")
|
|
219
|
+
else:
|
|
220
|
+
# Look for paths through connected areas
|
|
221
|
+
paths = self._find_simple_paths(current_area_id, target_id, max_depth=3)
|
|
222
|
+
if paths:
|
|
223
|
+
lines.append("🗺️ Possible routes:")
|
|
224
|
+
for i, path in enumerate(paths[:3]): # Show first 3 paths
|
|
225
|
+
route_description = self._describe_path(path)
|
|
226
|
+
lines.append(f" Route {i+1}: {route_description}")
|
|
227
|
+
else:
|
|
228
|
+
lines.append("❓ No known path found (areas may not be connected yet)")
|
|
229
|
+
|
|
230
|
+
return "\n".join(lines)
|
|
231
|
+
|
|
232
|
+
def _find_simple_paths(self, start_id: int, target_id: int, max_depth: int = 3) -> List[List[int]]:
|
|
233
|
+
"""Find simple paths between areas (basic BFS)"""
|
|
234
|
+
if start_id == target_id:
|
|
235
|
+
return [[start_id]]
|
|
236
|
+
|
|
237
|
+
visited = set()
|
|
238
|
+
queue = [(start_id, [start_id])]
|
|
239
|
+
paths = []
|
|
240
|
+
|
|
241
|
+
while queue and len(paths) < 5: # Limit to 5 paths
|
|
242
|
+
current_id, path = queue.pop(0)
|
|
243
|
+
|
|
244
|
+
if len(path) > max_depth:
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
if current_id in visited:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
visited.add(current_id)
|
|
251
|
+
|
|
252
|
+
connections = self.stitcher.get_connected_areas(current_id)
|
|
253
|
+
for next_id, _, _ in connections:
|
|
254
|
+
if next_id == target_id:
|
|
255
|
+
paths.append(path + [next_id])
|
|
256
|
+
elif next_id not in visited and len(path) < max_depth:
|
|
257
|
+
queue.append((next_id, path + [next_id]))
|
|
258
|
+
|
|
259
|
+
return paths
|
|
260
|
+
|
|
261
|
+
def _describe_path(self, path: List[int]) -> str:
|
|
262
|
+
"""Describe a path as a sequence of area names"""
|
|
263
|
+
if len(path) < 2:
|
|
264
|
+
return "No path"
|
|
265
|
+
|
|
266
|
+
descriptions = []
|
|
267
|
+
for i in range(len(path) - 1):
|
|
268
|
+
from_id = path[i]
|
|
269
|
+
to_id = path[i + 1]
|
|
270
|
+
|
|
271
|
+
from_area = self.stitcher.map_areas.get(from_id)
|
|
272
|
+
to_area = self.stitcher.map_areas.get(to_id)
|
|
273
|
+
|
|
274
|
+
if from_area and to_area:
|
|
275
|
+
# Find the connection direction
|
|
276
|
+
connections = self.stitcher.get_connected_areas(from_id)
|
|
277
|
+
direction = "→"
|
|
278
|
+
for conn_id, _, conn_dir in connections:
|
|
279
|
+
if conn_id == to_id:
|
|
280
|
+
direction = self._get_direction_symbol(conn_dir)
|
|
281
|
+
break
|
|
282
|
+
|
|
283
|
+
descriptions.append(f"{from_area.location_name} {direction} {to_area.location_name}")
|
|
284
|
+
|
|
285
|
+
return " → ".join(descriptions)
|
|
286
|
+
|
|
287
|
+
def _get_direction_symbol(self, direction: str) -> str:
|
|
288
|
+
"""Get symbol for direction"""
|
|
289
|
+
symbols = {
|
|
290
|
+
"north": "⬆️", "south": "⬇️", "east": "➡️", "west": "⬅️",
|
|
291
|
+
"up": "🔼", "down": "🔽", "northeast": "↗️", "northwest": "↖️",
|
|
292
|
+
"southeast": "↘️", "southwest": "↙️"
|
|
293
|
+
}
|
|
294
|
+
return symbols.get(direction.lower(), "🔄")
|
|
295
|
+
|
|
296
|
+
def _get_warp_type_symbol(self, warp_type: str) -> str:
|
|
297
|
+
"""Get symbol for warp type"""
|
|
298
|
+
symbols = {
|
|
299
|
+
"door": "🚪", "stairs": "🪜", "warp": "🌀",
|
|
300
|
+
"route_transition": "🛤️", "exit": "🚪"
|
|
301
|
+
}
|
|
302
|
+
return symbols.get(warp_type, "🔗")
|
|
303
|
+
|
|
304
|
+
def generate_complete_world_overview(self) -> str:
|
|
305
|
+
"""Generate a complete overview combining all visualization types"""
|
|
306
|
+
lines = [
|
|
307
|
+
"=" * 60,
|
|
308
|
+
" POKEMON EMERALD WORLD MAP",
|
|
309
|
+
" (Stitched View)",
|
|
310
|
+
"=" * 60,
|
|
311
|
+
"",
|
|
312
|
+
self.generate_world_map_summary(),
|
|
313
|
+
"",
|
|
314
|
+
self.generate_route_network_map(),
|
|
315
|
+
"",
|
|
316
|
+
self.generate_area_connections_map(),
|
|
317
|
+
"",
|
|
318
|
+
self.generate_warp_details_report(),
|
|
319
|
+
"",
|
|
320
|
+
"=" * 60,
|
|
321
|
+
f"Generated from {len(self.stitcher.map_areas)} discovered areas",
|
|
322
|
+
"=" * 60
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
return "\n".join(lines)
|
|
326
|
+
|
|
327
|
+
def create_map_visualizer(memory_reader) -> MapVisualizer:
|
|
328
|
+
"""Create a map visualizer from a memory reader's stitcher"""
|
|
329
|
+
if hasattr(memory_reader, '_map_stitcher'):
|
|
330
|
+
return MapVisualizer(memory_reader._map_stitcher)
|
|
331
|
+
else:
|
|
332
|
+
# Create a standalone stitcher
|
|
333
|
+
stitcher = MapStitcher()
|
|
334
|
+
return MapVisualizer(stitcher)
|