synth-ai 0.2.0__py3-none-any.whl → 0.2.1.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. synth_ai/__init__.py +28 -2
  2. synth_ai/core/system.py +4 -0
  3. synth_ai/environments/__init__.py +35 -0
  4. synth_ai/environments/environment/__init__.py +1 -0
  5. synth_ai/environments/environment/artifacts/__init__.py +1 -0
  6. synth_ai/environments/environment/artifacts/base.py +50 -0
  7. synth_ai/environments/environment/core.py +22 -0
  8. synth_ai/environments/environment/db/__init__.py +1 -0
  9. synth_ai/environments/environment/db/sqlite.py +45 -0
  10. synth_ai/environments/environment/registry.py +24 -0
  11. synth_ai/environments/environment/resources/sqlite.py +46 -0
  12. synth_ai/environments/environment/results.py +1 -0
  13. synth_ai/environments/environment/rewards/__init__.py +1 -0
  14. synth_ai/environments/environment/rewards/core.py +28 -0
  15. synth_ai/environments/environment/shared_engine.py +26 -0
  16. synth_ai/environments/environment/tools/__init__.py +34 -0
  17. synth_ai/environments/examples/__init__.py +1 -0
  18. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  19. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
  20. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
  26. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  27. synth_ai/environments/examples/crafter_classic/engine.py +502 -0
  28. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  29. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  30. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  31. synth_ai/environments/examples/crafter_classic/environment.py +255 -0
  32. synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
  33. synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
  34. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  35. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  36. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  37. synth_ai/environments/examples/enron/engine.py +291 -0
  38. synth_ai/environments/examples/enron/environment.py +165 -0
  39. synth_ai/environments/examples/enron/taskset.py +112 -0
  40. synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
  41. synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
  42. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  43. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  44. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
  45. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  46. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
  47. synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
  48. synth_ai/environments/examples/minigrid/engine.py +589 -0
  49. synth_ai/environments/examples/minigrid/environment.py +274 -0
  50. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  51. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  52. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  53. synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
  54. synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
  55. synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
  56. synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
  57. synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
  58. synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
  59. synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
  60. synth_ai/environments/examples/nethack/__init__.py +7 -0
  61. synth_ai/environments/examples/nethack/achievements.py +337 -0
  62. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  63. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  64. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
  65. synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
  66. synth_ai/environments/examples/nethack/engine.py +738 -0
  67. synth_ai/environments/examples/nethack/environment.py +255 -0
  68. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  69. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  70. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  71. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  72. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  73. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  74. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  75. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  76. synth_ai/environments/examples/nethack/taskset.py +323 -0
  77. synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
  78. synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
  79. synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
  80. synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
  81. synth_ai/environments/examples/red/__init__.py +7 -0
  82. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  83. synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
  84. synth_ai/environments/examples/red/config_logging.py +110 -0
  85. synth_ai/environments/examples/red/engine.py +693 -0
  86. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  87. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  88. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  89. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  90. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  91. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  92. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  93. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  94. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  95. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  96. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  97. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  98. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  99. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  100. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  101. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  102. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  103. synth_ai/environments/examples/red/environment.py +235 -0
  104. synth_ai/environments/examples/red/taskset.py +77 -0
  105. synth_ai/environments/examples/red/test_fixes.py +125 -0
  106. synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
  107. synth_ai/environments/examples/red/units/__init__.py +1 -0
  108. synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
  109. synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
  110. synth_ai/environments/examples/red/units/test_engine.py +192 -0
  111. synth_ai/environments/examples/red/units/test_environment.py +455 -0
  112. synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
  113. synth_ai/environments/examples/red/units/test_integration.py +217 -0
  114. synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
  115. synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
  116. synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
  117. synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
  118. synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
  119. synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
  120. synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
  121. synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
  122. synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
  123. synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
  124. synth_ai/environments/examples/red/units/test_taskset.py +116 -0
  125. synth_ai/environments/examples/red/units/test_tree.py +448 -0
  126. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  127. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
  128. synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
  129. synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
  130. synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
  131. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
  132. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
  133. synth_ai/environments/examples/sokoban/engine.py +675 -0
  134. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  135. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  136. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  137. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  138. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  139. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  140. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  141. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  142. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  143. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  144. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  145. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  146. synth_ai/environments/examples/sokoban/environment.py +228 -0
  147. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  148. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  149. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  150. synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
  151. synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
  152. synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
  153. synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
  154. synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
  155. synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
  156. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  157. synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
  158. synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
  159. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  160. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  161. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  162. synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
  163. synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
  164. synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
  165. synth_ai/environments/examples/verilog/__init__.py +10 -0
  166. synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
  167. synth_ai/environments/examples/verilog/engine.py +328 -0
  168. synth_ai/environments/examples/verilog/environment.py +349 -0
  169. synth_ai/environments/examples/verilog/taskset.py +418 -0
  170. synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
  171. synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
  172. synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
  173. synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
  174. synth_ai/environments/reproducibility/core.py +42 -0
  175. synth_ai/environments/reproducibility/tree.py +364 -0
  176. synth_ai/environments/service/app.py +78 -0
  177. synth_ai/environments/service/core_routes.py +775 -0
  178. synth_ai/environments/service/external_registry.py +57 -0
  179. synth_ai/environments/service/registry.py +9 -0
  180. synth_ai/environments/stateful/__init__.py +1 -0
  181. synth_ai/environments/stateful/core.py +28 -0
  182. synth_ai/environments/stateful/engine.py +21 -0
  183. synth_ai/environments/stateful/state.py +7 -0
  184. synth_ai/environments/tasks/api.py +19 -0
  185. synth_ai/environments/tasks/core.py +78 -0
  186. synth_ai/environments/tasks/filters.py +39 -0
  187. synth_ai/environments/tasks/utils.py +89 -0
  188. synth_ai/environments/v0_observability/history.py +3 -0
  189. synth_ai/environments/v0_observability/log.py +2 -0
  190. synth_ai/lm/caching/constants.py +1 -0
  191. synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
  192. synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
  193. synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
  194. synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
  195. synth_ai/{zyk/lms → lm}/config.py +2 -1
  196. synth_ai/{zyk/lms → lm}/constants.py +2 -2
  197. synth_ai/{zyk/lms → lm}/core/all.py +10 -10
  198. synth_ai/{zyk/lms → lm}/core/main.py +57 -33
  199. synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
  200. synth_ai/lm/cost/monitor.py +1 -0
  201. synth_ai/lm/cost/statefulness.py +1 -0
  202. synth_ai/lm/provider_support/__init__.py +8 -0
  203. synth_ai/lm/provider_support/anthropic.py +945 -0
  204. synth_ai/lm/provider_support/openai.py +1115 -0
  205. synth_ai/lm/provider_support/suppress_logging.py +31 -0
  206. synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
  207. synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
  208. synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
  209. synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
  210. synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +35 -32
  211. synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
  212. synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
  213. synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
  214. synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
  215. synth_ai/lm/vendors/supported/__init__.py +0 -0
  216. synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
  217. synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
  218. synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
  219. synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
  220. synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
  221. synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
  222. synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
  223. synth_ai/tracing/__init__.py +0 -0
  224. synth_ai/tracing/abstractions.py +224 -0
  225. synth_ai/tracing/base_client.py +91 -0
  226. synth_ai/tracing/client_manager.py +131 -0
  227. synth_ai/tracing/config.py +140 -0
  228. synth_ai/tracing/context.py +146 -0
  229. synth_ai/tracing/decorators.py +679 -0
  230. synth_ai/tracing/events/__init__.py +0 -0
  231. synth_ai/tracing/events/manage.py +147 -0
  232. synth_ai/tracing/events/scope.py +86 -0
  233. synth_ai/tracing/events/store.py +227 -0
  234. synth_ai/tracing/immediate_client.py +152 -0
  235. synth_ai/tracing/local.py +18 -0
  236. synth_ai/tracing/log_client_base.py +74 -0
  237. synth_ai/tracing/retry_queue.py +187 -0
  238. synth_ai/tracing/trackers.py +515 -0
  239. synth_ai/tracing/upload.py +504 -0
  240. synth_ai/tracing/utils.py +9 -0
  241. synth_ai/zyk/__init__.py +28 -2
  242. synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
  243. synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
  244. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
  245. synth_ai/zyk/lms/caching/constants.py +0 -1
  246. synth_ai/zyk/lms/cost/monitor.py +0 -1
  247. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  248. synth_ai-0.2.0.dist-info/METADATA +0 -36
  249. synth_ai-0.2.0.dist-info/RECORD +0 -50
  250. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  251. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  253. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  254. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  255. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  256. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  259. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  260. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  261. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  264. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  265. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
  266. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,307 @@
1
+ """Unit tests for NetHack trajectory recording and visualization."""
2
+
3
+ import unittest
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import numpy as np
8
+ from datetime import datetime
9
+
10
+ from src.synth_env.examples.nethack.helpers.trajectory_recorder import (
11
+ TrajectoryRecorder,
12
+ TrajectoryFrame,
13
+ TrajectoryMetadata,
14
+ )
15
+ from src.synth_env.examples.nethack.helpers.visualization.visualizer import (
16
+ NetHackVisualizer,
17
+ )
18
+
19
+
20
+ class TestTrajectoryRecorder(unittest.TestCase):
21
+ """Test trajectory recording functionality."""
22
+
23
+ def setUp(self):
24
+ """Create temporary directory for tests."""
25
+ self.test_dir = tempfile.mkdtemp()
26
+ self.recorder = TrajectoryRecorder(self.test_dir)
27
+
28
+ def tearDown(self):
29
+ """Clean up temporary directory."""
30
+ shutil.rmtree(self.test_dir)
31
+
32
+ def test_start_recording(self):
33
+ """Test starting a recording."""
34
+ trajectory_id = self.recorder.start_recording("wizard", "test_task")
35
+
36
+ self.assertIsNotNone(trajectory_id)
37
+ self.assertTrue(self.recorder.is_recording)
38
+ self.assertEqual(self.recorder.metadata.character_role, "wizard")
39
+ self.assertEqual(self.recorder.metadata.task_id, "test_task")
40
+ self.assertEqual(self.recorder.current_step, 0)
41
+
42
+ def test_record_step(self):
43
+ """Test recording individual steps."""
44
+ self.recorder.start_recording("knight")
45
+
46
+ # Create test observation
47
+ obs = {
48
+ "player_stats": {
49
+ "x": 10,
50
+ "y": 20,
51
+ "hp": 16,
52
+ "max_hp": 16,
53
+ "depth": 1,
54
+ "gold": 0,
55
+ "experience_level": 1,
56
+ },
57
+ "ascii_map": "###\n#@#\n###",
58
+ "message": "Welcome to NetHack!",
59
+ "inventory": [],
60
+ }
61
+
62
+ # Record steps
63
+ self.recorder.record_step("north", obs, 0.0, False, {})
64
+ self.recorder.record_step("east", obs, 1.0, False, {"extra": "info"})
65
+
66
+ self.assertEqual(len(self.recorder.frames), 2)
67
+ self.assertEqual(self.recorder.current_step, 2)
68
+ self.assertEqual(self.recorder.total_reward, 1.0)
69
+
70
+ # Check frame contents
71
+ frame1 = self.recorder.frames[0]
72
+ self.assertEqual(frame1.action, "north")
73
+ self.assertEqual(frame1.reward, 0.0)
74
+ self.assertFalse(frame1.done)
75
+
76
+ frame2 = self.recorder.frames[1]
77
+ self.assertEqual(frame2.action, "east")
78
+ self.assertEqual(frame2.reward, 1.0)
79
+ self.assertEqual(frame2.info["extra"], "info")
80
+
81
+ def test_stop_recording(self):
82
+ """Test stopping a recording."""
83
+ self.recorder.start_recording("monk")
84
+
85
+ obs = {"player_stats": {"depth": 3}}
86
+ self.recorder.record_step("down", obs, 5.0, False, {})
87
+
88
+ self.recorder.stop_recording("completed", {"achievement1": True})
89
+
90
+ self.assertFalse(self.recorder.is_recording)
91
+ self.assertEqual(self.recorder.metadata.final_status, "completed")
92
+ self.assertEqual(self.recorder.metadata.max_depth_reached, 3)
93
+ self.assertEqual(self.recorder.metadata.achievements, {"achievement1": True})
94
+
95
+ def test_save_and_load_trajectory(self):
96
+ """Test saving and loading trajectories."""
97
+ # Create and save trajectory
98
+ self.recorder.start_recording("rogue", "test_save")
99
+
100
+ obs = {
101
+ "player_stats": {"x": 5, "y": 10, "depth": 2},
102
+ "ascii_map": "test_map",
103
+ "message": "test message",
104
+ }
105
+
106
+ self.recorder.record_step("wait", obs, 0.5, False, {})
107
+ self.recorder.record_step("search", obs, 1.5, True, {"final": True})
108
+ self.recorder.stop_recording("died")
109
+
110
+ filepath = self.recorder.save_trajectory()
111
+ self.assertTrue(Path(filepath).exists())
112
+
113
+ # Load trajectory
114
+ loaded_recorder, metadata, frames = TrajectoryRecorder.load_trajectory(filepath)
115
+
116
+ # Check metadata
117
+ self.assertEqual(metadata.character_role, "rogue")
118
+ self.assertEqual(metadata.task_id, "test_save")
119
+ self.assertEqual(metadata.total_steps, 2)
120
+ self.assertEqual(metadata.total_reward, 2.0)
121
+ self.assertEqual(metadata.final_status, "died")
122
+
123
+ # Check frames
124
+ self.assertEqual(len(frames), 2)
125
+ self.assertEqual(frames[0].action, "wait")
126
+ self.assertEqual(frames[1].action, "search")
127
+ self.assertTrue(frames[1].done)
128
+
129
+ def test_numpy_serialization(self):
130
+ """Test serialization of numpy arrays in observations."""
131
+ self.recorder.start_recording("wizard")
132
+
133
+ # Create observation with numpy arrays
134
+ obs = {
135
+ "map_array": np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32),
136
+ "position": np.array([10, 20]),
137
+ "nested": {"data": np.zeros((2, 2), dtype=np.float32)},
138
+ }
139
+
140
+ self.recorder.record_step("test", obs, 0.0, False, {})
141
+ self.recorder.stop_recording()
142
+
143
+ # Save and load
144
+ filepath = self.recorder.save_trajectory()
145
+ _, _, frames = TrajectoryRecorder.load_trajectory(filepath)
146
+
147
+ # Check numpy arrays are restored correctly
148
+ loaded_obs = frames[0].observation
149
+ np.testing.assert_array_equal(loaded_obs["map_array"], obs["map_array"])
150
+ np.testing.assert_array_equal(loaded_obs["position"], obs["position"])
151
+ np.testing.assert_array_equal(loaded_obs["nested"]["data"], obs["nested"]["data"])
152
+
153
+ def test_get_summary(self):
154
+ """Test trajectory summary generation."""
155
+ self.recorder.start_recording("barbarian")
156
+
157
+ obs = {"player_stats": {"depth": 1}}
158
+
159
+ # Record various actions
160
+ self.recorder.record_step("north", obs, 1.0, False, {})
161
+ self.recorder.record_step("north", obs, 0.0, False, {})
162
+ self.recorder.record_step("east", obs, 2.0, False, {})
163
+ self.recorder.record_step("search", obs, 0.0, False, {})
164
+ self.recorder.record_step("north", obs, 1.0, True, {})
165
+
166
+ summary = self.recorder.get_summary()
167
+
168
+ self.assertEqual(summary["total_steps"], 5)
169
+ self.assertEqual(summary["total_reward"], 4.0)
170
+ self.assertEqual(summary["unique_actions"], 3)
171
+ self.assertEqual(summary["actions_distribution"]["north"], 3)
172
+ self.assertEqual(summary["average_reward_per_step"], 0.8)
173
+
174
+
175
+ class TestNetHackVisualizer(unittest.TestCase):
176
+ """Test visualization functionality."""
177
+
178
+ def setUp(self):
179
+ """Create visualizer instance."""
180
+ self.viz = NetHackVisualizer(cell_size=10, font_size=8)
181
+ self.test_dir = tempfile.mkdtemp()
182
+
183
+ def tearDown(self):
184
+ """Clean up."""
185
+ shutil.rmtree(self.test_dir)
186
+
187
+ def test_ascii_to_image(self):
188
+ """Test ASCII map to image conversion."""
189
+ ascii_map = """######
190
+ #....#
191
+ #.@..#
192
+ #....#
193
+ ######"""
194
+
195
+ # Test without highlight
196
+ img = self.viz.ascii_to_image(ascii_map)
197
+ self.assertEqual(img.shape[0], 5 * self.viz.cell_size) # 5 rows
198
+ self.assertEqual(img.shape[1], 6 * self.viz.cell_size) # 6 cols
199
+ self.assertEqual(img.shape[2], 3) # RGB
200
+
201
+ # Test with highlight
202
+ img_highlight = self.viz.ascii_to_image(ascii_map, highlight_pos=(2, 2))
203
+ self.assertIsNotNone(img_highlight)
204
+
205
+ def test_create_frame_image(self):
206
+ """Test frame image creation."""
207
+ obs = {
208
+ "ascii_map": "#####\n#@..#\n#####",
209
+ "player_stats": {
210
+ "x": 1,
211
+ "y": 1,
212
+ "hp": 10,
213
+ "max_hp": 10,
214
+ "experience_level": 1,
215
+ "depth": 1,
216
+ "gold": 0,
217
+ },
218
+ "message": "Test message",
219
+ }
220
+
221
+ # Test without stats
222
+ img = self.viz.create_frame_image(obs, include_stats=False)
223
+ self.assertEqual(len(img.shape), 3)
224
+
225
+ # Test with stats (if PIL available)
226
+ img_stats = self.viz.create_frame_image(obs, include_stats=True)
227
+ self.assertEqual(len(img_stats.shape), 3)
228
+
229
+ def test_plot_trajectory_stats(self):
230
+ """Test trajectory statistics plotting."""
231
+ frames = []
232
+ for i in range(10):
233
+ frames.append(
234
+ {
235
+ "action": "north" if i % 2 == 0 else "south",
236
+ "observation": {
237
+ "player_stats": {
238
+ "x": i,
239
+ "y": 10 - i,
240
+ "depth": 1 + i // 5,
241
+ "hp": 10 - i // 2,
242
+ }
243
+ },
244
+ "reward": 1.0 if i % 3 == 0 else 0.0,
245
+ }
246
+ )
247
+
248
+ output_path = Path(self.test_dir) / "test_stats.png"
249
+ self.viz.plot_trajectory_stats(frames, str(output_path))
250
+
251
+ self.assertTrue(output_path.exists())
252
+
253
+ def test_plot_action_distribution(self):
254
+ """Test action distribution plotting."""
255
+ frames = []
256
+ actions = ["north", "south", "east", "west", "wait", "search"]
257
+
258
+ for i in range(20):
259
+ frames.append({"action": actions[i % len(actions)], "observation": {}, "reward": 0.0})
260
+
261
+ output_path = Path(self.test_dir) / "test_actions.png"
262
+ self.viz.plot_action_distribution(frames, str(output_path))
263
+
264
+ self.assertTrue(output_path.exists())
265
+
266
+
267
+ class TestTrajectoryFrame(unittest.TestCase):
268
+ """Test TrajectoryFrame serialization."""
269
+
270
+ def test_frame_serialization(self):
271
+ """Test frame to_dict and from_dict."""
272
+ frame = TrajectoryFrame(
273
+ step=5,
274
+ action="test_action",
275
+ observation={
276
+ "array_data": np.array([1, 2, 3]),
277
+ "nested": {"matrix": np.eye(2, dtype=np.float32)},
278
+ "list_data": [1, 2, 3],
279
+ "string": "test",
280
+ },
281
+ reward=2.5,
282
+ done=False,
283
+ info={"extra": "data"},
284
+ timestamp=datetime.now().timestamp(),
285
+ )
286
+
287
+ # Serialize
288
+ frame_dict = frame.to_dict()
289
+
290
+ # Check numpy arrays are converted
291
+ self.assertEqual(frame_dict["observation"]["array_data"]["type"], "ndarray")
292
+ self.assertEqual(frame_dict["observation"]["nested"]["matrix"]["type"], "ndarray")
293
+
294
+ # Deserialize
295
+ restored_frame = TrajectoryFrame.from_dict(frame_dict)
296
+
297
+ # Check restoration
298
+ self.assertEqual(restored_frame.step, 5)
299
+ self.assertEqual(restored_frame.action, "test_action")
300
+ np.testing.assert_array_equal(restored_frame.observation["array_data"], np.array([1, 2, 3]))
301
+ np.testing.assert_array_equal(
302
+ restored_frame.observation["nested"]["matrix"], np.eye(2, dtype=np.float32)
303
+ )
304
+
305
+
306
+ if __name__ == "__main__":
307
+ unittest.main()
@@ -0,0 +1,7 @@
1
+ from .config_logging import configure_logging
2
+ from .environment import PokemonRedEnvironment
3
+
4
+ # Configure logging when red module is imported
5
+ configure_logging()
6
+
7
+ __all__ = ["PokemonRedEnvironment"]
@@ -0,0 +1 @@
1
+ # Agent demos for Pokemon Red environment