synth-ai 0.2.14__py3-none-any.whl → 0.4.4__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.

Files changed (1086) hide show
  1. synth_ai/__init__.py +25 -46
  2. synth_ai/__main__.py +30 -3
  3. synth_ai/cli/__init__.py +98 -72
  4. synth_ai/cli/__main__.py +42 -0
  5. synth_ai/cli/_internal/__init__.py +5 -0
  6. synth_ai/cli/_internal/modal_wrapper.py +31 -0
  7. synth_ai/cli/_internal/storage.py +20 -0
  8. synth_ai/cli/_internal/typer_patch.py +47 -0
  9. synth_ai/cli/_internal/validate_task_app.py +29 -0
  10. synth_ai/cli/agents/__init__.py +17 -0
  11. synth_ai/cli/agents/claude.py +77 -0
  12. synth_ai/cli/agents/codex.py +265 -0
  13. synth_ai/cli/agents/opencode.py +253 -0
  14. synth_ai/cli/commands/__init__.py +18 -0
  15. synth_ai/cli/commands/artifacts/__init__.py +13 -0
  16. synth_ai/cli/commands/artifacts/client.py +119 -0
  17. synth_ai/cli/commands/artifacts/config.py +57 -0
  18. synth_ai/cli/commands/artifacts/core.py +24 -0
  19. synth_ai/cli/commands/artifacts/download.py +188 -0
  20. synth_ai/cli/commands/artifacts/export.py +186 -0
  21. synth_ai/cli/commands/artifacts/list.py +156 -0
  22. synth_ai/cli/commands/artifacts/parsing.py +250 -0
  23. synth_ai/cli/commands/artifacts/show.py +336 -0
  24. synth_ai/cli/commands/demo/__init__.py +3 -0
  25. synth_ai/cli/commands/demo/core.py +153 -0
  26. synth_ai/cli/commands/eval/__init__.py +10 -0
  27. synth_ai/cli/commands/eval/config.py +338 -0
  28. synth_ai/cli/commands/eval/core.py +258 -0
  29. synth_ai/cli/commands/eval/runner.py +704 -0
  30. synth_ai/cli/commands/eval/validation.py +60 -0
  31. synth_ai/cli/commands/filter/__init__.py +12 -0
  32. synth_ai/cli/commands/filter/core.py +424 -0
  33. synth_ai/cli/commands/filter/errors.py +55 -0
  34. synth_ai/cli/commands/filter/validation.py +77 -0
  35. synth_ai/cli/commands/help/__init__.py +185 -0
  36. synth_ai/cli/commands/help/core.py +72 -0
  37. synth_ai/cli/commands/scan/__init__.py +19 -0
  38. synth_ai/cli/commands/scan/cloudflare_scanner.py +403 -0
  39. synth_ai/cli/commands/scan/core.py +344 -0
  40. synth_ai/cli/commands/scan/health_checker.py +242 -0
  41. synth_ai/cli/commands/scan/local_scanner.py +278 -0
  42. synth_ai/cli/commands/scan/models.py +83 -0
  43. synth_ai/cli/commands/smoke/__init__.py +7 -0
  44. synth_ai/cli/commands/smoke/core.py +1428 -0
  45. synth_ai/cli/commands/status/__init__.py +3 -0
  46. synth_ai/cli/commands/status/client.py +91 -0
  47. synth_ai/cli/commands/status/config.py +12 -0
  48. synth_ai/cli/commands/status/errors.py +11 -0
  49. synth_ai/cli/commands/status/subcommands/__init__.py +3 -0
  50. synth_ai/cli/commands/status/subcommands/config.py +13 -0
  51. synth_ai/cli/commands/status/subcommands/files.py +34 -0
  52. synth_ai/cli/commands/status/subcommands/jobs.py +51 -0
  53. synth_ai/cli/commands/status/subcommands/models.py +35 -0
  54. synth_ai/cli/commands/status/subcommands/runs.py +34 -0
  55. synth_ai/cli/commands/status/subcommands/session.py +77 -0
  56. synth_ai/cli/commands/status/subcommands/summary.py +39 -0
  57. synth_ai/cli/commands/status/subcommands/utils.py +41 -0
  58. synth_ai/cli/commands/status/utils.py +23 -0
  59. synth_ai/cli/commands/train/__init__.py +51 -0
  60. synth_ai/cli/commands/train/core.py +22 -0
  61. synth_ai/cli/commands/train/errors.py +117 -0
  62. synth_ai/cli/commands/train/prompt_learning_validation.py +632 -0
  63. synth_ai/cli/commands/train/validation.py +392 -0
  64. synth_ai/cli/commands/train/verifier_schemas.py +200 -0
  65. synth_ai/cli/commands/train/verifier_validation.py +235 -0
  66. synth_ai/cli/demo_apps/__init__.py +10 -0
  67. synth_ai/cli/demo_apps/core/__init__.py +28 -0
  68. synth_ai/cli/demo_apps/core/cli.py +1735 -0
  69. synth_ai/cli/demo_apps/crafter/crafter_fft_4b.toml +55 -0
  70. synth_ai/cli/demo_apps/crafter/grpo_crafter_task_app.py +186 -0
  71. synth_ai/cli/demo_apps/crafter/rl_from_base_qwen4b.toml +74 -0
  72. synth_ai/cli/demo_apps/demo_registry.py +176 -0
  73. synth_ai/cli/demo_apps/demo_task_apps/core.py +440 -0
  74. synth_ai/cli/demo_apps/demo_task_apps/crafter/__init__.py +1 -0
  75. synth_ai/cli/demo_apps/demo_task_apps/crafter/grpo_crafter_task_app.py +185 -0
  76. synth_ai/cli/demo_apps/demo_task_apps/math/config.toml +73 -0
  77. synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +738 -0
  78. synth_ai/cli/demo_apps/demo_task_apps/math/task_app_entry.py +39 -0
  79. synth_ai/cli/demo_apps/math/__init__.py +1 -0
  80. synth_ai/cli/demo_apps/math/_common.py +16 -0
  81. synth_ai/cli/demo_apps/math/app.py +38 -0
  82. synth_ai/cli/demo_apps/math/config.toml +75 -0
  83. synth_ai/cli/demo_apps/math/deploy_modal.py +54 -0
  84. synth_ai/cli/demo_apps/math/modal_task_app.py +698 -0
  85. synth_ai/cli/demo_apps/math/task_app_entry.py +53 -0
  86. synth_ai/cli/demo_apps/mipro/main.py +271 -0
  87. synth_ai/cli/demo_apps/mipro/task_app.py +911 -0
  88. synth_ai/cli/demo_apps/mipro/train_cfg.toml +92 -0
  89. synth_ai/cli/demos/__init__.py +12 -0
  90. synth_ai/cli/demos/demo.py +32 -0
  91. synth_ai/cli/demos/rl_demo.py +254 -0
  92. synth_ai/cli/deploy.py +216 -0
  93. synth_ai/cli/infra/__init__.py +14 -0
  94. synth_ai/cli/infra/balance.py +216 -0
  95. synth_ai/cli/infra/mcp.py +35 -0
  96. synth_ai/cli/infra/modal_app.py +36 -0
  97. synth_ai/cli/infra/setup.py +69 -0
  98. synth_ai/cli/infra/status.py +16 -0
  99. synth_ai/cli/infra/turso.py +77 -0
  100. synth_ai/cli/lib/__init__.py +10 -0
  101. synth_ai/cli/lib/agents.py +76 -0
  102. synth_ai/cli/lib/apps/modal_app.py +101 -0
  103. synth_ai/cli/lib/apps/task_app.py +642 -0
  104. synth_ai/cli/lib/bin.py +39 -0
  105. synth_ai/cli/lib/env.py +375 -0
  106. synth_ai/cli/lib/errors.py +85 -0
  107. synth_ai/cli/lib/modal.py +315 -0
  108. synth_ai/cli/lib/plotting.py +126 -0
  109. synth_ai/cli/lib/prompt_args.py +39 -0
  110. synth_ai/cli/lib/prompts.py +284 -0
  111. synth_ai/cli/lib/sqld.py +122 -0
  112. synth_ai/cli/lib/task_app_discovery.py +884 -0
  113. synth_ai/cli/lib/task_app_env.py +295 -0
  114. synth_ai/cli/lib/train_cfgs.py +300 -0
  115. synth_ai/cli/lib/tunnel_records.py +207 -0
  116. synth_ai/cli/local/__init__.py +14 -0
  117. synth_ai/cli/local/experiment_queue/__init__.py +72 -0
  118. synth_ai/cli/local/experiment_queue/api_schemas.py +221 -0
  119. synth_ai/cli/local/experiment_queue/celery_app.py +208 -0
  120. synth_ai/cli/local/experiment_queue/config.py +128 -0
  121. synth_ai/cli/local/experiment_queue/config_utils.py +272 -0
  122. synth_ai/cli/local/experiment_queue/database.py +175 -0
  123. synth_ai/cli/local/experiment_queue/dispatcher.py +119 -0
  124. synth_ai/cli/local/experiment_queue/models.py +231 -0
  125. synth_ai/cli/local/experiment_queue/progress_info.py +160 -0
  126. synth_ai/cli/local/experiment_queue/results.py +373 -0
  127. synth_ai/cli/local/experiment_queue/schemas.py +131 -0
  128. synth_ai/cli/local/experiment_queue/service.py +344 -0
  129. synth_ai/cli/local/experiment_queue/status.py +372 -0
  130. synth_ai/cli/local/experiment_queue/status_tracker.py +360 -0
  131. synth_ai/cli/local/experiment_queue/tasks.py +1984 -0
  132. synth_ai/cli/local/experiment_queue/trace_storage.py +65 -0
  133. synth_ai/cli/local/experiment_queue/validation.py +157 -0
  134. synth_ai/cli/local/session/__init__.py +92 -0
  135. synth_ai/cli/local/session/client.py +383 -0
  136. synth_ai/cli/local/session/constants.py +63 -0
  137. synth_ai/cli/local/session/exceptions.py +105 -0
  138. synth_ai/cli/local/session/manager.py +139 -0
  139. synth_ai/cli/local/session/models.py +89 -0
  140. synth_ai/cli/local/session/query.py +110 -0
  141. synth_ai/cli/root.py +30 -6
  142. synth_ai/cli/task_apps/__init__.py +37 -0
  143. synth_ai/cli/task_apps/commands.py +3145 -0
  144. synth_ai/cli/task_apps/deploy.py +7 -0
  145. synth_ai/cli/task_apps/list.py +26 -0
  146. synth_ai/cli/task_apps/main.py +36 -0
  147. synth_ai/cli/task_apps/modal_serve.py +11 -0
  148. synth_ai/cli/task_apps/serve.py +11 -0
  149. synth_ai/cli/training/__init__.py +8 -0
  150. synth_ai/cli/training/train.py +5 -0
  151. synth_ai/cli/training/train_cfg.py +34 -0
  152. synth_ai/cli/training/watch.py +506 -0
  153. synth_ai/cli/turso.py +34 -55
  154. synth_ai/cli/utils/__init__.py +8 -0
  155. synth_ai/cli/utils/experiments.py +235 -0
  156. synth_ai/cli/utils/queue.py +504 -0
  157. synth_ai/cli/utils/recent.py +133 -0
  158. synth_ai/cli/utils/traces.py +164 -0
  159. synth_ai/contracts/__init__.py +67 -0
  160. synth_ai/core/__init__.py +100 -0
  161. synth_ai/core/_utils/__init__.py +54 -0
  162. synth_ai/core/_utils/base_url.py +10 -0
  163. synth_ai/core/_utils/http.py +10 -0
  164. synth_ai/core/_utils/prompts.py +14 -0
  165. synth_ai/core/_utils/task_app_state.py +12 -0
  166. synth_ai/core/_utils/user_config.py +10 -0
  167. synth_ai/core/apps/common.py +116 -0
  168. synth_ai/core/auth.py +95 -0
  169. synth_ai/core/cfgs.py +240 -0
  170. synth_ai/core/config/__init__.py +16 -0
  171. synth_ai/core/config/base.py +168 -0
  172. synth_ai/core/config/resolver.py +89 -0
  173. synth_ai/core/env.py +231 -0
  174. synth_ai/core/errors.py +125 -0
  175. synth_ai/core/http.py +230 -0
  176. synth_ai/core/integrations/__init__.py +11 -0
  177. synth_ai/core/integrations/cloudflare.py +1886 -0
  178. synth_ai/core/integrations/mcp/__init__.py +6 -0
  179. synth_ai/core/integrations/mcp/__main__.py +8 -0
  180. synth_ai/core/integrations/mcp/claude.py +36 -0
  181. synth_ai/core/integrations/mcp/main.py +254 -0
  182. synth_ai/core/integrations/mcp/setup.py +100 -0
  183. synth_ai/core/integrations/modal.py +277 -0
  184. synth_ai/core/json.py +72 -0
  185. synth_ai/core/log_filter.py +99 -0
  186. synth_ai/core/logging.py +82 -0
  187. synth_ai/core/paths.py +107 -0
  188. synth_ai/core/pricing.py +109 -0
  189. synth_ai/core/process.py +233 -0
  190. synth_ai/core/ssl.py +25 -0
  191. synth_ai/core/storage/__init__.py +71 -0
  192. synth_ai/core/task_app_state.py +318 -0
  193. synth_ai/core/telemetry.py +282 -0
  194. synth_ai/core/tracing_v3/__init__.py +99 -0
  195. synth_ai/core/tracing_v3/abstractions.py +348 -0
  196. synth_ai/core/tracing_v3/config.py +229 -0
  197. synth_ai/core/tracing_v3/constants.py +21 -0
  198. synth_ai/core/tracing_v3/db_config.py +182 -0
  199. synth_ai/core/tracing_v3/decorators.py +401 -0
  200. synth_ai/core/tracing_v3/llm_call_record_helpers.py +437 -0
  201. synth_ai/core/tracing_v3/migration_helper.py +119 -0
  202. synth_ai/core/tracing_v3/session_tracer.py +542 -0
  203. synth_ai/core/tracing_v3/storage/base.py +211 -0
  204. synth_ai/core/tracing_v3/storage/config.py +109 -0
  205. synth_ai/core/tracing_v3/storage/factory.py +39 -0
  206. synth_ai/core/tracing_v3/trace_utils.py +326 -0
  207. synth_ai/core/tracing_v3/turso/daemon.py +278 -0
  208. synth_ai/core/tracing_v3/turso/models.py +470 -0
  209. synth_ai/core/tracing_v3/turso/native_manager.py +1385 -0
  210. synth_ai/core/tracing_v3/utils.py +108 -0
  211. synth_ai/core/urls.py +18 -0
  212. synth_ai/core/user_config.py +137 -0
  213. synth_ai/core/uvicorn.py +222 -0
  214. synth_ai/data/__init__.py +83 -0
  215. synth_ai/data/enums.py +122 -0
  216. synth_ai/data/rewards.py +249 -0
  217. synth_ai/data/traces.py +35 -0
  218. synth_ai/products/__init__.py +6 -0
  219. synth_ai/products/graph_evolve/__init__.py +45 -0
  220. synth_ai/products/graph_evolve/client.py +226 -0
  221. synth_ai/products/graph_evolve/config.py +591 -0
  222. synth_ai/products/graph_evolve/converters/__init__.py +42 -0
  223. synth_ai/products/graph_evolve/converters/openai_sft.py +484 -0
  224. synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +109 -0
  225. synth_ai/products/graph_evolve/run.py +222 -0
  226. synth_ai/products/graph_gepa/__init__.py +23 -0
  227. synth_ai/products/graph_gepa/converters/__init__.py +19 -0
  228. synth_ai/products/graph_gepa/converters/openai_sft.py +29 -0
  229. synth_ai/sdk/__init__.py +129 -0
  230. synth_ai/sdk/api/__init__.py +1 -0
  231. synth_ai/sdk/api/eval/__init__.py +33 -0
  232. synth_ai/sdk/api/eval/job.py +732 -0
  233. synth_ai/sdk/api/models/supported.py +514 -0
  234. synth_ai/sdk/api/research_agent/__init__.py +296 -0
  235. synth_ai/sdk/api/train/__init__.py +85 -0
  236. synth_ai/sdk/api/train/builders.py +1076 -0
  237. synth_ai/sdk/api/train/cli.py +2196 -0
  238. synth_ai/sdk/api/train/config_finder.py +267 -0
  239. synth_ai/sdk/api/train/configs/__init__.py +67 -0
  240. synth_ai/sdk/api/train/configs/prompt_learning.py +1800 -0
  241. synth_ai/sdk/api/train/configs/rl.py +436 -0
  242. synth_ai/sdk/api/train/configs/sft.py +263 -0
  243. synth_ai/sdk/api/train/configs/shared.py +81 -0
  244. synth_ai/sdk/api/train/context_learning.py +312 -0
  245. synth_ai/sdk/api/train/env_resolver.py +418 -0
  246. synth_ai/sdk/api/train/graph_validators.py +216 -0
  247. synth_ai/sdk/api/train/graphgen.py +1102 -0
  248. synth_ai/sdk/api/train/graphgen_models.py +873 -0
  249. synth_ai/sdk/api/train/graphgen_validators.py +109 -0
  250. synth_ai/sdk/api/train/local_api.py +10 -0
  251. synth_ai/sdk/api/train/pollers.py +160 -0
  252. synth_ai/sdk/api/train/progress/__init__.py +97 -0
  253. synth_ai/sdk/api/train/progress/dataclasses.py +569 -0
  254. synth_ai/sdk/api/train/progress/events.py +326 -0
  255. synth_ai/sdk/api/train/progress/results.py +428 -0
  256. synth_ai/sdk/api/train/progress/tracker.py +641 -0
  257. synth_ai/sdk/api/train/prompt_learning.py +800 -0
  258. synth_ai/sdk/api/train/rl.py +478 -0
  259. synth_ai/sdk/api/train/sft.py +398 -0
  260. synth_ai/sdk/api/train/summary.py +522 -0
  261. synth_ai/sdk/api/train/supported_algos.py +147 -0
  262. synth_ai/sdk/api/train/task_app.py +351 -0
  263. synth_ai/sdk/api/train/utils.py +279 -0
  264. synth_ai/sdk/api/train/validators.py +2424 -0
  265. synth_ai/sdk/graphs/__init__.py +15 -0
  266. synth_ai/sdk/graphs/completions.py +776 -0
  267. synth_ai/sdk/graphs/verifier_schemas.py +222 -0
  268. synth_ai/sdk/inference/__init__.py +6 -0
  269. synth_ai/sdk/inference/client.py +128 -0
  270. synth_ai/sdk/jobs/__init__.py +16 -0
  271. synth_ai/sdk/jobs/client.py +371 -0
  272. synth_ai/sdk/learning/__init__.py +99 -0
  273. synth_ai/sdk/learning/client.py +240 -0
  274. synth_ai/sdk/learning/context_learning_client.py +531 -0
  275. synth_ai/sdk/learning/context_learning_types.py +294 -0
  276. synth_ai/sdk/learning/ft_client.py +7 -0
  277. synth_ai/sdk/learning/health.py +49 -0
  278. synth_ai/sdk/learning/jobs.py +202 -0
  279. synth_ai/sdk/learning/prompt_extraction.py +334 -0
  280. synth_ai/sdk/learning/prompt_learning_client.py +455 -0
  281. synth_ai/sdk/learning/prompt_learning_types.py +186 -0
  282. synth_ai/sdk/learning/rl/__init__.py +35 -0
  283. synth_ai/sdk/learning/rl/client.py +268 -0
  284. synth_ai/sdk/learning/rl/contracts.py +23 -0
  285. synth_ai/sdk/learning/rl/env_keys.py +166 -0
  286. synth_ai/sdk/learning/rl/secrets.py +13 -0
  287. synth_ai/sdk/learning/sft/client.py +95 -0
  288. synth_ai/sdk/learning/sft/config.py +270 -0
  289. synth_ai/sdk/learning/sft/data.py +698 -0
  290. synth_ai/sdk/learning/validators.py +52 -0
  291. synth_ai/sdk/localapi/__init__.py +40 -0
  292. synth_ai/sdk/localapi/apps/__init__.py +28 -0
  293. synth_ai/sdk/localapi/client.py +10 -0
  294. synth_ai/sdk/localapi/contracts.py +10 -0
  295. synth_ai/sdk/localapi/helpers.py +519 -0
  296. synth_ai/sdk/localapi/rollouts.py +93 -0
  297. synth_ai/sdk/localapi/server.py +29 -0
  298. synth_ai/sdk/localapi/template.py +49 -0
  299. synth_ai/sdk/streaming/__init__.py +35 -0
  300. synth_ai/sdk/streaming/config.py +94 -0
  301. synth_ai/sdk/streaming/handlers.py +1997 -0
  302. synth_ai/sdk/streaming/streamer.py +708 -0
  303. synth_ai/sdk/streaming/types.py +112 -0
  304. synth_ai/sdk/task/__init__.py +164 -0
  305. synth_ai/sdk/task/apps/__init__.py +169 -0
  306. synth_ai/sdk/task/client.py +175 -0
  307. synth_ai/sdk/task/config.py +256 -0
  308. synth_ai/sdk/task/contracts.py +340 -0
  309. synth_ai/sdk/task/datasets.py +108 -0
  310. synth_ai/sdk/task/in_process.py +1200 -0
  311. synth_ai/sdk/task/in_process_runner.py +314 -0
  312. synth_ai/sdk/task/inference_api.py +299 -0
  313. synth_ai/sdk/task/proxy.py +287 -0
  314. synth_ai/sdk/task/rubrics/__init__.py +54 -0
  315. synth_ai/sdk/task/rubrics/loaders.py +156 -0
  316. synth_ai/sdk/task/rubrics/strict.py +148 -0
  317. synth_ai/sdk/task/rubrics.py +219 -0
  318. synth_ai/sdk/task/server.py +640 -0
  319. synth_ai/sdk/task/trace_correlation_helpers.py +557 -0
  320. synth_ai/sdk/task/tracing_utils.py +95 -0
  321. synth_ai/sdk/task/validators.py +441 -0
  322. synth_ai/sdk/training/__init__.py +93 -0
  323. synth_ai/sdk/tunnels/__init__.py +118 -0
  324. synth_ai/sdk/tunnels/cleanup.py +83 -0
  325. synth_ai/sdk/tunnels/ports.py +120 -0
  326. synth_ai/sdk/tunnels/tunneled_api.py +363 -0
  327. synth_ai/utils/__init__.py +213 -0
  328. synth_ai-0.4.4.dist-info/METADATA +262 -0
  329. synth_ai-0.4.4.dist-info/RECORD +369 -0
  330. synth_ai-0.4.4.dist-info/top_level.txt +1 -0
  331. examples/__init__.py +0 -16
  332. examples/analyze_semantic_words.sh +0 -17
  333. examples/crafter_debug_render.py +0 -186
  334. examples/dev/qwen3_32b_qlora_4xh100.toml +0 -40
  335. examples/multi_step/configs/README_verilog_rl.md +0 -77
  336. examples/multi_step/configs/VERILOG_REWARDS.md +0 -90
  337. examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +0 -183
  338. examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +0 -35
  339. examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +0 -36
  340. examples/multi_step/configs/crafter_rl_outcome.toml +0 -74
  341. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +0 -187
  342. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +0 -83
  343. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +0 -78
  344. examples/multi_step/configs/crafter_synth_backend.md +0 -40
  345. examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +0 -31
  346. examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +0 -33
  347. examples/multi_step/configs/verilog_rl_lora.toml +0 -190
  348. examples/multi_step/crafter_rl_lora.md +0 -70
  349. examples/multi_step/judges/crafter_backend_judge.py +0 -220
  350. examples/multi_step/judges/verilog_backend_judge.py +0 -234
  351. examples/multi_step/readme.md +0 -48
  352. examples/multi_step/sse_metrics_streaming_notes.md +0 -357
  353. examples/multi_step/task_app_config_notes.md +0 -494
  354. examples/multi_step/verilog_rl_lora.md +0 -218
  355. examples/qwen_coder/README.md +0 -102
  356. examples/qwen_coder/_shared.py +0 -113
  357. examples/qwen_coder/configs/coder_lora_30b.toml +0 -61
  358. examples/qwen_coder/configs/coder_lora_4b.toml +0 -57
  359. examples/qwen_coder/configs/coder_lora_small.toml +0 -58
  360. examples/qwen_coder/generate_dataset.py +0 -98
  361. examples/qwen_coder/infer_ft_smoke.py +0 -65
  362. examples/qwen_coder/infer_prod_proxy.py +0 -73
  363. examples/qwen_coder/infer_via_synth.py +0 -87
  364. examples/qwen_coder/scripts/infer_coder.sh +0 -19
  365. examples/qwen_coder/scripts/train_coder_30b.sh +0 -22
  366. examples/qwen_coder/sft_full_17b.py +0 -103
  367. examples/qwen_coder/sft_lora_30b.py +0 -110
  368. examples/qwen_coder/subset_jsonl.py +0 -39
  369. examples/qwen_coder/todos.md +0 -38
  370. examples/qwen_coder/validate_jsonl.py +0 -60
  371. examples/rl/README.md +0 -169
  372. examples/rl/download_dataset.py +0 -80
  373. examples/run_crafter_demo.sh +0 -10
  374. examples/sft/README.md +0 -139
  375. examples/sft/configs/crafter_fft_qwen0p6b.toml +0 -44
  376. examples/sft/configs/crafter_lora_qwen0p6b.toml +0 -45
  377. examples/sft/evaluate.py +0 -119
  378. examples/sft/export_dataset.py +0 -117
  379. examples/sft/generate_traces.py +0 -164
  380. examples/swe/__init__.py +0 -12
  381. examples/swe/task_app/README.md +0 -105
  382. examples/swe/task_app/__init__.py +0 -2
  383. examples/swe/task_app/grpo_swe_mini.py +0 -601
  384. examples/swe/task_app/grpo_swe_mini_task_app.py +0 -136
  385. examples/swe/task_app/hosted/README.md +0 -173
  386. examples/swe/task_app/hosted/__init__.py +0 -5
  387. examples/swe/task_app/hosted/branching.py +0 -143
  388. examples/swe/task_app/hosted/environment_routes.py +0 -1289
  389. examples/swe/task_app/hosted/envs/__init__.py +0 -1
  390. examples/swe/task_app/hosted/envs/crafter/__init__.py +0 -6
  391. examples/swe/task_app/hosted/envs/crafter/app.py +0 -1
  392. examples/swe/task_app/hosted/envs/crafter/environment.py +0 -522
  393. examples/swe/task_app/hosted/envs/crafter/policy.py +0 -478
  394. examples/swe/task_app/hosted/envs/crafter/react_agent.py +0 -108
  395. examples/swe/task_app/hosted/envs/crafter/shared.py +0 -305
  396. examples/swe/task_app/hosted/envs/crafter/tools.py +0 -47
  397. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +0 -8
  398. examples/swe/task_app/hosted/envs/mini_swe/environment.py +0 -1164
  399. examples/swe/task_app/hosted/envs/mini_swe/policy.py +0 -355
  400. examples/swe/task_app/hosted/envs/mini_swe/shared.py +0 -83
  401. examples/swe/task_app/hosted/envs/mini_swe/tools.py +0 -96
  402. examples/swe/task_app/hosted/hosted_app.py +0 -204
  403. examples/swe/task_app/hosted/inference/__init__.py +0 -5
  404. examples/swe/task_app/hosted/inference/openai_client.py +0 -618
  405. examples/swe/task_app/hosted/main.py +0 -100
  406. examples/swe/task_app/hosted/policy_routes.py +0 -1079
  407. examples/swe/task_app/hosted/registry.py +0 -195
  408. examples/swe/task_app/hosted/rollout.py +0 -1911
  409. examples/swe/task_app/hosted/storage/__init__.py +0 -5
  410. examples/swe/task_app/hosted/storage/volume.py +0 -211
  411. examples/swe/task_app/hosted/test_agents.py +0 -161
  412. examples/swe/task_app/hosted/test_service.py +0 -136
  413. examples/swe/task_app/hosted/utils.py +0 -62
  414. examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +0 -258
  415. examples/task_apps/TESTING.md +0 -275
  416. examples/task_apps/crafter/CREATE_SFT_DATASET.md +0 -273
  417. examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +0 -152
  418. examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +0 -174
  419. examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +0 -268
  420. examples/task_apps/crafter/QUERY_EXAMPLES.md +0 -203
  421. examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +0 -316
  422. examples/task_apps/crafter/__init__.py +0 -0
  423. examples/task_apps/crafter/eval_image_only_gpt4o.toml +0 -28
  424. examples/task_apps/crafter/eval_text_only_groq_llama.toml +0 -36
  425. examples/task_apps/crafter/filter_sft_dataset.toml +0 -16
  426. examples/task_apps/crafter/task_app/README.md +0 -42
  427. examples/task_apps/crafter/task_app/__init__.py +0 -5
  428. examples/task_apps/crafter/task_app/grpo_crafter.py +0 -973
  429. examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +0 -146
  430. examples/task_apps/crafter/task_app/synth_envs_hosted/README.md +0 -173
  431. examples/task_apps/crafter/task_app/synth_envs_hosted/__init__.py +0 -5
  432. examples/task_apps/crafter/task_app/synth_envs_hosted/branching.py +0 -143
  433. examples/task_apps/crafter/task_app/synth_envs_hosted/environment_routes.py +0 -1226
  434. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/__init__.py +0 -1
  435. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/__init__.py +0 -6
  436. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/app.py +0 -1
  437. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +0 -532
  438. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +0 -547
  439. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +0 -123
  440. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/shared.py +0 -305
  441. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/tools.py +0 -47
  442. examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +0 -204
  443. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/__init__.py +0 -5
  444. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +0 -704
  445. examples/task_apps/crafter/task_app/synth_envs_hosted/main.py +0 -100
  446. examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +0 -1152
  447. examples/task_apps/crafter/task_app/synth_envs_hosted/registry.py +0 -195
  448. examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +0 -2160
  449. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/__init__.py +0 -5
  450. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/volume.py +0 -211
  451. examples/task_apps/crafter/task_app/synth_envs_hosted/test_agents.py +0 -161
  452. examples/task_apps/crafter/task_app/synth_envs_hosted/test_service.py +0 -136
  453. examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +0 -218
  454. examples/task_apps/dev/pokemon_emerald/__init__.py +0 -2
  455. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +0 -811
  456. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +0 -120
  457. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +0 -160
  458. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +0 -155
  459. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +0 -69
  460. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +0 -96
  461. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +0 -1502
  462. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +0 -4
  463. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +0 -68
  464. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +0 -216
  465. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +0 -35
  466. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +0 -631
  467. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +0 -1544
  468. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +0 -1428
  469. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +0 -4848
  470. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +0 -41
  471. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +0 -298
  472. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +0 -95
  473. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +0 -204
  474. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/__init__.py +0 -0
  475. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +0 -2152
  476. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +0 -429
  477. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +0 -155
  478. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +0 -78
  479. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/__init__.py +0 -0
  480. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +0 -122
  481. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +0 -76
  482. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +0 -413
  483. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +0 -204
  484. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +0 -133
  485. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +0 -229
  486. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +0 -300
  487. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +0 -205
  488. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +0 -200
  489. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +0 -284
  490. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +0 -468
  491. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +0 -575
  492. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +0 -311
  493. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +0 -259
  494. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/__init__.py +0 -0
  495. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +0 -372
  496. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +0 -296
  497. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +0 -275
  498. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +0 -22
  499. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +0 -44
  500. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +0 -514
  501. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +0 -415
  502. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +0 -1763
  503. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +0 -33
  504. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +0 -106
  505. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +0 -334
  506. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +0 -1020
  507. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +0 -188
  508. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +0 -1481
  509. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +0 -862
  510. examples/task_apps/dev/pokemon_emerald/modal_app.py +0 -114
  511. examples/task_apps/dev/pokemon_emerald/task_app/README.md +0 -81
  512. examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +0 -6
  513. examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +0 -685
  514. examples/task_apps/enron/__init__.py +0 -1
  515. examples/task_apps/enron/eval_groq_qwen32.toml +0 -16
  516. examples/task_apps/enron/filter_sft.toml +0 -5
  517. examples/task_apps/enron/task_app/README.md +0 -14
  518. examples/task_apps/enron/task_app/__init__.py +0 -1
  519. examples/task_apps/enron/task_app/grpo_enron.py +0 -906
  520. examples/task_apps/enron/task_app/grpo_enron_task_app.py +0 -146
  521. examples/task_apps/enron/tests/__init__.py +0 -4
  522. examples/task_apps/enron/tests/conftest.py +0 -115
  523. examples/task_apps/enron/tests/integration/__init__.py +0 -4
  524. examples/task_apps/enron/tests/integration/test_enron_eval.py +0 -179
  525. examples/task_apps/enron/tests/integration/test_enron_rollout.py +0 -135
  526. examples/task_apps/enron/tests/unit/__init__.py +0 -4
  527. examples/task_apps/enron/tests/unit/test_enron_environment.py +0 -126
  528. examples/task_apps/math/README.md +0 -22
  529. examples/task_apps/math/__init__.py +0 -0
  530. examples/task_apps/math/math_single_step.py +0 -1000
  531. examples/task_apps/math/math_task_app.py +0 -115
  532. examples/task_apps/pokemon_battle/__init__.py +0 -2
  533. examples/task_apps/pokemon_battle/modal_app.py +0 -104
  534. examples/task_apps/pokemon_battle/task_app/README.md +0 -68
  535. examples/task_apps/pokemon_battle/task_app/__init__.py +0 -6
  536. examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +0 -932
  537. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +0 -283
  538. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +0 -155
  539. examples/task_apps/pokemon_red/README.md +0 -357
  540. examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +0 -415
  541. examples/task_apps/pokemon_red/__init__.py +0 -3
  542. examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +0 -29
  543. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +0 -225
  544. examples/task_apps/pokemon_red/pallet_town_rl_config.toml +0 -75
  545. examples/task_apps/pokemon_red/task_app.py +0 -799
  546. examples/task_apps/pokemon_red/test_pallet_town_rewards.py +0 -193
  547. examples/task_apps/sokoban/README.md +0 -307
  548. examples/task_apps/sokoban/__init__.py +0 -3
  549. examples/task_apps/sokoban/eval_groq_qwen32.toml +0 -16
  550. examples/task_apps/sokoban/eval_openai_gpt5.toml +0 -16
  551. examples/task_apps/sokoban/filter_sft.toml +0 -5
  552. examples/task_apps/sokoban/task_app.py +0 -1058
  553. examples/task_apps/sokoban/tests/__init__.py +0 -4
  554. examples/task_apps/sokoban/tests/conftest.py +0 -113
  555. examples/task_apps/sokoban/tests/integration/__init__.py +0 -4
  556. examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +0 -57
  557. examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +0 -198
  558. examples/task_apps/sokoban/tests/unit/__init__.py +0 -4
  559. examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +0 -114
  560. examples/task_apps/verilog/__init__.py +0 -1
  561. examples/task_apps/verilog/eval_groq_qwen32b.toml +0 -24
  562. examples/task_apps/verilog/filter_sft.toml +0 -5
  563. examples/task_apps/verilog/task_app/README.md +0 -12
  564. examples/task_apps/verilog/task_app/__init__.py +0 -1
  565. examples/task_apps/verilog/task_app/grpo_verilog.py +0 -1166
  566. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +0 -145
  567. examples/task_apps/verilog/tests/__init__.py +0 -4
  568. examples/task_apps/verilog/tests/conftest.py +0 -115
  569. examples/task_apps/verilog/tests/integration/__init__.py +0 -4
  570. examples/task_apps/verilog/tests/integration/test_verilog_eval.py +0 -181
  571. examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +0 -55
  572. examples/task_apps/verilog/tests/unit/__init__.py +0 -4
  573. examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +0 -118
  574. examples/vlm/PROPOSAL.md +0 -53
  575. examples/vlm/README.md +0 -68
  576. examples/vlm/configs/crafter_vlm_gpt4o.toml +0 -44
  577. examples/vlm/crafter_image_only_agent.py +0 -207
  578. examples/vlm/crafter_openai_vlm_agent.py +0 -277
  579. examples/vlm/filter_image_rows.py +0 -63
  580. examples/vlm/run_crafter_vlm_benchmark.py +0 -316
  581. examples/warming_up_to_rl/analyze_trace_db.py +0 -422
  582. examples/warming_up_to_rl/configs/crafter_fft.toml +0 -48
  583. examples/warming_up_to_rl/configs/crafter_fft_4b.toml +0 -54
  584. examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +0 -20
  585. examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +0 -13
  586. examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +0 -23
  587. examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +0 -35
  588. examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +0 -26
  589. examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +0 -36
  590. examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +0 -32
  591. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +0 -83
  592. examples/warming_up_to_rl/configs/rl_from_ft.toml +0 -56
  593. examples/warming_up_to_rl/export_trace_sft.py +0 -723
  594. examples/warming_up_to_rl/groq_test.py +0 -97
  595. examples/warming_up_to_rl/manage_secrets.py +0 -131
  596. examples/warming_up_to_rl/old/event_rewards.md +0 -234
  597. examples/warming_up_to_rl/old/notes.md +0 -73
  598. examples/warming_up_to_rl/readme.md +0 -179
  599. examples/warming_up_to_rl/run_eval.py +0 -736
  600. examples/warming_up_to_rl/run_fft_and_save.py +0 -380
  601. examples/warming_up_to_rl/run_local_rollout.py +0 -239
  602. examples/warming_up_to_rl/run_local_rollout_modal.py +0 -248
  603. examples/warming_up_to_rl/run_local_rollout_parallel.py +0 -405
  604. examples/warming_up_to_rl/run_local_rollout_traced.py +0 -477
  605. examples/warming_up_to_rl/run_rl_and_save.py +0 -124
  606. examples/warming_up_to_rl/run_rollout_remote.py +0 -156
  607. examples/workflows/__init__.py +0 -0
  608. examples/workflows/math_rl/__init__.py +0 -0
  609. examples/workflows/math_rl/configs/eval_base_qwen.toml +0 -15
  610. examples/workflows/math_rl/configs/eval_rl_qwen.toml +0 -11
  611. examples/workflows/math_rl/configs/rl_from_base_qwen.toml +0 -35
  612. examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +0 -74
  613. examples/workflows/math_rl/configs/rl_from_ft_qwen.toml +0 -35
  614. examples/workflows/math_rl/download_dataset.py +0 -80
  615. examples/workflows/math_rl/run_eval.py +0 -436
  616. examples/workflows/math_rl/run_rl_and_save.py +0 -111
  617. synth_ai/api/models/supported.py +0 -377
  618. synth_ai/api/train/__init__.py +0 -5
  619. synth_ai/api/train/builders.py +0 -351
  620. synth_ai/api/train/cli.py +0 -635
  621. synth_ai/api/train/config_finder.py +0 -228
  622. synth_ai/api/train/configs/__init__.py +0 -44
  623. synth_ai/api/train/configs/rl.py +0 -134
  624. synth_ai/api/train/configs/sft.py +0 -95
  625. synth_ai/api/train/configs/shared.py +0 -24
  626. synth_ai/api/train/env_resolver.py +0 -349
  627. synth_ai/api/train/pollers.py +0 -75
  628. synth_ai/api/train/supported_algos.py +0 -147
  629. synth_ai/api/train/task_app.py +0 -195
  630. synth_ai/api/train/utils.py +0 -225
  631. synth_ai/cli/_modal_wrapper.py +0 -29
  632. synth_ai/cli/_storage.py +0 -20
  633. synth_ai/cli/_typer_patch.py +0 -49
  634. synth_ai/cli/_validate_task_app.py +0 -11
  635. synth_ai/cli/balance.py +0 -216
  636. synth_ai/cli/calc.py +0 -84
  637. synth_ai/cli/demo.py +0 -165
  638. synth_ai/cli/legacy_root_backup.py +0 -468
  639. synth_ai/cli/man.py +0 -106
  640. synth_ai/cli/recent.py +0 -132
  641. synth_ai/cli/rl_demo.py +0 -254
  642. synth_ai/cli/status.py +0 -134
  643. synth_ai/cli/task_apps.py +0 -4523
  644. synth_ai/cli/traces.py +0 -164
  645. synth_ai/cli/tui.py +0 -57
  646. synth_ai/cli/watch.py +0 -506
  647. synth_ai/compound/cais.py +0 -0
  648. synth_ai/config/base_url.py +0 -107
  649. synth_ai/core/experiment.py +0 -13
  650. synth_ai/core/system.py +0 -15
  651. synth_ai/demo_registry.py +0 -295
  652. synth_ai/demos/core/__init__.py +0 -1
  653. synth_ai/demos/core/cli.py +0 -1718
  654. synth_ai/demos/demo_task_apps/core.py +0 -440
  655. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +0 -184
  656. synth_ai/demos/demo_task_apps/math/config.toml +0 -74
  657. synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
  658. synth_ai/demos/demo_task_apps/math/modal_task_app.py +0 -739
  659. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -37
  660. synth_ai/environments/__init__.py +0 -31
  661. synth_ai/environments/environment/__init__.py +0 -1
  662. synth_ai/environments/environment/artifacts/__init__.py +0 -1
  663. synth_ai/environments/environment/artifacts/base.py +0 -52
  664. synth_ai/environments/environment/core.py +0 -67
  665. synth_ai/environments/environment/db/__init__.py +0 -1
  666. synth_ai/environments/environment/db/sqlite.py +0 -45
  667. synth_ai/environments/environment/registry.py +0 -233
  668. synth_ai/environments/environment/resources/sqlite.py +0 -45
  669. synth_ai/environments/environment/results.py +0 -1
  670. synth_ai/environments/environment/rewards/__init__.py +0 -1
  671. synth_ai/environments/environment/rewards/core.py +0 -29
  672. synth_ai/environments/environment/shared_engine.py +0 -26
  673. synth_ai/environments/environment/tools/__init__.py +0 -200
  674. synth_ai/environments/examples/__init__.py +0 -1
  675. synth_ai/environments/examples/bandit/__init__.py +0 -33
  676. synth_ai/environments/examples/bandit/engine.py +0 -302
  677. synth_ai/environments/examples/bandit/environment.py +0 -194
  678. synth_ai/environments/examples/bandit/taskset.py +0 -200
  679. synth_ai/environments/examples/crafter_classic/__init__.py +0 -8
  680. synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +0 -250
  681. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +0 -59
  682. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +0 -152
  683. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +0 -24
  684. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +0 -1194
  685. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +0 -56
  686. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +0 -32
  687. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  688. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +0 -384
  689. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +0 -53
  690. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +0 -178
  691. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +0 -222
  692. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +0 -183
  693. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +0 -210
  694. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +0 -206
  695. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +0 -49
  696. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +0 -64
  697. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +0 -88
  698. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +0 -77
  699. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +0 -324
  700. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  701. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +0 -362
  702. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +0 -49
  703. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +0 -332
  704. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +0 -97
  705. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +0 -217
  706. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +0 -87
  707. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +0 -88
  708. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +0 -195
  709. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +0 -400
  710. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +0 -195
  711. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +0 -56
  712. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +0 -858
  713. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +0 -52
  714. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +0 -874
  715. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +0 -1412
  716. synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +0 -216
  717. synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +0 -296
  718. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +0 -58
  719. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +0 -464
  720. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +0 -152
  721. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +0 -51
  722. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +0 -1412
  723. synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +0 -112
  724. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +0 -203
  725. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +0 -305
  726. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +0 -126
  727. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +0 -94
  728. synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +0 -142
  729. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +0 -26
  730. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +0 -984
  731. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +0 -724
  732. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +0 -386
  733. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +0 -205
  734. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +0 -150
  735. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +0 -283
  736. synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +0 -280
  737. synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +0 -456
  738. synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +0 -166
  739. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +0 -102
  740. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +0 -128
  741. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +0 -655
  742. synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +0 -202
  743. synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +0 -166
  744. synth_ai/environments/examples/crafter_classic/config_logging.py +0 -111
  745. synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
  746. synth_ai/environments/examples/crafter_classic/engine.py +0 -579
  747. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +0 -64
  748. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +0 -6
  749. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +0 -75
  750. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +0 -267
  751. synth_ai/environments/examples/crafter_classic/environment.py +0 -495
  752. synth_ai/environments/examples/crafter_classic/taskset.py +0 -233
  753. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +0 -228
  754. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +0 -299
  755. synth_ai/environments/examples/crafter_custom/__init__.py +0 -4
  756. synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +0 -1
  757. synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +0 -202
  758. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +0 -7
  759. synth_ai/environments/examples/crafter_custom/crafter/config.py +0 -182
  760. synth_ai/environments/examples/crafter_custom/crafter/constants.py +0 -8
  761. synth_ai/environments/examples/crafter_custom/crafter/engine.py +0 -269
  762. synth_ai/environments/examples/crafter_custom/crafter/env.py +0 -262
  763. synth_ai/environments/examples/crafter_custom/crafter/objects.py +0 -417
  764. synth_ai/environments/examples/crafter_custom/crafter/recorder.py +0 -187
  765. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +0 -118
  766. synth_ai/environments/examples/crafter_custom/dataset_builder.py +0 -373
  767. synth_ai/environments/examples/crafter_custom/environment.py +0 -312
  768. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +0 -159
  769. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +0 -158
  770. synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +0 -71
  771. synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +0 -105
  772. synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +0 -119
  773. synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +0 -52
  774. synth_ai/environments/examples/crafter_custom/run_dataset.py +0 -305
  775. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +0 -156
  776. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +0 -281
  777. synth_ai/environments/examples/enron/art_helpers/types_enron.py +0 -25
  778. synth_ai/environments/examples/enron/engine.py +0 -300
  779. synth_ai/environments/examples/enron/environment.py +0 -234
  780. synth_ai/environments/examples/enron/taskset.py +0 -112
  781. synth_ai/environments/examples/enron/units/keyword_stats.py +0 -112
  782. synth_ai/environments/examples/minigrid/__init__.py +0 -48
  783. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +0 -1188
  784. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +0 -48
  785. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +0 -562
  786. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +0 -221
  787. synth_ai/environments/examples/minigrid/engine.py +0 -589
  788. synth_ai/environments/examples/minigrid/environment.py +0 -274
  789. synth_ai/environments/examples/minigrid/environment_mapping.py +0 -242
  790. synth_ai/environments/examples/minigrid/puzzle_loader.py +0 -417
  791. synth_ai/environments/examples/minigrid/taskset.py +0 -583
  792. synth_ai/environments/examples/nethack/__init__.py +0 -7
  793. synth_ai/environments/examples/nethack/achievements.py +0 -337
  794. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +0 -981
  795. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +0 -74
  796. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +0 -831
  797. synth_ai/environments/examples/nethack/engine.py +0 -739
  798. synth_ai/environments/examples/nethack/environment.py +0 -256
  799. synth_ai/environments/examples/nethack/helpers/__init__.py +0 -41
  800. synth_ai/environments/examples/nethack/helpers/action_mapping.py +0 -301
  801. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +0 -402
  802. synth_ai/environments/examples/nethack/helpers/observation_utils.py +0 -433
  803. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +0 -200
  804. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +0 -269
  805. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +0 -308
  806. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +0 -431
  807. synth_ai/environments/examples/nethack/taskset.py +0 -323
  808. synth_ai/environments/examples/red/__init__.py +0 -7
  809. synth_ai/environments/examples/red/agent_demos/__init__.py +0 -1
  810. synth_ai/environments/examples/red/config_logging.py +0 -110
  811. synth_ai/environments/examples/red/engine.py +0 -721
  812. synth_ai/environments/examples/red/engine_helpers/__init__.py +0 -1
  813. synth_ai/environments/examples/red/engine_helpers/memory_map.py +0 -35
  814. synth_ai/environments/examples/red/engine_helpers/reward_components.py +0 -276
  815. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +0 -142
  816. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +0 -57
  817. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +0 -284
  818. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +0 -150
  819. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +0 -138
  820. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +0 -57
  821. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +0 -331
  822. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +0 -121
  823. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +0 -477
  824. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +0 -559
  825. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +0 -313
  826. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +0 -148
  827. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +0 -247
  828. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +0 -368
  829. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +0 -172
  830. synth_ai/environments/examples/red/environment.py +0 -298
  831. synth_ai/environments/examples/red/taskset.py +0 -79
  832. synth_ai/environments/examples/red/units/__init__.py +0 -1
  833. synth_ai/environments/examples/sokoban/__init__.py +0 -1
  834. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +0 -899
  835. synth_ai/environments/examples/sokoban/engine.py +0 -678
  836. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +0 -1
  837. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +0 -657
  838. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +0 -18
  839. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +0 -3
  840. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +0 -131
  841. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +0 -370
  842. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +0 -332
  843. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +0 -306
  844. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +0 -67
  845. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +0 -115
  846. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +0 -123
  847. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +0 -394
  848. synth_ai/environments/examples/sokoban/environment.py +0 -229
  849. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +0 -440
  850. synth_ai/environments/examples/sokoban/puzzle_loader.py +0 -312
  851. synth_ai/environments/examples/sokoban/taskset.py +0 -544
  852. synth_ai/environments/examples/tictactoe/__init__.py +0 -1
  853. synth_ai/environments/examples/tictactoe/engine.py +0 -368
  854. synth_ai/environments/examples/tictactoe/environment.py +0 -240
  855. synth_ai/environments/examples/tictactoe/taskset.py +0 -215
  856. synth_ai/environments/examples/verilog/__init__.py +0 -10
  857. synth_ai/environments/examples/verilog/engine.py +0 -421
  858. synth_ai/environments/examples/verilog/environment.py +0 -350
  859. synth_ai/environments/examples/verilog/taskset.py +0 -420
  860. synth_ai/environments/examples/wordle/__init__.py +0 -29
  861. synth_ai/environments/examples/wordle/engine.py +0 -398
  862. synth_ai/environments/examples/wordle/environment.py +0 -159
  863. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +0 -75
  864. synth_ai/environments/examples/wordle/taskset.py +0 -230
  865. synth_ai/environments/reproducibility/core.py +0 -42
  866. synth_ai/environments/reproducibility/helpers.py +0 -0
  867. synth_ai/environments/reproducibility/tree.py +0 -363
  868. synth_ai/environments/service/app.py +0 -97
  869. synth_ai/environments/service/core_routes.py +0 -1021
  870. synth_ai/environments/service/external_registry.py +0 -56
  871. synth_ai/environments/service/registry.py +0 -9
  872. synth_ai/environments/stateful/__init__.py +0 -1
  873. synth_ai/environments/stateful/core.py +0 -163
  874. synth_ai/environments/stateful/engine.py +0 -21
  875. synth_ai/environments/stateful/state.py +0 -7
  876. synth_ai/environments/tasks/api.py +0 -19
  877. synth_ai/environments/tasks/core.py +0 -81
  878. synth_ai/environments/tasks/filters.py +0 -40
  879. synth_ai/environments/tasks/utils.py +0 -90
  880. synth_ai/environments/v0_observability/history.py +0 -3
  881. synth_ai/environments/v0_observability/log.py +0 -2
  882. synth_ai/evals/__init__.py +0 -15
  883. synth_ai/evals/base.py +0 -13
  884. synth_ai/evals/client.py +0 -82
  885. synth_ai/evals/types.py +0 -42
  886. synth_ai/handshake.py +0 -109
  887. synth_ai/http.py +0 -26
  888. synth_ai/http_client.py +0 -136
  889. synth_ai/inference/__init__.py +0 -5
  890. synth_ai/inference/client.py +0 -34
  891. synth_ai/jobs/client.py +0 -295
  892. synth_ai/judge_schemas.py +0 -127
  893. synth_ai/learning/__init__.py +0 -59
  894. synth_ai/learning/client.py +0 -241
  895. synth_ai/learning/ft_client.py +0 -7
  896. synth_ai/learning/health.py +0 -49
  897. synth_ai/learning/jobs.py +0 -201
  898. synth_ai/learning/rl/__init__.py +0 -39
  899. synth_ai/learning/rl/client.py +0 -267
  900. synth_ai/learning/rl/contracts.py +0 -27
  901. synth_ai/learning/rl/env_keys.py +0 -166
  902. synth_ai/learning/rl/secrets.py +0 -13
  903. synth_ai/learning/sft/client.py +0 -68
  904. synth_ai/learning/sft/config.py +0 -270
  905. synth_ai/learning/sft/data.py +0 -295
  906. synth_ai/learning/validators.py +0 -49
  907. synth_ai/lm/__init__.py +0 -25
  908. synth_ai/task/__init__.py +0 -121
  909. synth_ai/task/apps/__init__.py +0 -129
  910. synth_ai/task/client.py +0 -167
  911. synth_ai/task/config.py +0 -257
  912. synth_ai/task/contracts.py +0 -236
  913. synth_ai/task/datasets.py +0 -108
  914. synth_ai/task/proxy.py +0 -251
  915. synth_ai/task/rubrics/__init__.py +0 -56
  916. synth_ai/task/rubrics/loaders.py +0 -152
  917. synth_ai/task/rubrics/strict.py +0 -149
  918. synth_ai/task/server.py +0 -432
  919. synth_ai/task/trace_correlation_helpers.py +0 -315
  920. synth_ai/task/tracing_utils.py +0 -84
  921. synth_ai/task/validators.py +0 -418
  922. synth_ai/tracing_v3/__init__.py +0 -97
  923. synth_ai/tracing_v3/abstractions.py +0 -302
  924. synth_ai/tracing_v3/config.py +0 -84
  925. synth_ai/tracing_v3/db_config.py +0 -194
  926. synth_ai/tracing_v3/decorators.py +0 -398
  927. synth_ai/tracing_v3/llm_call_record_helpers.py +0 -391
  928. synth_ai/tracing_v3/migration_helper.py +0 -120
  929. synth_ai/tracing_v3/session_tracer.py +0 -540
  930. synth_ai/tracing_v3/storage/base.py +0 -210
  931. synth_ai/tracing_v3/storage/config.py +0 -75
  932. synth_ai/tracing_v3/storage/factory.py +0 -39
  933. synth_ai/tracing_v3/trace_utils.py +0 -317
  934. synth_ai/tracing_v3/turso/daemon.py +0 -151
  935. synth_ai/tracing_v3/turso/models.py +0 -469
  936. synth_ai/tracing_v3/turso/native_manager.py +0 -1209
  937. synth_ai/tracing_v3/utils.py +0 -108
  938. synth_ai/tui/__init__.py +0 -5
  939. synth_ai/tui/__main__.py +0 -13
  940. synth_ai/tui/cli/__init__.py +0 -1
  941. synth_ai/tui/cli/query_experiments.py +0 -164
  942. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  943. synth_ai/tui/dashboard.py +0 -906
  944. synth_ai/v0/api/__init__.py +0 -8
  945. synth_ai/v0/api/models/__init__.py +0 -8
  946. synth_ai/v0/api/models/supported.py +0 -8
  947. synth_ai/v0/config/__init__.py +0 -15
  948. synth_ai/v0/config/base_url.py +0 -12
  949. synth_ai/v0/lm/__init__.py +0 -51
  950. synth_ai/v0/lm/caching/__init__.py +0 -0
  951. synth_ai/v0/lm/caching/constants.py +0 -6
  952. synth_ai/v0/lm/caching/dbs.py +0 -0
  953. synth_ai/v0/lm/caching/ephemeral.py +0 -100
  954. synth_ai/v0/lm/caching/handler.py +0 -137
  955. synth_ai/v0/lm/caching/initialize.py +0 -11
  956. synth_ai/v0/lm/caching/persistent.py +0 -114
  957. synth_ai/v0/lm/config.py +0 -115
  958. synth_ai/v0/lm/constants.py +0 -32
  959. synth_ai/v0/lm/core/__init__.py +0 -8
  960. synth_ai/v0/lm/core/all.py +0 -73
  961. synth_ai/v0/lm/core/exceptions.py +0 -5
  962. synth_ai/v0/lm/core/main.py +0 -331
  963. synth_ai/v0/lm/core/main_v3.py +0 -594
  964. synth_ai/v0/lm/core/synth_models.py +0 -35
  965. synth_ai/v0/lm/core/vendor_clients.py +0 -190
  966. synth_ai/v0/lm/cost/__init__.py +0 -0
  967. synth_ai/v0/lm/cost/monitor.py +0 -1
  968. synth_ai/v0/lm/cost/statefulness.py +0 -1
  969. synth_ai/v0/lm/injection.py +0 -80
  970. synth_ai/v0/lm/overrides.py +0 -206
  971. synth_ai/v0/lm/provider_support/__init__.py +0 -8
  972. synth_ai/v0/lm/provider_support/anthropic.py +0 -972
  973. synth_ai/v0/lm/provider_support/openai.py +0 -1139
  974. synth_ai/v0/lm/provider_support/suppress_logging.py +0 -31
  975. synth_ai/v0/lm/structured_outputs/__init__.py +0 -0
  976. synth_ai/v0/lm/structured_outputs/handler.py +0 -440
  977. synth_ai/v0/lm/structured_outputs/inject.py +0 -297
  978. synth_ai/v0/lm/structured_outputs/rehabilitate.py +0 -185
  979. synth_ai/v0/lm/tools/__init__.py +0 -3
  980. synth_ai/v0/lm/tools/base.py +0 -172
  981. synth_ai/v0/lm/unified_interface.py +0 -202
  982. synth_ai/v0/lm/vendors/__init__.py +0 -0
  983. synth_ai/v0/lm/vendors/base.py +0 -81
  984. synth_ai/v0/lm/vendors/core/__init__.py +0 -0
  985. synth_ai/v0/lm/vendors/core/anthropic_api.py +0 -387
  986. synth_ai/v0/lm/vendors/core/gemini_api.py +0 -292
  987. synth_ai/v0/lm/vendors/core/mistral_api.py +0 -322
  988. synth_ai/v0/lm/vendors/core/openai_api.py +0 -227
  989. synth_ai/v0/lm/vendors/core/synth_dev_api.py +0 -0
  990. synth_ai/v0/lm/vendors/local/__init__.py +0 -0
  991. synth_ai/v0/lm/vendors/local/ollama.py +0 -0
  992. synth_ai/v0/lm/vendors/openai_standard.py +0 -782
  993. synth_ai/v0/lm/vendors/openai_standard_responses.py +0 -259
  994. synth_ai/v0/lm/vendors/retries.py +0 -22
  995. synth_ai/v0/lm/vendors/supported/__init__.py +0 -0
  996. synth_ai/v0/lm/vendors/supported/custom_endpoint.py +0 -415
  997. synth_ai/v0/lm/vendors/supported/deepseek.py +0 -69
  998. synth_ai/v0/lm/vendors/supported/grok.py +0 -75
  999. synth_ai/v0/lm/vendors/supported/groq.py +0 -16
  1000. synth_ai/v0/lm/vendors/supported/ollama.py +0 -15
  1001. synth_ai/v0/lm/vendors/supported/openrouter.py +0 -74
  1002. synth_ai/v0/lm/vendors/supported/together.py +0 -11
  1003. synth_ai/v0/lm/vendors/synth_client.py +0 -835
  1004. synth_ai/v0/lm/warmup.py +0 -186
  1005. synth_ai/v0/tracing/__init__.py +0 -0
  1006. synth_ai/v0/tracing/abstractions.py +0 -224
  1007. synth_ai/v0/tracing/base_client.py +0 -91
  1008. synth_ai/v0/tracing/client_manager.py +0 -131
  1009. synth_ai/v0/tracing/config.py +0 -142
  1010. synth_ai/v0/tracing/context.py +0 -146
  1011. synth_ai/v0/tracing/decorators.py +0 -682
  1012. synth_ai/v0/tracing/events/__init__.py +0 -0
  1013. synth_ai/v0/tracing/events/manage.py +0 -147
  1014. synth_ai/v0/tracing/events/scope.py +0 -86
  1015. synth_ai/v0/tracing/events/store.py +0 -228
  1016. synth_ai/v0/tracing/immediate_client.py +0 -151
  1017. synth_ai/v0/tracing/local.py +0 -18
  1018. synth_ai/v0/tracing/log_client_base.py +0 -73
  1019. synth_ai/v0/tracing/retry_queue.py +0 -186
  1020. synth_ai/v0/tracing/trackers.py +0 -515
  1021. synth_ai/v0/tracing/upload.py +0 -409
  1022. synth_ai/v0/tracing/utils.py +0 -9
  1023. synth_ai/v0/tracing_v1/__init__.py +0 -16
  1024. synth_ai/v0/tracing_v1/abstractions.py +0 -224
  1025. synth_ai/v0/tracing_v1/base_client.py +0 -91
  1026. synth_ai/v0/tracing_v1/client_manager.py +0 -131
  1027. synth_ai/v0/tracing_v1/config.py +0 -142
  1028. synth_ai/v0/tracing_v1/context.py +0 -146
  1029. synth_ai/v0/tracing_v1/decorators.py +0 -703
  1030. synth_ai/v0/tracing_v1/events/__init__.py +0 -0
  1031. synth_ai/v0/tracing_v1/events/manage.py +0 -147
  1032. synth_ai/v0/tracing_v1/events/scope.py +0 -86
  1033. synth_ai/v0/tracing_v1/events/store.py +0 -228
  1034. synth_ai/v0/tracing_v1/immediate_client.py +0 -151
  1035. synth_ai/v0/tracing_v1/local.py +0 -18
  1036. synth_ai/v0/tracing_v1/log_client_base.py +0 -73
  1037. synth_ai/v0/tracing_v1/retry_queue.py +0 -186
  1038. synth_ai/v0/tracing_v1/trackers.py +0 -515
  1039. synth_ai/v0/tracing_v1/upload.py +0 -527
  1040. synth_ai/v0/tracing_v1/utils.py +0 -9
  1041. synth_ai/v0/tracing_v3/__init__.py +0 -10
  1042. synth_ai/v0/tracing_v3/abstractions.py +0 -3
  1043. synth_ai/v0/tracing_v3/decorators.py +0 -3
  1044. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +0 -3
  1045. synth_ai/v0/tracing_v3/session_tracer.py +0 -3
  1046. synth_ai-0.2.14.dist-info/METADATA +0 -139
  1047. synth_ai-0.2.14.dist-info/RECORD +0 -762
  1048. synth_ai-0.2.14.dist-info/top_level.txt +0 -2
  1049. /synth_ai/{demos/demo_task_apps → cli/demo_apps}/crafter/__init__.py +0 -0
  1050. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/__init__.py +0 -0
  1051. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/crafter_fft_4b.toml +0 -0
  1052. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +0 -0
  1053. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
  1054. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/_common.py +0 -0
  1055. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/app.py +0 -0
  1056. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/deploy_modal.py +0 -0
  1057. {examples/task_apps → synth_ai/core/apps}/__init__.py +0 -0
  1058. /synth_ai/{tracing_v3 → core/tracing_v3}/examples/basic_usage.py +0 -0
  1059. /synth_ai/{tracing_v3 → core/tracing_v3}/hooks.py +0 -0
  1060. /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
  1061. /synth_ai/{tracing_v3 → core/tracing_v3}/replica_sync.py +0 -0
  1062. /synth_ai/{tracing_v3 → core/tracing_v3}/serialization.py +0 -0
  1063. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
  1064. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
  1065. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
  1066. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/utils.py +0 -0
  1067. /synth_ai/{tracing_v3 → core/tracing_v3}/turso/__init__.py +0 -0
  1068. /synth_ai/{learning → sdk/learning}/algorithms.py +0 -0
  1069. /synth_ai/{learning → sdk/learning}/config.py +0 -0
  1070. /synth_ai/{learning → sdk/learning}/constants.py +0 -0
  1071. /synth_ai/{learning → sdk/learning}/core.py +0 -0
  1072. /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
  1073. /synth_ai/{learning → sdk/learning}/rl/config.py +0 -0
  1074. /synth_ai/{learning → sdk/learning}/rl_client.py +0 -0
  1075. /synth_ai/{learning → sdk/learning}/sft/__init__.py +0 -0
  1076. /synth_ai/{learning → sdk/learning}/sse.py +0 -0
  1077. /synth_ai/{task → sdk/task}/auth.py +0 -0
  1078. /synth_ai/{task → sdk/task}/errors.py +0 -0
  1079. /synth_ai/{task → sdk/task}/health.py +0 -0
  1080. /synth_ai/{task → sdk/task}/json.py +0 -0
  1081. /synth_ai/{task → sdk/task}/rubrics/models.py +0 -0
  1082. /synth_ai/{task → sdk/task}/rubrics/scoring.py +0 -0
  1083. /synth_ai/{task → sdk/task}/vendors.py +0 -0
  1084. {synth_ai-0.2.14.dist-info → synth_ai-0.4.4.dist-info}/WHEEL +0 -0
  1085. {synth_ai-0.2.14.dist-info → synth_ai-0.4.4.dist-info}/entry_points.txt +0 -0
  1086. {synth_ai-0.2.14.dist-info → synth_ai-0.4.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,1763 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Map Stitching System for Pokemon Emerald
4
-
5
- Connects previously seen map areas with warps and transitions to create
6
- a unified world map showing connections between routes, towns, and buildings.
7
- """
8
-
9
- import json
10
- import logging
11
- import os
12
- from typing import Dict, List, Tuple, Optional, Set, Any
13
- from dataclasses import dataclass, asdict
14
- from pathlib import Path
15
- from pokemon_env.enums import MapLocation, MetatileBehavior
16
- from utils import state_formatter
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
- @dataclass
21
- class WarpConnection:
22
- """Represents a connection between two map areas"""
23
- from_map_id: int # (map_bank << 8) | map_number
24
- to_map_id: int
25
- from_position: Tuple[int, int] # Player position when warp triggered
26
- to_position: Tuple[int, int] # Player position after warp
27
- warp_type: str # "door", "stairs", "exit", "route_transition"
28
- direction: str # "north", "south", "east", "west", "up", "down"
29
-
30
- def get_reverse_connection(self) -> 'WarpConnection':
31
- """Get the reverse direction of this warp"""
32
- reverse_dirs = {
33
- "north": "south", "south": "north",
34
- "east": "west", "west": "east",
35
- "up": "down", "down": "up"
36
- }
37
- return WarpConnection(
38
- from_map_id=self.to_map_id,
39
- to_map_id=self.from_map_id,
40
- from_position=self.to_position,
41
- to_position=self.from_position,
42
- warp_type=self.warp_type,
43
- direction=reverse_dirs.get(self.direction, "unknown")
44
- )
45
-
46
- @dataclass
47
- class MapArea:
48
- """Represents a single map area with its data"""
49
- map_id: int # (map_bank << 8) | map_number
50
- location_name: str
51
- map_data: List[List[Tuple]] # Raw tile data from memory
52
- player_last_position: Tuple[int, int] # Last known player position
53
- warp_tiles: List[Tuple[int, int, str]] # (x, y, warp_type) positions
54
- boundaries: Dict[str, int] # north, south, east, west limits
55
- visited_count: int
56
- first_seen: float # timestamp
57
- last_seen: float # timestamp
58
- overworld_coords: Optional[Tuple[int, int]] = None # (X, Y) in overworld coordinate system
59
-
60
- def get_map_bounds(self) -> Tuple[int, int, int, int]:
61
- """Return (min_x, min_y, max_x, max_y) for this map"""
62
- height = len(self.map_data)
63
- width = len(self.map_data[0]) if height > 0 else 0
64
- return (0, 0, width - 1, height - 1)
65
-
66
- def has_warp_at(self, x: int, y: int) -> Optional[str]:
67
- """Check if there's a warp at the given position"""
68
- for wx, wy, warp_type in self.warp_tiles:
69
- if wx == x and wy == y:
70
- return warp_type
71
- return None
72
-
73
- class MapStitcher:
74
- """Main class for managing map stitching and connections"""
75
-
76
- def __init__(self, save_file: str = None):
77
- # Setup cache directory
78
- self.cache_dir = ".pokeagent_cache"
79
- os.makedirs(self.cache_dir, exist_ok=True)
80
-
81
- # Use cache folder for default save file
82
- if save_file is None:
83
- save_file = os.path.join(self.cache_dir, "map_stitcher_data.json")
84
- self.save_file = Path(save_file)
85
- self.map_areas: Dict[int, MapArea] = {}
86
- self.warp_connections: List[WarpConnection] = []
87
- self.pending_warps: List[Dict] = [] # Track potential warps
88
- self.last_map_id: Optional[int] = None
89
- self.last_position: Optional[Tuple[int, int]] = None
90
-
91
- # Load existing data
92
- self.load_from_file()
93
-
94
- def _merge_map_tiles(self, area: MapArea, new_tiles: List[List[Tuple]], player_pos: Tuple[int, int]):
95
- """Merge new tiles into existing map data, building up complete map over time.
96
-
97
- This is the core stitching logic - it takes the new 15x15 view around
98
- the player and merges it into the accumulated map data for this area.
99
- """
100
- if not new_tiles:
101
- return
102
-
103
- # Get dimensions of new tile data (usually 15x15)
104
- new_height = len(new_tiles)
105
- new_width = len(new_tiles[0]) if new_tiles else 0
106
-
107
- if new_height == 0 or new_width == 0:
108
- return
109
-
110
- # Calculate the offset of the new tiles relative to player position
111
- # The player is at the center of the new tiles
112
- center_y = new_height // 2
113
- center_x = new_width // 2
114
-
115
- # If this is the first data for this area, initialize with a large empty grid
116
- if area.map_data is None or not area.map_data:
117
- # Create a 100x100 grid initially (can expand as needed)
118
- # Use None to indicate unexplored tiles
119
- area.map_data = [[None for _ in range(100)] for _ in range(100)]
120
- # Track the actual bounds of explored area
121
- area.explored_bounds = {
122
- 'min_x': 50, 'max_x': 50,
123
- 'min_y': 50, 'max_y': 50
124
- }
125
- # Place player at center of our coordinate system initially
126
- area.origin_offset = {'x': 50 - player_pos[0], 'y': 50 - player_pos[1]}
127
-
128
- # Ensure origin_offset exists
129
- if not hasattr(area, 'origin_offset'):
130
- area.origin_offset = {'x': 50 - player_pos[0], 'y': 50 - player_pos[1]}
131
-
132
- # Get the offset to map player coordinates to our stored grid
133
- offset_x = area.origin_offset.get('x', 0)
134
- offset_y = area.origin_offset.get('y', 0)
135
-
136
- # CRITICAL: Check for unreasonable coordinate jumps that indicate a map transition error
137
- # If the player position would require massive grid expansion, it's likely a different map
138
- grid_center_x = player_pos[0] + offset_x
139
- grid_center_y = player_pos[1] + offset_y
140
-
141
- MAX_REASONABLE_SIZE = 200 # Maximum reasonable size for a single map area
142
-
143
- # Check if this would cause unreasonable expansion
144
- if (grid_center_x < -50 or grid_center_x > MAX_REASONABLE_SIZE + 50 or
145
- grid_center_y < -50 or grid_center_y > MAX_REASONABLE_SIZE + 50):
146
- logger.warning(f"Detected unreasonable coordinate jump for map {area.map_id:04X}: "
147
- f"player at {player_pos}, grid position would be ({grid_center_x}, {grid_center_y})")
148
- logger.warning(f"This likely indicates map areas are being incorrectly merged. "
149
- f"Resetting origin offset for this area.")
150
-
151
- # Reset the map data for this area to prevent corruption
152
- area.map_data = [[None for _ in range(100)] for _ in range(100)]
153
- area.explored_bounds = {
154
- 'min_x': 50, 'max_x': 50,
155
- 'min_y': 50, 'max_y': 50
156
- }
157
- area.origin_offset = {'x': 50 - player_pos[0], 'y': 50 - player_pos[1]}
158
- offset_x = area.origin_offset['x']
159
- offset_y = area.origin_offset['y']
160
-
161
- # Merge the new tiles into the existing map
162
- for dy in range(new_height):
163
- for dx in range(new_width):
164
- # Calculate the world position of this tile
165
- world_x = player_pos[0] - center_x + dx
166
- world_y = player_pos[1] - center_y + dy
167
-
168
- # Calculate the position in our stored grid
169
- grid_x = world_x + offset_x
170
- grid_y = world_y + offset_y
171
-
172
- # Sanity check to prevent excessive memory usage
173
- if grid_x < 0 or grid_y < 0 or grid_x >= MAX_REASONABLE_SIZE or grid_y >= MAX_REASONABLE_SIZE:
174
- logger.debug(f"Skipping tile at grid position ({grid_x}, {grid_y}) - out of reasonable bounds")
175
- continue
176
-
177
- # Expand grid if necessary (but within reasonable limits)
178
- if grid_y >= len(area.map_data) and grid_y < MAX_REASONABLE_SIZE:
179
- # Expand vertically
180
- expansion_needed = min(grid_y - len(area.map_data) + 1,
181
- MAX_REASONABLE_SIZE - len(area.map_data))
182
- for _ in range(expansion_needed):
183
- area.map_data.append([None] * len(area.map_data[0]))
184
-
185
- if grid_x >= len(area.map_data[0]) and grid_x < MAX_REASONABLE_SIZE:
186
- # Expand horizontally
187
- new_width_needed = min(grid_x + 1, MAX_REASONABLE_SIZE)
188
- for row in area.map_data:
189
- expansion = new_width_needed - len(row)
190
- if expansion > 0:
191
- row.extend([None] * expansion)
192
-
193
- # Store the tile (always update with latest data)
194
- if 0 <= grid_x < len(area.map_data[0]) and 0 <= grid_y < len(area.map_data):
195
- tile = new_tiles[dy][dx]
196
- # Store all tiles including 1023 (which represents walls/boundaries)
197
- # The display logic will handle showing them correctly
198
- if tile:
199
- area.map_data[grid_y][grid_x] = tile
200
-
201
- # Update explored bounds for all tiles including boundaries
202
- # tile_id 1023 represents trees/walls at map edges - we want to include these
203
- tile_id = tile[0] if tile and len(tile) > 0 else None
204
- if tile_id is not None: # Include all tiles, even 1023
205
- if not hasattr(area, 'explored_bounds'):
206
- area.explored_bounds = {
207
- 'min_x': grid_x, 'max_x': grid_x,
208
- 'min_y': grid_y, 'max_y': grid_y
209
- }
210
- else:
211
- area.explored_bounds['min_x'] = min(area.explored_bounds['min_x'], grid_x)
212
- area.explored_bounds['max_x'] = max(area.explored_bounds['max_x'], grid_x)
213
- area.explored_bounds['min_y'] = min(area.explored_bounds['min_y'], grid_y)
214
- area.explored_bounds['max_y'] = max(area.explored_bounds['max_y'], grid_y)
215
-
216
- def get_map_id(self, map_bank: int, map_number: int) -> int:
217
- """Convert map bank/number to unique ID"""
218
- return (map_bank << 8) | map_number
219
-
220
- def decode_map_id(self, map_id: int) -> Tuple[int, int]:
221
- """Convert map ID back to bank/number"""
222
- return (map_id >> 8, map_id & 0xFF)
223
-
224
- def update_save_file(self, new_save_file: str):
225
- """Update the save file path and reload data"""
226
- self.save_file = Path(new_save_file)
227
- # Clear current data and reload from new file
228
- self.map_areas = {}
229
- self.warp_connections = []
230
- self.pending_warps = []
231
- self.load_from_file()
232
-
233
- def update_map_area(self, map_bank: int, map_number: int, location_name: str,
234
- map_data: List[List[Tuple]], player_pos: Tuple[int, int],
235
- timestamp: float, overworld_coords: Optional[Tuple[int, int]] = None):
236
- """Update or create a map area with new data"""
237
- map_id = self.get_map_id(map_bank, map_number)
238
-
239
- # Skip map 0 (startup/initialization state) as it's not a real location
240
- if map_id == 0:
241
- logger.debug(f"Skipping map 0 (startup state)")
242
- return
243
-
244
- # Validate map ID is reasonable
245
- if map_id < 0 or map_id > 0xFFFF:
246
- logger.error(f"Invalid map ID {map_id} from bank={map_bank}, number={map_number}")
247
- return
248
-
249
- # Validate player position - check for invalid values
250
- if player_pos:
251
- px, py = player_pos
252
- # Check for invalid coordinates (65535 = 0xFFFF is a common error value)
253
- if px < 0 or px > 1000 or py < 0 or py > 1000 or px == 0xFFFF or py == 0xFFFF:
254
- logger.warning(f"Invalid player position {player_pos} for map {map_id:04X}, ignoring update")
255
- return
256
-
257
- if map_id in self.map_areas:
258
- # Update existing area - we're revisiting this location
259
- area = self.map_areas[map_id]
260
- logger.info(f"Revisiting existing map area {area.location_name} (ID: {map_id:04X})")
261
- area.visited_count = getattr(area, 'visited_count', 0) + 1
262
- # Update location name if we have a better one (not empty or "Unknown")
263
- if location_name and location_name.strip() and location_name != "Unknown":
264
- if area.location_name == "Unknown" or not area.location_name:
265
- logger.info(f"Updating location name for map {map_id:04X}: '{area.location_name}' -> '{location_name}'")
266
- area.location_name = location_name
267
- # Try to resolve other unknown names since we got new location info
268
- self.resolve_unknown_location_names()
269
- elif area.location_name != location_name:
270
- # Check if this is a significant name difference that might indicate a problem
271
- name1_words = set(area.location_name.lower().split())
272
- name2_words = set(location_name.lower().split())
273
-
274
- # If the names share no common words, this might be a misidentified map
275
- if not name1_words.intersection(name2_words):
276
- logger.warning(f"Significant location name mismatch for map {map_id:04X}: "
277
- f"existing='{area.location_name}' vs new='{location_name}'. "
278
- f"This might indicate incorrect map identification.")
279
- else:
280
- logger.info(f"Found different location name for map {map_id:04X}: '{area.location_name}' vs '{location_name}', keeping current")
281
- else:
282
- area.location_name = location_name
283
-
284
- # MERGE map data instead of replacing - this is the key to stitching!
285
- if map_data and player_pos:
286
- # When revisiting, check if player position makes sense with existing map
287
- if hasattr(area, 'origin_offset') and area.origin_offset:
288
- expected_grid_x = player_pos[0] + area.origin_offset['x']
289
- expected_grid_y = player_pos[1] + area.origin_offset['y']
290
-
291
- # Check if player position is reasonable for this map
292
- if (0 <= expected_grid_x <= 200 and 0 <= expected_grid_y <= 200):
293
- # Position is reasonable - merge tiles
294
- self._merge_map_tiles(area, map_data, player_pos)
295
- logger.debug(f"Merged {len(map_data) * len(map_data[0]) if map_data else 0} new tiles into area")
296
- else:
297
- logger.warning(f"Player position {player_pos} seems incorrect for map {map_id:04X} "
298
- f"(would be at grid {expected_grid_x},{expected_grid_y})")
299
- else:
300
- # First visit to this area after loading - merge normally
301
- self._merge_map_tiles(area, map_data, player_pos)
302
- logger.debug(f"Merged {len(map_data) * len(map_data[0]) if map_data else 0} new tiles into area")
303
-
304
- area.player_last_position = player_pos
305
- area.last_seen = timestamp
306
- # Remove deprecated fields - keep it simple
307
- logger.debug(f"Updated map area {area.location_name} (ID: {map_id:04X})")
308
- else:
309
- # Create new area
310
- # Try to resolve location name from map ID if empty
311
- if not location_name or not location_name.strip():
312
- # Import and use the location mapping
313
- try:
314
- map_enum = MapLocation(map_id)
315
- final_location_name = map_enum.name.replace('_', ' ').title()
316
- logger.info(f"Resolved location name for map {map_id:04X}: {final_location_name}")
317
- except ValueError:
318
- # Fallback for unknown map IDs
319
- final_location_name = f"Map_{map_id:04X}"
320
- logger.debug(f"Unknown map ID {map_id:04X}, using fallback name")
321
- else:
322
- final_location_name = location_name
323
-
324
- area = MapArea(
325
- map_id=map_id,
326
- location_name=final_location_name,
327
- map_data=None, # Start with empty data - will be populated by merge
328
- player_last_position=player_pos,
329
- warp_tiles=[], # Deprecated - not needed
330
- boundaries={"north": 0, "south": 10, "west": 0, "east": 10}, # Simple default
331
- visited_count=1,
332
- first_seen=timestamp,
333
- last_seen=timestamp,
334
- overworld_coords=None # Not needed
335
- )
336
- self.map_areas[map_id] = area
337
-
338
- # Now merge the initial tiles
339
- if map_data and player_pos:
340
- self._merge_map_tiles(area, map_data, player_pos)
341
- logger.debug(f"Initialized new area with {len(map_data) * len(map_data[0]) if map_data else 0} tiles")
342
- logger.info(f"Added new map area: {final_location_name} (ID: {map_id:04X}) as separate location")
343
-
344
- # Check for area transitions and potential warp connections
345
- # print(f"🔍 Transition check: last_map_id={self.last_map_id}, current_map_id={map_id}, last_pos={self.last_position}, current_pos={player_pos}")
346
- if self.last_map_id is not None and self.last_map_id != map_id:
347
- logger.info(f"🔄 Map transition detected! {self.last_map_id} -> {map_id}")
348
-
349
- # Use the last position stored in the previous map area for the from_pos
350
- # This is the actual exit point from the previous map
351
- from_area = self.map_areas.get(self.last_map_id)
352
- from_pos = from_area.player_last_position if from_area else self.last_position
353
-
354
- logger.info(f"🔄 Warp coordinates: from_pos={from_pos} (exit from map {self.last_map_id}), to_pos={player_pos} (entry to map {map_id})")
355
- self._detect_warp_connection(self.last_map_id, map_id,
356
- from_pos, player_pos, timestamp)
357
-
358
- # Try to resolve any unknown location names after adding connections
359
- # Note: resolve_unknown_location_names() can be called with memory_reader from calling code
360
- if self.resolve_unknown_location_names():
361
- logger.info("Resolved unknown location names after area transition")
362
- # Save will be handled by the calling code
363
-
364
- # Update tracking variables for next iteration
365
- if self.last_position != player_pos:
366
- self.last_map_id = map_id
367
- self.last_position = player_pos
368
-
369
- def _detect_warp_tiles(self, map_data: List[List[Tuple]]) -> List[Tuple[int, int, str]]:
370
- """Detect tiles that can be warps (doors, stairs, exits)"""
371
- warp_tiles = []
372
-
373
- for y, row in enumerate(map_data):
374
- for x, tile in enumerate(row):
375
- if len(tile) >= 2:
376
- tile_id, behavior = tile[:2]
377
-
378
- if hasattr(behavior, 'name'):
379
- behavior_name = behavior.name
380
- elif isinstance(behavior, int):
381
- try:
382
- behavior_enum = MetatileBehavior(behavior)
383
- behavior_name = behavior_enum.name
384
- except ValueError:
385
- continue
386
- else:
387
- continue
388
-
389
- # Classify warp types
390
- warp_type = None
391
- if "DOOR" in behavior_name:
392
- warp_type = "door"
393
- elif "STAIRS" in behavior_name:
394
- warp_type = "stairs"
395
- elif "WARP" in behavior_name:
396
- warp_type = "warp"
397
- elif x == 0 or x == len(row) - 1 or y == 0 or y == len(map_data) - 1:
398
- # Edge tiles might be exits to other routes/areas
399
- if behavior_name == "NORMAL" and tile[2] == 0: # collision == 0
400
- warp_type = "exit"
401
-
402
- if warp_type:
403
- warp_tiles.append((x, y, warp_type))
404
-
405
- return warp_tiles
406
-
407
- def _calculate_boundaries(self, map_data: List[List[Tuple]]) -> Dict[str, int]:
408
- """Calculate walkable boundaries of the map"""
409
- height = len(map_data)
410
- width = len(map_data[0]) if height > 0 else 0
411
-
412
- return {
413
- "north": 0,
414
- "south": height - 1,
415
- "west": 0,
416
- "east": width - 1
417
- }
418
-
419
- def _detect_warp_connection(self, from_map_id: int, to_map_id: int,
420
- from_pos: Optional[Tuple[int, int]],
421
- to_pos: Tuple[int, int], timestamp: float):
422
- """Detect and record warp connections between maps"""
423
- if from_pos is None:
424
- return
425
-
426
- from_area = self.map_areas.get(from_map_id)
427
- to_area = self.map_areas.get(to_map_id)
428
-
429
- if not from_area or not to_area:
430
- return
431
-
432
- # Determine warp type and direction
433
- warp_type = "route_transition" # default
434
- direction = self._determine_warp_direction(from_area, to_area, from_pos, to_pos)
435
-
436
- # Check if we were near a warp tile
437
- near_warp = from_area.has_warp_at(from_pos[0], from_pos[1])
438
- if near_warp:
439
- warp_type = near_warp
440
-
441
- # Create the connection
442
- print(f"🔄 Creating warp connection: {from_pos} -> {to_pos} (maps {from_map_id} -> {to_map_id})")
443
- connection = WarpConnection(
444
- from_map_id=from_map_id,
445
- to_map_id=to_map_id,
446
- from_position=from_pos,
447
- to_position=to_pos,
448
- warp_type=warp_type,
449
- direction=direction
450
- )
451
-
452
- # Check if this connection already exists
453
- if not self._connection_exists(connection):
454
- self.warp_connections.append(connection)
455
- print(f"Added warp connection: {from_area.location_name} -> {to_area.location_name} "
456
- f"({warp_type}, {direction})")
457
-
458
- # Auto-add reverse connection for two-way warps
459
- if warp_type in ["door", "stairs", "route_transition"]:
460
- reverse = connection.get_reverse_connection()
461
- if not self._connection_exists(reverse):
462
- self.warp_connections.append(reverse)
463
- logger.debug(f"Added reverse connection: {to_area.location_name} -> {from_area.location_name}")
464
-
465
- def _determine_warp_direction(self, from_area: MapArea, to_area: MapArea,
466
- from_pos: Tuple[int, int], to_pos: Tuple[int, int]) -> str:
467
- """Determine the direction of movement for a warp"""
468
- from_x, from_y = from_pos
469
- to_x, to_y = to_pos
470
-
471
- # Check if this is a vertical building transition (indoors <-> outdoors)
472
- from_indoor = from_area.location_name and ("HOUSE" in from_area.location_name.upper() or "ROOM" in from_area.location_name.upper())
473
- to_indoor = to_area.location_name and ("HOUSE" in to_area.location_name.upper() or "ROOM" in to_area.location_name.upper())
474
-
475
- if from_indoor and not to_indoor:
476
- return "down" # Exiting building
477
- elif not from_indoor and to_indoor:
478
- return "up" # Entering building
479
-
480
- # For horizontal transitions, compare positions
481
- from_bounds = from_area.get_map_bounds()
482
- to_bounds = to_area.get_map_bounds()
483
-
484
- # Simple heuristic based on position relative to map center
485
- from_center_x = (from_bounds[2] - from_bounds[0]) // 2
486
- from_center_y = (from_bounds[3] - from_bounds[1]) // 2
487
-
488
- if from_x < from_center_x:
489
- return "west"
490
- elif from_x > from_center_x:
491
- return "east"
492
- elif from_y < from_center_y:
493
- return "north"
494
- else:
495
- return "south"
496
-
497
- def _connection_exists(self, connection: WarpConnection) -> bool:
498
- """Check if a similar connection already exists"""
499
- for existing in self.warp_connections:
500
- if (existing.from_map_id == connection.from_map_id and
501
- existing.to_map_id == connection.to_map_id and
502
- existing.warp_type == connection.warp_type):
503
- return True
504
- return False
505
-
506
- def _infer_overworld_coordinates(self, location_name: str, player_pos: Tuple[int, int]) -> Optional[Tuple[int, int]]:
507
- """Infer overworld coordinates - should return None to keep coordinates unknown until discovered"""
508
- # All coordinates start as unknown (?, ?) until actually discovered
509
- # This ensures authentic exploration without pre-existing knowledge
510
- return None
511
-
512
- def update_overworld_coordinates(self, map_id: int, coords: Tuple[int, int]):
513
- """Update overworld coordinates for a discovered area"""
514
- if map_id in self.map_areas:
515
- self.map_areas[map_id].overworld_coords = coords
516
- logger.info(f"Updated coordinates for {self.map_areas[map_id].location_name}: {coords}")
517
-
518
- def update_location_name(self, map_id: int, location_name: str):
519
- """Update location name for an existing area"""
520
- if map_id in self.map_areas and location_name and location_name.strip() and location_name != "Unknown":
521
- area = self.map_areas[map_id]
522
- if area.location_name == "Unknown" or not area.location_name:
523
- logger.info(f"Updating location name for map {map_id:04X}: '{area.location_name}' -> '{location_name}'")
524
- area.location_name = location_name
525
- # Try to resolve other unknown names since we got new location info
526
- self.resolve_unknown_location_names()
527
- return True
528
- return False
529
-
530
- def resolve_unknown_location_names(self, memory_reader=None):
531
- """Try to resolve 'Unknown' location names using the memory reader if available"""
532
- resolved_count = 0
533
-
534
- # If we have a memory reader, we can potentially resolve current location
535
- if memory_reader is not None:
536
- try:
537
- current_location = memory_reader.read_location()
538
- current_map_bank = memory_reader._read_u8(memory_reader.addresses.MAP_BANK)
539
- current_map_number = memory_reader._read_u8(memory_reader.addresses.MAP_NUMBER)
540
- current_map_id = (current_map_bank << 8) | current_map_number
541
-
542
- # Update current map if it's unknown
543
- if current_map_id in self.map_areas:
544
- area = self.map_areas[current_map_id]
545
- if area.location_name == "Unknown" and current_location and current_location.strip() and current_location != "Unknown":
546
- old_name = area.location_name
547
- area.location_name = current_location
548
- logger.info(f"Resolved current location name for map {current_map_id:04X}: '{old_name}' -> '{area.location_name}'")
549
- resolved_count += 1
550
- except Exception as e:
551
- logger.debug(f"Could not resolve current location: {e}")
552
-
553
- if resolved_count > 0:
554
- logger.info(f"Resolved {resolved_count} unknown location names")
555
- return True
556
- return False
557
-
558
- def get_connected_areas(self, map_id: int) -> List[Tuple[int, str, str]]:
559
- """Get all areas connected to the given map ID"""
560
- connections = []
561
- for conn in self.warp_connections:
562
- if conn.from_map_id == map_id:
563
- to_area = self.map_areas.get(conn.to_map_id)
564
- if to_area:
565
- connections.append((conn.to_map_id, to_area.location_name, conn.direction))
566
- return connections
567
-
568
- def get_world_map_layout(self) -> Dict[str, Any]:
569
- """Generate a layout showing how all areas connect"""
570
- layout = {
571
- "areas": {},
572
- "connections": []
573
- }
574
-
575
- # Add all known areas
576
- for map_id, area in self.map_areas.items():
577
- layout["areas"][f"{map_id:04X}"] = {
578
- "name": area.location_name,
579
- "position": area.player_last_position,
580
- "bounds": area.boundaries,
581
- "warp_count": len(area.warp_tiles),
582
- "visited_count": area.visited_count
583
- }
584
-
585
- # Add all connections
586
- for conn in self.warp_connections:
587
- from_area = self.map_areas.get(conn.from_map_id)
588
- to_area = self.map_areas.get(conn.to_map_id)
589
- if from_area and to_area:
590
- layout["connections"].append({
591
- "from": f"{conn.from_map_id:04X}",
592
- "to": f"{conn.to_map_id:04X}",
593
- "from_name": from_area.location_name,
594
- "to_name": to_area.location_name,
595
- "warp_type": conn.warp_type,
596
- "direction": conn.direction,
597
- "from_pos": conn.from_position,
598
- "to_pos": conn.to_position
599
- })
600
-
601
- return layout
602
-
603
- def get_player_position_for_location(self, location_name: str) -> Optional[Tuple[int, int]]:
604
- """Get the last known player position for a specific location.
605
-
606
- Returns:
607
- Tuple of (x, y) coordinates or None if not found or invalid
608
- """
609
- # Find the map area with this location name
610
- for area in self.map_areas.values():
611
- if area.location_name and location_name and area.location_name.lower() == location_name.lower():
612
- if hasattr(area, 'player_last_position') and area.player_last_position:
613
- px, py = area.player_last_position
614
- # Validate the position
615
- if px >= 0 and px < 1000 and py >= 0 and py < 1000 and px != 0xFFFF and py != 0xFFFF:
616
- return (px, py)
617
- break
618
- return None
619
-
620
- def get_location_connections(self, location_name=None):
621
- """Get connections for a specific location or all locations.
622
-
623
- Args:
624
- location_name: Optional location name to get connections for.
625
- If None, returns all location connections.
626
-
627
- Returns:
628
- If location_name provided: List of (to_location, from_coords, to_coords) tuples
629
- Otherwise: Dict mapping location names to connection lists
630
- """
631
- location_connections = {}
632
-
633
- # Process each warp connection
634
- for conn in self.warp_connections:
635
- from_area = self.map_areas.get(conn.from_map_id)
636
- to_area = self.map_areas.get(conn.to_map_id)
637
-
638
- if from_area and to_area:
639
- from_location = from_area.location_name
640
- to_location = to_area.location_name
641
-
642
- # Add forward connection
643
- if from_location not in location_connections:
644
- location_connections[from_location] = []
645
-
646
- # Check if connection already exists
647
- exists = False
648
- for existing in location_connections[from_location]:
649
- if existing[0] == to_location:
650
- exists = True
651
- break
652
-
653
- if not exists:
654
- # Use the actual last positions from map areas, not the warp spawn point
655
- # This gives more useful information about where transitions happen
656
- from_pos = list(conn.from_position) if conn.from_position else [1, 1]
657
- to_pos = list(to_area.player_last_position) if to_area.player_last_position else list(conn.to_position)
658
-
659
- location_connections[from_location].append([
660
- to_location,
661
- from_pos,
662
- to_pos
663
- ])
664
-
665
- # If specific location requested, return just its connections (case-insensitive)
666
- if location_name:
667
- # Try to find the location with case-insensitive matching
668
- for loc_name, connections in location_connections.items():
669
- if loc_name and loc_name.lower() == location_name.lower():
670
- return connections
671
- return []
672
-
673
- return location_connections
674
-
675
- def get_location_grid(self, location_name: str, simplified: bool = True) -> Dict[Tuple[int, int], str]:
676
- """Get a simplified grid representation of a location for display.
677
-
678
- Args:
679
- location_name: Name of the location to get grid for
680
- simplified: If True, return simplified symbols (., #, D, etc.), otherwise raw tile data
681
-
682
- Returns:
683
- Dictionary mapping (x, y) coordinates to tile symbols
684
- """
685
- # Find the map area with this location name (case-insensitive)
686
- map_area = None
687
- for area in self.map_areas.values():
688
- if area.location_name and location_name and area.location_name.lower() == location_name.lower():
689
- map_area = area
690
- break
691
-
692
- if not map_area:
693
- # Debug: print available locations
694
- logger.debug(f"Could not find map area for '{location_name}'")
695
- logger.debug(f"Available locations: {[a.location_name for a in self.map_areas.values() if a.location_name][:5]}")
696
- return {}
697
-
698
- if not map_area.map_data:
699
- logger.debug(f"Map area found for '{location_name}' but has no map_data")
700
- return {}
701
-
702
- grid = {}
703
-
704
- # If we have explored bounds, use them to extract only the explored portion
705
- if hasattr(map_area, 'explored_bounds'):
706
- bounds = map_area.explored_bounds
707
- for y in range(bounds['min_y'], bounds['max_y'] + 1):
708
- for x in range(bounds['min_x'], bounds['max_x'] + 1):
709
- if y < len(map_area.map_data) and x < len(map_area.map_data[0]):
710
- tile = map_area.map_data[y][x]
711
- if tile is not None: # Only include explored tiles
712
- # Adjust coordinates to be relative to the explored area
713
- rel_x = x - bounds['min_x']
714
- rel_y = y - bounds['min_y']
715
-
716
- if simplified:
717
- # Convert to simplified symbol
718
- symbol = self._tile_to_symbol(tile)
719
- if symbol is not None: # Only add if it's a valid tile
720
- # Debug specific problematic position
721
- if rel_x == 2 and rel_y == 1:
722
- logger.debug(f"Tile at rel(2,1) from grid[{y}][{x}]: {tile[:3] if len(tile) >= 3 else tile} -> symbol '{symbol}'")
723
- grid[(rel_x, rel_y)] = symbol
724
- else:
725
- grid[(rel_x, rel_y)] = tile
726
-
727
- # Add '?' for unexplored but adjacent tiles
728
- if simplified:
729
- # Find all positions adjacent to explored walkable tiles
730
- to_check = set()
731
- for (x, y), symbol in list(grid.items()):
732
- # Only add ? next to truly walkable tiles, not walls
733
- if symbol in ['.', 'D', 'S', '^', '~', 's', 'I', # Walkable terrain
734
- '→', '←', '↑', '↓', '↗', '↖', '↘', '↙']: # Ledges
735
- # Check all 4 adjacent positions (not diagonal)
736
- for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
737
- adj_pos = (x + dx, y + dy)
738
- if adj_pos not in grid:
739
- to_check.add(adj_pos)
740
-
741
- # Add '?' for these unexplored adjacent positions
742
- for pos in to_check:
743
- grid[pos] = '?'
744
-
745
- return grid
746
-
747
- # Fallback: old logic for non-accumulated maps
748
- # Check if we should extract a focused area from the stored map
749
- extract_bounds = getattr(map_area, '_display_extract_bounds', None)
750
- if extract_bounds:
751
- extract_start_x, extract_start_y, display_size = extract_bounds
752
- # Extract only the specified area
753
- for y in range(display_size):
754
- for x in range(display_size):
755
- stored_y = extract_start_y + y
756
- stored_x = extract_start_x + x
757
- if (stored_y < len(map_area.map_data) and
758
- stored_x < len(map_area.map_data[stored_y])):
759
- tile = map_area.map_data[stored_y][stored_x]
760
- if tile and len(tile) >= 3:
761
- tile_id, behavior, collision = tile[:3]
762
-
763
- if simplified:
764
- # Use the centralized tile_to_symbol function
765
- symbol = self._tile_to_symbol(tile)
766
- if symbol is not None: # Only add if it's a valid tile
767
- grid[(x, y)] = symbol
768
- else:
769
- # Return raw tile data
770
- grid[(x, y)] = tile
771
- else:
772
- # Use full stored map (fallback for old behavior)
773
- for y, row in enumerate(map_area.map_data):
774
- for x, tile in enumerate(row):
775
- if tile and len(tile) >= 3:
776
- tile_id, behavior, collision = tile[:3]
777
-
778
- if simplified:
779
- # Use the centralized tile_to_symbol function
780
- symbol = self._tile_to_symbol(tile)
781
- if symbol is not None: # Only add if it's a valid tile
782
- grid[(x, y)] = symbol
783
- else:
784
- # Return raw tile data
785
- grid[(x, y)] = tile
786
-
787
- return grid
788
-
789
- def get_all_location_grids(self, simplified: bool = True) -> Dict[str, Dict[Tuple[int, int], str]]:
790
- """Get grids for all known locations.
791
-
792
- Returns:
793
- Dictionary mapping location names to their grids
794
- """
795
- all_grids = {}
796
- for area in self.map_areas.values():
797
- if area.location_name and area.map_data:
798
- all_grids[area.location_name] = self.get_location_grid(area.location_name, simplified)
799
- return all_grids
800
-
801
- def save_to_file(self):
802
- """Save stitching data to JSON file"""
803
- try:
804
- data = {
805
- "map_areas": {},
806
- "location_connections": {}
807
- }
808
-
809
- # Convert map areas to serializable format
810
- for map_id, area in self.map_areas.items():
811
- # Trim null rows from map_data before saving
812
- if area.map_data:
813
- trimmed_map_data, trim_offsets = self._trim_null_rows(area.map_data)
814
- else:
815
- trimmed_map_data, trim_offsets = [], {}
816
-
817
- # Save only essential data
818
- area_data = {
819
- "map_id": area.map_id,
820
- "location_name": area.location_name,
821
- "map_data": trimmed_map_data,
822
- "player_last_position": area.player_last_position
823
- }
824
-
825
- # Save trim offsets if we trimmed the data
826
- if trim_offsets:
827
- area_data["trim_offsets"] = trim_offsets
828
-
829
- # Save additional attributes for map stitching
830
- if hasattr(area, 'explored_bounds'):
831
- area_data["explored_bounds"] = area.explored_bounds
832
- if hasattr(area, 'origin_offset'):
833
- area_data["origin_offset"] = area.origin_offset
834
- data["map_areas"][str(map_id)] = area_data
835
-
836
- # Generate location_connections from warp_connections
837
- # MapStitcher is the single source of truth for connections
838
- data["location_connections"] = self.get_location_connections()
839
- logger.debug(f"Saved {len(data['location_connections'])} location connections from {len(self.warp_connections)} warp connections")
840
-
841
- with open(self.save_file, 'w') as f:
842
- # Save in minified format to reduce file size
843
- json.dump(data, f, separators=(',', ':'))
844
-
845
- logger.debug(f"Saved map stitching data to {self.save_file}")
846
-
847
- except Exception as e:
848
- logger.error(f"Failed to save map stitching data: {e}")
849
-
850
- def load_from_file(self):
851
- """Load stitching data from JSON file"""
852
- if not self.save_file.exists():
853
- return
854
-
855
- # Check if file is empty
856
- if self.save_file.stat().st_size == 0:
857
- logger.debug(f"Map stitcher file {self.save_file} is empty, starting fresh")
858
- return
859
-
860
- try:
861
- with open(self.save_file, 'r') as f:
862
- data = json.load(f)
863
-
864
- # Add loaded data to existing map areas (accumulate knowledge)
865
- # Restore map areas (with map_data for world map display)
866
- for map_id_str, area_data in data.get("map_areas", {}).items():
867
- map_id = int(map_id_str)
868
-
869
- # Skip map 0 during loading as well (cleanup old data)
870
- if map_id == 0:
871
- logger.debug(f"Skipping load of map 0 (startup state) during file load")
872
- continue
873
-
874
- # Try to resolve location name if it's Unknown or missing
875
- location_name = area_data.get("location_name")
876
- if not location_name or location_name == "Unknown":
877
- # Import and use the location mapping
878
- try:
879
- map_enum = MapLocation(map_id)
880
- location_name = map_enum.name.replace('_', ' ').title()
881
- logger.info(f"Resolved location name for map {map_id:04X} during load: {location_name}")
882
- except ValueError:
883
- # Fallback for unknown map IDs
884
- location_name = f"Map_{map_id:04X}"
885
- logger.debug(f"Unknown map ID {map_id:04X} during load, using fallback name")
886
-
887
- # Reconstruct full map data from trimmed version
888
- trimmed_data = area_data.get("map_data", [])
889
- trim_offsets = area_data.get("trim_offsets", {})
890
-
891
- if trim_offsets and trim_offsets.get('compacted'):
892
- # New compacted format - reconstruct from tile list
893
- row_offset = trim_offsets.get('row_offset', 0)
894
- col_offset = trim_offsets.get('col_offset', 0)
895
- original_height = trim_offsets.get('original_height', 100)
896
- original_width = trim_offsets.get('original_width', 100)
897
-
898
- # Create full-sized map data array
899
- full_map_data = [[None for _ in range(original_width)] for _ in range(original_height)]
900
-
901
- # Restore tiles from compacted format
902
- if isinstance(trimmed_data, list):
903
- # New list format: [[rel_row, rel_col, tile], ...]
904
- for item in trimmed_data:
905
- if len(item) >= 3:
906
- rel_row, rel_col, tile = item[0], item[1], item[2]
907
- actual_row = row_offset + rel_row
908
- actual_col = col_offset + rel_col
909
- if actual_row < original_height and actual_col < original_width:
910
- full_map_data[actual_row][actual_col] = tile
911
- elif isinstance(trimmed_data, dict) and 'tiles' in trimmed_data:
912
- # Old dict format (backward compatibility)
913
- for pos_key, tile in trimmed_data['tiles'].items():
914
- rel_row, rel_col = map(int, pos_key.split(','))
915
- actual_row = row_offset + rel_row
916
- actual_col = col_offset + rel_col
917
- if actual_row < original_height and actual_col < original_width:
918
- full_map_data[actual_row][actual_col] = tile
919
-
920
- map_data = full_map_data
921
- elif trimmed_data and trim_offsets:
922
- # Old trimmed format (backward compatibility)
923
- row_offset = trim_offsets.get('row_offset', 0)
924
- col_offset = trim_offsets.get('col_offset', 0)
925
- original_height = trim_offsets.get('original_height', len(trimmed_data) + row_offset)
926
- original_width = trim_offsets.get('original_width', 100)
927
-
928
- # Create full-sized map data array
929
- full_map_data = [[None for _ in range(original_width)] for _ in range(original_height)]
930
-
931
- # Place trimmed data back at correct position
932
- for i, row in enumerate(trimmed_data):
933
- for j, tile in enumerate(row):
934
- if tile is not None:
935
- full_map_data[row_offset + i][col_offset + j] = tile
936
-
937
- map_data = full_map_data
938
- else:
939
- # No trim offsets, use data as-is (backward compatibility)
940
- map_data = trimmed_data
941
-
942
- # Validate and clean player position when loading
943
- player_pos_data = area_data.get("player_last_position", [0, 0])
944
- if player_pos_data:
945
- px, py = player_pos_data[0], player_pos_data[1] if len(player_pos_data) > 1 else 0
946
- # Clean up invalid positions (65535 = 0xFFFF is an error value)
947
- if px < 0 or px > 1000 or py < 0 or py > 1000 or px == 0xFFFF or py == 0xFFFF:
948
- logger.warning(f"Cleaning invalid player position {player_pos_data} for map {map_id:04X}")
949
- player_pos_data = [0, 0] # Reset to origin
950
- else:
951
- player_pos_data = [0, 0]
952
-
953
- area = MapArea(
954
- map_id=area_data["map_id"],
955
- location_name=location_name,
956
- map_data=map_data,
957
- player_last_position=tuple(player_pos_data),
958
- warp_tiles=[], # Deprecated - not needed
959
- boundaries={"north": 0, "south": 10, "west": 0, "east": 10}, # Default boundaries
960
- visited_count=1, # Default
961
- first_seen=0, # Default
962
- last_seen=0, # Default
963
- overworld_coords=None # Not needed
964
- )
965
- # Restore additional stitching attributes if present
966
- if "explored_bounds" in area_data:
967
- area.explored_bounds = area_data["explored_bounds"]
968
- # When loading trimmed data, adjust explored_bounds to match
969
- # Since we trimmed null rows/columns, the bounds are now relative to the trimmed data
970
- if area.map_data:
971
- # The trimmed data starts at (0,0), so adjust bounds accordingly
972
- actual_height = len(area.map_data)
973
- actual_width = max(len(row) for row in area.map_data) if area.map_data else 0
974
- # Keep the existing explored_bounds as they track the original coordinate space
975
- # The map_data is now compact but explored_bounds maintains the relationship
976
- else:
977
- # Initialize explored bounds from map data if not present
978
- if area.map_data:
979
- min_x, max_x = 100, 0
980
- min_y, max_y = 100, 0
981
- for y, row in enumerate(area.map_data):
982
- for x, tile in enumerate(row):
983
- if tile is not None:
984
- min_x = min(min_x, x)
985
- max_x = max(max_x, x)
986
- min_y = min(min_y, y)
987
- max_y = max(max_y, y)
988
- if min_x <= max_x:
989
- area.explored_bounds = {
990
- 'min_x': min_x, 'max_x': max_x,
991
- 'min_y': min_y, 'max_y': max_y
992
- }
993
-
994
- if "origin_offset" in area_data:
995
- area.origin_offset = area_data["origin_offset"]
996
- else:
997
- # Initialize origin offset based on player position
998
- if area.player_last_position:
999
- # Assume player was at center of initial explored area
1000
- area.origin_offset = {'x': 50 - area.player_last_position[0],
1001
- 'y': 50 - area.player_last_position[1]}
1002
- self.map_areas[map_id] = area
1003
- # Debug: log if map_data was loaded
1004
- if area.map_data:
1005
- logger.debug(f"Loaded map_data for {location_name}: {len(area.map_data)}x{len(area.map_data[0]) if area.map_data else 0}")
1006
-
1007
- # Reconstruct warp_connections from location_connections
1008
- location_connections = data.get("location_connections", {})
1009
-
1010
- # Clear existing warp connections to avoid duplicates
1011
- self.warp_connections = []
1012
-
1013
- # Convert location_connections back to warp_connections
1014
- for from_location, connections in location_connections.items():
1015
- # Find the map_id for this location
1016
- from_map_id = None
1017
- for map_id, area in self.map_areas.items():
1018
- if area.location_name == from_location:
1019
- from_map_id = map_id
1020
- break
1021
-
1022
- if from_map_id is None:
1023
- continue
1024
-
1025
- for conn_data in connections:
1026
- to_location = conn_data[0]
1027
- from_pos = tuple(conn_data[1]) if len(conn_data) > 1 else (0, 0)
1028
- to_pos = tuple(conn_data[2]) if len(conn_data) > 2 else (0, 0)
1029
-
1030
- # Find the map_id for the destination
1031
- to_map_id = None
1032
- for map_id, area in self.map_areas.items():
1033
- if area.location_name == to_location:
1034
- to_map_id = map_id
1035
- break
1036
-
1037
- if to_map_id is None:
1038
- continue
1039
-
1040
- # Create warp connection
1041
- warp_conn = WarpConnection(
1042
- from_map_id=from_map_id,
1043
- to_map_id=to_map_id,
1044
- from_position=from_pos,
1045
- to_position=to_pos,
1046
- warp_type="stairs", # Default type
1047
- direction=None
1048
- )
1049
- self.warp_connections.append(warp_conn)
1050
-
1051
- logger.info(f"Reconstructed {len(self.warp_connections)} warp connections from {len(location_connections)} location connections")
1052
-
1053
- logger.info(f"Loaded {len(self.map_areas)} areas and {len(self.warp_connections)} connections")
1054
-
1055
- # Try to resolve any "Unknown" location names
1056
- if self.resolve_unknown_location_names():
1057
- # Save the updated names
1058
- self.save_to_file()
1059
-
1060
- except Exception as e:
1061
- logger.error(f"Failed to load map stitching data: {e}")
1062
-
1063
- def get_stats(self) -> Dict[str, Any]:
1064
- """Get statistics about the stitched world map"""
1065
- indoor_areas = sum(1 for area in self.map_areas.values()
1066
- if area.location_name and ("HOUSE" in area.location_name.upper() or "ROOM" in area.location_name.upper()))
1067
- outdoor_areas = len(self.map_areas) - indoor_areas
1068
-
1069
- warp_types = {}
1070
- for conn in self.warp_connections:
1071
- warp_types[conn.warp_type] = warp_types.get(conn.warp_type, 0) + 1
1072
-
1073
- return {
1074
- "total_areas": len(self.map_areas),
1075
- "indoor_areas": indoor_areas,
1076
- "outdoor_areas": outdoor_areas,
1077
- "total_connections": len(self.warp_connections),
1078
- "warp_types": warp_types,
1079
- "most_visited": max(self.map_areas.values(), key=lambda a: a.visited_count).location_name if self.map_areas else None
1080
- }
1081
-
1082
- def generate_world_map_grid(self, current_map_id: Optional[int] = None) -> Dict[str, Any]:
1083
- """Generate a world map grid showing discovered areas and connections"""
1084
- # Define world map bounds (rough Pokemon Emerald overworld size)
1085
- map_width = 50
1086
- map_height = 35
1087
-
1088
- # Initialize empty grid
1089
- grid = [['.' for _ in range(map_width)] for _ in range(map_height)]
1090
- area_labels = {}
1091
-
1092
- # Place discovered areas on the grid
1093
- for map_id, area in self.map_areas.items():
1094
- coords = area.overworld_coords
1095
- if coords is None:
1096
- continue # Skip areas without known coordinates
1097
-
1098
- x, y = coords
1099
- if 0 <= x < map_width and 0 <= y < map_height:
1100
- # Determine symbol based on area type
1101
- name = area.location_name.upper() if area.location_name else "UNKNOWN"
1102
- if any(keyword in name for keyword in ["HOUSE", "CENTER", "MART", "GYM", "ROOM"]):
1103
- symbol = "H" # Houses/buildings
1104
- elif "ROUTE" in name:
1105
- symbol = "R" # Routes
1106
- elif any(keyword in name for keyword in ["TOWN", "CITY"]):
1107
- symbol = "T" # Towns/cities
1108
- else:
1109
- symbol = "?" # Unknown/other
1110
-
1111
- # Mark current player location
1112
- if map_id == current_map_id:
1113
- symbol = "P" # Player
1114
-
1115
- grid[y][x] = symbol
1116
-
1117
- # Store area name for reference
1118
- area_labels[f"{x},{y}"] = area.location_name
1119
-
1120
- # Add connection lines between areas
1121
- for conn in self.warp_connections:
1122
- from_area = self.map_areas.get(conn.from_map_id)
1123
- to_area = self.map_areas.get(conn.to_map_id)
1124
-
1125
- if (from_area and to_area and
1126
- from_area.overworld_coords and to_area.overworld_coords):
1127
-
1128
- from_x, from_y = from_area.overworld_coords
1129
- to_x, to_y = to_area.overworld_coords
1130
-
1131
- # Draw simple connection line (just mark endpoints for now)
1132
- # In a more sophisticated version, we could draw actual paths
1133
- if (0 <= from_x < map_width and 0 <= from_y < map_height and
1134
- 0 <= to_x < map_width and 0 <= to_y < map_height):
1135
-
1136
- # Mark connection endpoints if they're empty
1137
- if grid[from_y][from_x] == '.':
1138
- grid[from_y][from_x] = "+"
1139
- if grid[to_y][to_x] == '.':
1140
- grid[to_y][to_x] = "+"
1141
-
1142
- return {
1143
- "grid": grid,
1144
- "width": map_width,
1145
- "height": map_height,
1146
- "area_labels": area_labels,
1147
- "legend": {
1148
- "P": "Current Player Location",
1149
- "T": "Town/City",
1150
- "R": "Route",
1151
- "H": "House/Building",
1152
- "+": "Connection Point",
1153
- ".": "Unexplored",
1154
- "?": "Other Area"
1155
- }
1156
- }
1157
-
1158
- def _should_trim_edge(self, tiles, is_row=True):
1159
- """Check if an edge (row or column) should be trimmed.
1160
- An edge should be trimmed if it's all walls (#) with no meaningful content."""
1161
- # Count non-wall tiles
1162
- non_wall_count = 0
1163
- for tile in tiles:
1164
- if tile and tile not in ['#', ' ', None]:
1165
- non_wall_count += 1
1166
- # Trim if it's all walls or mostly walls with no content
1167
- return non_wall_count == 0
1168
-
1169
- def _trim_null_rows(self, map_data: List[List]) -> Tuple[List[List], Dict[str, int]]:
1170
- """Trim rows that are entirely null/None from map data to reduce file size.
1171
-
1172
- Returns a tuple of (trimmed_data, trim_offsets) where trim_offsets contains
1173
- the offsets needed to reconstruct original positions.
1174
- """
1175
- if not map_data:
1176
- return [], {}
1177
-
1178
- # Find bounds of actual data
1179
- start_row = None
1180
- end_row = None
1181
- start_col = None
1182
- end_col = None
1183
-
1184
- # Find row bounds
1185
- for i, row in enumerate(map_data):
1186
- if row and any(tile is not None for tile in row):
1187
- if start_row is None:
1188
- start_row = i
1189
- end_row = i
1190
-
1191
- if start_row is None:
1192
- # All data is null
1193
- return [], {}
1194
-
1195
- # Find column bounds across all rows
1196
- for row in map_data[start_row:end_row + 1]:
1197
- if row:
1198
- for j, tile in enumerate(row):
1199
- if tile is not None:
1200
- if start_col is None or j < start_col:
1201
- start_col = j
1202
- if end_col is None or j > end_col:
1203
- end_col = j
1204
-
1205
- if start_col is None:
1206
- return [], {}
1207
-
1208
- # Create compacted data - use a list of [row, col, tile] to save space
1209
- # This eliminates ALL null-only rows while preserving position information
1210
- tiles_list = []
1211
-
1212
- # Store only non-null tiles with their positions
1213
- for i in range(start_row, end_row + 1):
1214
- if map_data[i]:
1215
- for j in range(start_col, end_col + 1):
1216
- if j < len(map_data[i]) and map_data[i][j] is not None:
1217
- # Store as [relative_row, relative_col, tile_data]
1218
- # This is more compact than dict with string keys
1219
- rel_row = i - start_row
1220
- rel_col = j - start_col
1221
- tiles_list.append([rel_row, rel_col, map_data[i][j]])
1222
-
1223
- trim_offsets = {
1224
- 'row_offset': start_row,
1225
- 'col_offset': start_col,
1226
- 'original_height': len(map_data),
1227
- 'original_width': max(len(row) for row in map_data) if map_data else 0,
1228
- 'compacted': True # Flag to indicate new format
1229
- }
1230
-
1231
- return tiles_list, trim_offsets
1232
-
1233
- def generate_location_map_display(self, location_name: str, player_pos: Tuple[int, int] = None,
1234
- npcs: List[Dict] = None, connections: List[Dict] = None) -> List[str]:
1235
- """Generate a detailed map display for a specific location.
1236
-
1237
- Args:
1238
- location_name: Name of the location to display
1239
- player_pos: Current player position (x, y)
1240
- npcs: List of NPC positions and data
1241
- connections: List of location connections
1242
-
1243
- Returns:
1244
- List of display lines ready for formatting
1245
- """
1246
- lines = []
1247
-
1248
- # Get stored map data for this location
1249
- location_grid = self.get_location_grid(location_name, simplified=True)
1250
-
1251
- if not location_grid:
1252
- # No map data available - return empty to trigger memory fallback
1253
- return []
1254
-
1255
- # For accumulated maps, show the full explored area
1256
- # Get the dimensions of the explored area
1257
- max_x = max(x for x, y in location_grid.keys()) if location_grid else 0
1258
- max_y = max(y for x, y in location_grid.keys()) if location_grid else 0
1259
- min_x = min(x for x, y in location_grid.keys()) if location_grid else 0
1260
- min_y = min(y for x, y in location_grid.keys()) if location_grid else 0
1261
-
1262
- explored_width = max_x - min_x + 1
1263
- explored_height = max_y - min_y + 1
1264
-
1265
- # Show the full accumulated map (up to reasonable size)
1266
- # Don't try to focus on player for accumulated maps
1267
- if explored_width <= 30 and explored_height <= 30:
1268
- # Show the entire accumulated map
1269
- display_radius = max(explored_width, explored_height) // 2
1270
- display_size = max(explored_width, explored_height)
1271
- else:
1272
- # Very large area, limit to 30x30 for readability
1273
- display_radius = 15
1274
- display_size = 30
1275
-
1276
- display_center = display_radius # Player at center
1277
-
1278
- # For accumulated maps, just use the entire grid without focusing
1279
- # This shows the full explored area
1280
- all_positions = list(location_grid.keys())
1281
-
1282
- # Find player position in the grid if available
1283
- local_player_pos = None
1284
- if player_pos:
1285
- # Validate player position first
1286
- px, py = player_pos
1287
- if px >= 0 and px < 1000 and py >= 0 and py < 1000 and px != 0xFFFF and py != 0xFFFF:
1288
- # Find the stored map area to get coordinate conversion info
1289
- map_area = None
1290
- for area in self.map_areas.values():
1291
- if area.location_name and location_name and area.location_name.lower() == location_name.lower():
1292
- map_area = area
1293
- break
1294
-
1295
- if map_area:
1296
- # Use the stored player position from the map area if available
1297
- if hasattr(map_area, 'player_last_position') and map_area.player_last_position:
1298
- last_px, last_py = map_area.player_last_position
1299
- # Validate the stored position
1300
- if last_px >= 0 and last_px < 1000 and last_py >= 0 and last_py < 1000 and last_px != 0xFFFF and last_py != 0xFFFF:
1301
- player_pos = map_area.player_last_position
1302
- px, py = player_pos
1303
-
1304
- if hasattr(map_area, 'origin_offset') and map_area.origin_offset:
1305
- # Convert player world coordinates to grid-relative coordinates
1306
- offset_x = map_area.origin_offset.get('x', 0)
1307
- offset_y = map_area.origin_offset.get('y', 0)
1308
-
1309
- # Calculate player's position relative to the explored bounds
1310
- grid_player_x = px + offset_x
1311
- grid_player_y = py + offset_y
1312
-
1313
- # Convert to relative coordinates in the location_grid
1314
- if hasattr(map_area, 'explored_bounds'):
1315
- bounds = map_area.explored_bounds
1316
- rel_x = grid_player_x - bounds['min_x']
1317
- rel_y = grid_player_y - bounds['min_y']
1318
-
1319
- # Check if player is within the displayed area
1320
- if 0 <= rel_x <= (max_x - min_x) and 0 <= rel_y <= (max_y - min_y):
1321
- local_player_pos = (rel_x, rel_y)
1322
- logger.debug(f"Player at relative position {local_player_pos} in {location_name}")
1323
- else:
1324
- logger.debug(f"Player at {player_pos} is outside displayed area of {location_name}")
1325
-
1326
- if not all_positions:
1327
- return []
1328
-
1329
- min_x = min(pos[0] for pos in all_positions)
1330
- max_x = max(pos[0] for pos in all_positions)
1331
- min_y = min(pos[1] for pos in all_positions)
1332
- max_y = max(pos[1] for pos in all_positions)
1333
-
1334
- # Minimal trimming - only remove completely empty space
1335
- # Don't trim '?' as those are unexplored areas we want to show
1336
- # Don't aggressively trim walls as they show room boundaries
1337
-
1338
- # Only trim rows that are completely empty (all spaces/None)
1339
- while min_y < max_y:
1340
- row_tiles = [location_grid.get((x, min_y), ' ') for x in range(min_x, max_x + 1)]
1341
- # Keep the row if it has ANY content (including ? and #)
1342
- if any(t not in [' ', None] for t in row_tiles):
1343
- break
1344
- min_y += 1
1345
-
1346
- # Check bottom rows - only trim completely empty
1347
- while max_y > min_y:
1348
- row_tiles = [location_grid.get((x, max_y), ' ') for x in range(min_x, max_x + 1)]
1349
- if any(t not in [' ', None] for t in row_tiles):
1350
- break
1351
- max_y -= 1
1352
-
1353
- # Check left columns - only trim completely empty
1354
- while min_x < max_x:
1355
- col_tiles = [location_grid.get((min_x, y), ' ') for y in range(min_y, max_y + 1)]
1356
- if any(t not in [' ', None] for t in col_tiles):
1357
- break
1358
- min_x += 1
1359
-
1360
- # Check right columns - only trim completely empty
1361
- while max_x > min_x:
1362
- col_tiles = [location_grid.get((max_x, y), ' ') for y in range(min_y, max_y + 1)]
1363
- if any(t not in [' ', None] for t in col_tiles):
1364
- break
1365
- max_x -= 1
1366
-
1367
- # Build portal positions from connections
1368
- portal_positions = {}
1369
-
1370
- lines.append(f"\n--- MAP: {location_name.upper()} ---")
1371
-
1372
- # Create the map display
1373
- for y in range(min_y, max_y + 1):
1374
- row = ""
1375
- for x in range(min_x, max_x + 1):
1376
- # Check if this is an edge position
1377
- is_edge = (x == min_x or x == max_x or y == min_y or y == max_y)
1378
-
1379
- # Check for NPCs at this position
1380
- npc_at_pos = None
1381
- if npcs:
1382
- for npc in npcs:
1383
- npc_x = npc.get('current_x', npc.get('x'))
1384
- npc_y = npc.get('current_y', npc.get('y'))
1385
- if npc_x == x and npc_y == y:
1386
- npc_at_pos = npc
1387
- break
1388
-
1389
- if local_player_pos and (x, y) == local_player_pos:
1390
- row += "P"
1391
- elif npc_at_pos:
1392
- row += "N"
1393
- elif (x, y) in location_grid:
1394
- tile = location_grid[(x, y)]
1395
- # Check for portal markers at edges
1396
- if is_edge and tile == '.' and connections:
1397
- portal_added = False
1398
- for conn in connections:
1399
- direction = conn.get('direction', '').lower()
1400
- conn_name = conn.get('name', '')
1401
- if direction and conn_name and conn_name not in ['Unknown', 'None', '']:
1402
- if direction == 'east' and x == max_x:
1403
- row += "→"
1404
- portal_positions[(x, y)] = conn_name
1405
- portal_added = True
1406
- break
1407
- elif direction == 'west' and x == min_x:
1408
- row += "←"
1409
- portal_positions[(x, y)] = conn_name
1410
- portal_added = True
1411
- break
1412
- elif direction == 'north' and y == min_y:
1413
- row += "↑"
1414
- portal_positions[(x, y)] = conn_name
1415
- portal_added = True
1416
- break
1417
- elif direction == 'south' and y == max_y:
1418
- row += "↓"
1419
- portal_positions[(x, y)] = conn_name
1420
- portal_added = True
1421
- break
1422
-
1423
- if not portal_added:
1424
- row += tile
1425
- else:
1426
- row += tile
1427
- else:
1428
- # Position not in grid - just show as space
1429
- # The grid already has '?' symbols where needed from get_location_grid
1430
- row += " "
1431
-
1432
- # Add spacing between characters for square aspect ratio
1433
- # Most terminals have characters ~2x taller than wide, so spacing helps
1434
- spaced_row = " ".join(row)
1435
- lines.append(spaced_row)
1436
-
1437
- # Add legend
1438
- legend_lines = ["", "Legend:"]
1439
- legend_lines.append(" Movement: P=Player")
1440
- if npcs:
1441
- legend_lines.append(" N=NPC/Trainer")
1442
-
1443
- # Check what terrain symbols are visible
1444
- visible_symbols = set(location_grid.values())
1445
-
1446
- terrain_items = []
1447
- symbol_meanings = {
1448
- ".": ".=Walkable path",
1449
- "#": "#=Wall/Blocked",
1450
- "~": "~=Tall grass",
1451
- "^": "^=Grass",
1452
- "W": "W=Water",
1453
- "I": "I=Ice",
1454
- "s": "s=Sand",
1455
- "D": "D=Door",
1456
- "S": "S=Stairs/Ladder",
1457
- "C": "C=Computer/PC",
1458
- "→": "→=Ledge (jump east)",
1459
- "←": "←=Ledge (jump west)",
1460
- "↑": "↑=Ledge (jump north)",
1461
- "↓": "↓=Ledge (jump south)",
1462
- "↗": "↗=Ledge (jump NE)",
1463
- "↖": "↖=Ledge (jump NW)",
1464
- "↘": "↘=Ledge (jump SE)",
1465
- "↙": "↙=Ledge (jump SW)",
1466
- "L": "L=Ledge",
1467
- "T": "T=TV",
1468
- "?": "?=Unknown"
1469
- }
1470
-
1471
- for symbol, meaning in symbol_meanings.items():
1472
- if symbol in visible_symbols:
1473
- terrain_items.append(meaning)
1474
-
1475
- if terrain_items:
1476
- legend_lines.append(f" Terrain: {', '.join(terrain_items)}")
1477
-
1478
- # Add portal markers to legend if any
1479
- if portal_positions:
1480
- unique_portals = {}
1481
- for pos, dest in portal_positions.items():
1482
- x, y = pos
1483
- if x == min_x:
1484
- unique_portals["←"] = dest
1485
- elif x == max_x:
1486
- unique_portals["→"] = dest
1487
- elif y == min_y:
1488
- unique_portals["↑"] = dest
1489
- elif y == max_y:
1490
- unique_portals["↓"] = dest
1491
-
1492
- if unique_portals:
1493
- portal_items = []
1494
- for symbol, dest in unique_portals.items():
1495
- portal_items.append(f"{symbol}={dest}")
1496
- legend_lines.append(f" Exits: {', '.join(portal_items)}")
1497
-
1498
- lines.extend(legend_lines)
1499
-
1500
- # Add explicit portal connections with coordinates
1501
- if connections:
1502
- lines.append("")
1503
- lines.append("Portal Connections:")
1504
- for conn in connections:
1505
- to_location = conn.get('to', 'Unknown')
1506
- from_pos = conn.get('from_pos', [])
1507
- to_pos = conn.get('to_pos', [])
1508
-
1509
- if from_pos and to_pos and len(from_pos) >= 2 and len(to_pos) >= 2:
1510
- lines.append(f" {location_name} ({from_pos[0]},{from_pos[1]}) → {to_location} ({to_pos[0]},{to_pos[1]})")
1511
- elif from_pos and len(from_pos) >= 2:
1512
- lines.append(f" {location_name} ({from_pos[0]},{from_pos[1]}) → {to_location}")
1513
- else:
1514
- lines.append(f" → {to_location}")
1515
-
1516
- return lines
1517
-
1518
- def _tile_to_symbol(self, tile) -> str:
1519
- """Convert a tile tuple to a simplified symbol for display."""
1520
- if tile is None:
1521
- # This will be handled specially - unexplored areas next to walkable will show ?
1522
- return None # Mark as unexplored for special handling
1523
-
1524
- if len(tile) < 3:
1525
- return None # Invalid tile - unexplored
1526
-
1527
- tile_id, behavior, collision = tile[:3]
1528
-
1529
- # tile_id 1023 (0x3FF) means out-of-bounds/unloaded area
1530
- # These are trees/boundaries at the edge of maps - show as walls
1531
- if tile_id == 1023:
1532
- return '#' # Display as wall/blocked
1533
-
1534
- # Get behavior value
1535
- if hasattr(behavior, 'value'):
1536
- behavior_val = behavior.value
1537
- else:
1538
- behavior_val = behavior
1539
-
1540
- # Check behavior first for special terrain (even if impassable)
1541
- # Grass types (from MetatileBehavior enum)
1542
- if behavior_val == 2: # TALL_GRASS
1543
- return '~' # Tall grass (encounters)
1544
- elif behavior_val == 3: # LONG_GRASS
1545
- return '^' # Long grass
1546
- elif behavior_val == 7: # SHORT_GRASS
1547
- return '^' # Short grass
1548
- elif behavior_val == 36: # ASHGRASS
1549
- return '^' # Ash grass
1550
-
1551
- # Water types
1552
- elif behavior_val in [16, 17, 18, 19, 20, 21, 22, 23, 24, 26]: # Various water types
1553
- return 'W' # Water
1554
-
1555
- # Ice
1556
- elif behavior_val in [32, 38, 39]: # ICE, THIN_ICE, CRACKED_ICE
1557
- return 'I' # Ice
1558
-
1559
- # Sand
1560
- elif behavior_val in [6, 33]: # DEEP_SAND, SAND
1561
- return 's' # Sand
1562
-
1563
- # Doors and warps
1564
- elif behavior_val == 96: # NON_ANIMATED_DOOR
1565
- return 'D' # Door
1566
- elif behavior_val == 105: # ANIMATED_DOOR
1567
- return 'D' # Door
1568
- elif behavior_val in [98, 99, 100, 101]: # Arrow warps
1569
- return 'D' # Warp/Door
1570
- elif behavior_val == 97: # LADDER
1571
- return 'S' # Stairs/Ladder
1572
- elif behavior_val in [106, 107]: # Escalators
1573
- return 'S' # Stairs
1574
-
1575
- # PC and other interactables
1576
- elif behavior_val in [131, 197]: # PC, PLAYER_ROOM_PC_ON
1577
- return 'C' # Computer/PC (changed from 'P' to avoid conflict with Player)
1578
- elif behavior_val == 134: # TELEVISION
1579
- return 'T' # TV
1580
-
1581
- # Ledges/Jumps with directional arrows
1582
- elif behavior_val == 56: # JUMP_EAST
1583
- return '→' # Ledge east
1584
- elif behavior_val == 57: # JUMP_WEST
1585
- return '←' # Ledge west
1586
- elif behavior_val == 58: # JUMP_NORTH
1587
- return '↑' # Ledge north
1588
- elif behavior_val == 59: # JUMP_SOUTH
1589
- return '↓' # Ledge south
1590
- elif behavior_val == 60: # JUMP_NORTHEAST
1591
- return '↗' # Ledge northeast
1592
- elif behavior_val == 61: # JUMP_NORTHWEST
1593
- return '↖' # Ledge northwest
1594
- elif behavior_val == 62: # JUMP_SOUTHEAST
1595
- return '↘' # Ledge southeast
1596
- elif behavior_val == 63: # JUMP_SOUTHWEST
1597
- return '↙' # Ledge southwest
1598
-
1599
- # Now check collision for basic terrain
1600
- elif collision == 1: # Impassable
1601
- return '#' # Wall
1602
- elif collision == 0: # Walkable
1603
- return '.' # Floor
1604
- elif collision == 3: # Ledge/special
1605
- return 'L' # Ledge
1606
- elif collision == 4: # Water/surf
1607
- return 'W' # Water
1608
- else:
1609
- return '?' # Unknown
1610
-
1611
- def _is_explorable_edge(self, x: int, y: int, location_grid: Dict[Tuple[int, int], str]) -> bool:
1612
- """Check if an unexplored coordinate is worth exploring (adjacent to walkable tiles)."""
1613
- # Check all 4 adjacent tiles
1614
- for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
1615
- adj_x, adj_y = x + dx, y + dy
1616
- if (adj_x, adj_y) in location_grid:
1617
- tile = location_grid[(adj_x, adj_y)]
1618
- # If adjacent to walkable tile, this is explorable
1619
- # Include all walkable terrain types and ledges
1620
- if tile in ['.', 'D', 'S', '^', '~', 's', 'I', # Floor, doors, stairs, grass, sand, ice
1621
- '→', '←', '↑', '↓', '↗', '↖', '↘', '↙']: # Ledges in all directions
1622
- return True
1623
- return False
1624
-
1625
- def format_world_map_display(self, current_map_id: Optional[int] = None, max_width: int = 50) -> str:
1626
- """Format world map for display in agent context"""
1627
- world_map = self.generate_world_map_grid(current_map_id)
1628
- grid = world_map["grid"]
1629
- labels = world_map["area_labels"]
1630
- legend = world_map["legend"]
1631
-
1632
- lines = []
1633
- lines.append("=== WORLD MAP ===")
1634
- lines.append("")
1635
-
1636
- # Show grid with coordinates
1637
- for y, row in enumerate(grid):
1638
- row_str = ""
1639
- for x, cell in enumerate(row):
1640
- row_str += cell + " "
1641
- lines.append(f"{y:2d}: {row_str}")
1642
-
1643
- # Add coordinate header at bottom
1644
- header = " "
1645
- for x in range(0, len(grid[0]), 5): # Show every 5th coordinate
1646
- header += f"{x:2d} "
1647
- lines.append("")
1648
- lines.append(header)
1649
-
1650
- # Add legend
1651
- lines.append("")
1652
- lines.append("Legend:")
1653
- for symbol, meaning in legend.items():
1654
- lines.append(f" {symbol} = {meaning}")
1655
-
1656
- # Add discovered area list
1657
- if labels:
1658
- lines.append("")
1659
- lines.append("Discovered Areas:")
1660
- sorted_areas = sorted(labels.items(), key=lambda x: x[1])
1661
- for coord, name in sorted_areas[:10]: # Show first 10
1662
- lines.append(f" {coord}: {name}")
1663
- if len(sorted_areas) > 10:
1664
- lines.append(f" ... and {len(sorted_areas) - 10} more")
1665
-
1666
- return "\n".join(lines)
1667
-
1668
- def save_to_checkpoint(self, checkpoint_data: dict):
1669
- """Save map stitching data to checkpoint data structure"""
1670
- try:
1671
- map_stitcher_data = {
1672
- "map_areas": {},
1673
- "warp_connections": [],
1674
- "location_connections": {}
1675
- }
1676
-
1677
- # Convert map areas to serializable format (without map_data)
1678
- for map_id, area in self.map_areas.items():
1679
- area_data = {
1680
- "map_id": area.map_id,
1681
- "location_name": area.location_name,
1682
- "player_last_position": area.player_last_position,
1683
- "warp_tiles": area.warp_tiles,
1684
- "boundaries": area.boundaries,
1685
- "visited_count": area.visited_count,
1686
- "first_seen": area.first_seen,
1687
- "last_seen": area.last_seen,
1688
- "overworld_coords": area.overworld_coords
1689
- }
1690
- # print( Saving area {map_id} with overworld_coords = {area.overworld_coords}")
1691
- map_stitcher_data["map_areas"][str(map_id)] = area_data
1692
-
1693
- # Convert connections to serializable format
1694
- for conn in self.warp_connections:
1695
- map_stitcher_data["warp_connections"].append(asdict(conn))
1696
-
1697
- # Save location connections from state_formatter
1698
- try:
1699
- if hasattr(state_formatter, 'LOCATION_CONNECTIONS'):
1700
- map_stitcher_data["location_connections"] = state_formatter.LOCATION_CONNECTIONS
1701
- logger.debug(f"Saved {len(state_formatter.LOCATION_CONNECTIONS)} location connections to checkpoint")
1702
- except ImportError:
1703
- logger.debug("Could not import state_formatter for location connections in checkpoint")
1704
-
1705
- checkpoint_data["map_stitcher"] = map_stitcher_data
1706
- logger.debug(f"Saved {len(self.map_areas)} areas and {len(self.warp_connections)} connections to checkpoint")
1707
-
1708
- except Exception as e:
1709
- logger.error(f"Failed to save map stitcher to checkpoint: {e}")
1710
-
1711
- def load_from_checkpoint(self, checkpoint_data: dict):
1712
- """Load map stitching data from checkpoint data structure"""
1713
- try:
1714
- map_stitcher_data = checkpoint_data.get("map_stitcher")
1715
- if not map_stitcher_data:
1716
- return
1717
-
1718
- # Clear existing data
1719
- self.map_areas.clear()
1720
- self.warp_connections.clear()
1721
-
1722
- # Restore map areas (without map_data)
1723
- for map_id_str, area_data in map_stitcher_data.get("map_areas", {}).items():
1724
- map_id = int(map_id_str)
1725
- area = MapArea(
1726
- map_id=area_data["map_id"],
1727
- location_name=area_data["location_name"],
1728
- map_data=[], # Will be populated when area is revisited
1729
- player_last_position=tuple(area_data["player_last_position"]),
1730
- warp_tiles=[tuple(wt) for wt in area_data["warp_tiles"]],
1731
- boundaries=area_data["boundaries"],
1732
- visited_count=area_data["visited_count"],
1733
- first_seen=area_data["first_seen"],
1734
- last_seen=area_data["last_seen"],
1735
- overworld_coords=tuple(area_data["overworld_coords"]) if area_data.get("overworld_coords") else None
1736
- )
1737
- self.map_areas[map_id] = area
1738
-
1739
- # Restore connections
1740
- for conn_data in map_stitcher_data.get("warp_connections", []):
1741
- conn = WarpConnection(
1742
- from_map_id=conn_data["from_map_id"],
1743
- to_map_id=conn_data["to_map_id"],
1744
- from_position=tuple(conn_data["from_position"]),
1745
- to_position=tuple(conn_data["to_position"]),
1746
- warp_type=conn_data["warp_type"],
1747
- direction=conn_data["direction"]
1748
- )
1749
- self.warp_connections.append(conn)
1750
-
1751
- # Restore location connections to state_formatter
1752
- location_connections = map_stitcher_data.get("location_connections", {})
1753
- if location_connections:
1754
- try:
1755
- state_formatter.LOCATION_CONNECTIONS = location_connections
1756
- logger.info(f"Loaded {len(location_connections)} location connections from checkpoint")
1757
- except ImportError:
1758
- logger.debug("Could not import state_formatter for location connections from checkpoint")
1759
-
1760
- logger.info(f"Loaded {len(self.map_areas)} areas and {len(self.warp_connections)} connections from checkpoint")
1761
-
1762
- except Exception as e:
1763
- logger.error(f"Failed to load map stitcher from checkpoint: {e}")