synth-ai 0.2.4.dev3__py3-none-any.whl → 0.2.4.dev5__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 (105) hide show
  1. synth_ai/environments/examples/__init__.py +1 -0
  2. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  3. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  4. synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
  5. synth_ai/environments/examples/crafter_classic/engine.py +575 -0
  6. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  7. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  8. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  9. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +266 -0
  10. synth_ai/environments/examples/crafter_classic/environment.py +364 -0
  11. synth_ai/environments/examples/crafter_classic/taskset.py +233 -0
  12. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +229 -0
  13. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +298 -0
  14. synth_ai/environments/examples/crafter_custom/__init__.py +4 -0
  15. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +7 -0
  16. synth_ai/environments/examples/crafter_custom/crafter/config.py +182 -0
  17. synth_ai/environments/examples/crafter_custom/crafter/constants.py +8 -0
  18. synth_ai/environments/examples/crafter_custom/crafter/engine.py +269 -0
  19. synth_ai/environments/examples/crafter_custom/crafter/env.py +266 -0
  20. synth_ai/environments/examples/crafter_custom/crafter/objects.py +418 -0
  21. synth_ai/environments/examples/crafter_custom/crafter/recorder.py +187 -0
  22. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +119 -0
  23. synth_ai/environments/examples/crafter_custom/dataset_builder.py +373 -0
  24. synth_ai/environments/examples/crafter_custom/environment.py +312 -0
  25. synth_ai/environments/examples/crafter_custom/run_dataset.py +305 -0
  26. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  27. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  28. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  29. synth_ai/environments/examples/enron/engine.py +291 -0
  30. synth_ai/environments/examples/enron/environment.py +165 -0
  31. synth_ai/environments/examples/enron/taskset.py +112 -0
  32. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  33. synth_ai/environments/examples/minigrid/engine.py +589 -0
  34. synth_ai/environments/examples/minigrid/environment.py +274 -0
  35. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  36. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  37. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  38. synth_ai/environments/examples/nethack/__init__.py +7 -0
  39. synth_ai/environments/examples/nethack/achievements.py +337 -0
  40. synth_ai/environments/examples/nethack/engine.py +738 -0
  41. synth_ai/environments/examples/nethack/environment.py +255 -0
  42. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  43. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  44. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  45. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  46. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  47. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  48. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  49. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  50. synth_ai/environments/examples/nethack/taskset.py +323 -0
  51. synth_ai/environments/examples/red/__init__.py +7 -0
  52. synth_ai/environments/examples/red/config_logging.py +110 -0
  53. synth_ai/environments/examples/red/engine.py +693 -0
  54. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  55. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  56. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  57. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  58. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  59. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  60. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  61. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  62. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  63. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  64. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  69. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  70. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  71. synth_ai/environments/examples/red/environment.py +235 -0
  72. synth_ai/environments/examples/red/taskset.py +77 -0
  73. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  74. synth_ai/environments/examples/sokoban/engine.py +675 -0
  75. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  76. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  77. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  78. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  79. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  80. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  81. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  82. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  87. synth_ai/environments/examples/sokoban/environment.py +228 -0
  88. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  89. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  90. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  91. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  92. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  93. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  94. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  95. synth_ai/environments/examples/verilog/__init__.py +10 -0
  96. synth_ai/environments/examples/verilog/engine.py +328 -0
  97. synth_ai/environments/examples/verilog/environment.py +349 -0
  98. synth_ai/environments/examples/verilog/taskset.py +418 -0
  99. synth_ai/tracing_v3/examples/basic_usage.py +188 -0
  100. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/METADATA +1 -1
  101. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/RECORD +105 -6
  102. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/WHEEL +0 -0
  103. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/entry_points.txt +0 -0
  104. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/licenses/LICENSE +0 -0
  105. {synth_ai-0.2.4.dev3.dist-info → synth_ai-0.2.4.dev5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,229 @@
1
+ """
2
+ Trace hooks for Crafter Classic environment - v3 version.
3
+ Updated to use the v3 tracing architecture with async support.
4
+ """
5
+
6
+ from typing import Any, Optional, Dict, Set
7
+ from datetime import datetime
8
+
9
+ from synth_ai.tracing_v3.hooks import HookManager
10
+ from synth_ai.tracing_v3.abstractions import BaseEvent, EnvironmentEvent
11
+
12
+
13
+ # Achievement categorization by difficulty
14
+ EASY_ACHIEVEMENTS = {
15
+ "collect_wood",
16
+ "collect_stone",
17
+ "collect_sapling",
18
+ "collect_drink",
19
+ "place_stone",
20
+ "place_table",
21
+ "wake_up",
22
+ "eat_plant",
23
+ }
24
+
25
+ MEDIUM_ACHIEVEMENTS = {
26
+ "make_wood_pickaxe",
27
+ "make_wood_sword",
28
+ "place_furnace",
29
+ "place_plant",
30
+ "collect_coal",
31
+ "collect_iron",
32
+ "eat_cow",
33
+ }
34
+
35
+ HARD_ACHIEVEMENTS = {
36
+ "make_stone_pickaxe",
37
+ "make_stone_sword",
38
+ "make_iron_pickaxe",
39
+ "make_iron_sword",
40
+ "collect_diamond",
41
+ "defeat_skeleton",
42
+ "defeat_zombie",
43
+ }
44
+
45
+
46
+ async def check_easy_achievements(event_obj: BaseEvent, **kwargs) -> Optional[Dict[str, Any]]:
47
+ """Hook that fires when an easy achievement is unlocked."""
48
+ # Only process EnvironmentEvents
49
+ if not isinstance(event_obj, EnvironmentEvent):
50
+ return None
51
+
52
+ # Get achievements before and after
53
+ before_achievements = {}
54
+ after_achievements = {}
55
+
56
+ state_before = event_obj.system_state_before
57
+ state_after = event_obj.system_state_after
58
+
59
+ if isinstance(state_before, dict):
60
+ before_achievements = state_before.get("public_state", {}).get("achievements_status", {})
61
+ if isinstance(state_after, dict):
62
+ after_achievements = state_after.get("public_state", {}).get("achievements_status", {})
63
+
64
+ # Find new easy achievements
65
+ new_easy_achievements = []
66
+ for achievement, status in after_achievements.items():
67
+ if status and not before_achievements.get(achievement, False):
68
+ if achievement in EASY_ACHIEVEMENTS:
69
+ new_easy_achievements.append(achievement)
70
+
71
+ if new_easy_achievements:
72
+ # print(f"🎯 Easy achievement(s) unlocked: {', '.join(new_easy_achievements)}") # Disabled for clean output
73
+ return {
74
+ "achievements": new_easy_achievements,
75
+ "difficulty": "easy",
76
+ "timestamp": datetime.now().isoformat(),
77
+ }
78
+ return None
79
+
80
+
81
+ async def check_medium_achievements(event_obj: BaseEvent, **kwargs) -> Optional[Dict[str, Any]]:
82
+ """Hook that fires when a medium achievement is unlocked."""
83
+ # Only process EnvironmentEvents
84
+ if not isinstance(event_obj, EnvironmentEvent):
85
+ return None
86
+
87
+ # Get achievements before and after
88
+ before_achievements = {}
89
+ after_achievements = {}
90
+
91
+ state_before = event_obj.system_state_before
92
+ state_after = event_obj.system_state_after
93
+
94
+ if isinstance(state_before, dict):
95
+ before_achievements = state_before.get("public_state", {}).get("achievements_status", {})
96
+ if isinstance(state_after, dict):
97
+ after_achievements = state_after.get("public_state", {}).get("achievements_status", {})
98
+
99
+ # Find new medium achievements
100
+ new_medium_achievements = []
101
+ for achievement, status in after_achievements.items():
102
+ if status and not before_achievements.get(achievement, False):
103
+ if achievement in MEDIUM_ACHIEVEMENTS:
104
+ new_medium_achievements.append(achievement)
105
+
106
+ if new_medium_achievements:
107
+ # print(f"⭐ Medium achievement(s) unlocked: {', '.join(new_medium_achievements)}") # Disabled for clean output
108
+ return {
109
+ "achievements": new_medium_achievements,
110
+ "difficulty": "medium",
111
+ "timestamp": datetime.now().isoformat(),
112
+ }
113
+ return None
114
+
115
+
116
+ async def check_hard_achievements(event_obj: BaseEvent, **kwargs) -> Optional[Dict[str, Any]]:
117
+ """Hook that fires when a hard achievement is unlocked."""
118
+ # Only process EnvironmentEvents
119
+ if not isinstance(event_obj, EnvironmentEvent):
120
+ return None
121
+
122
+ # Get achievements before and after
123
+ before_achievements = {}
124
+ after_achievements = {}
125
+
126
+ state_before = event_obj.system_state_before
127
+ state_after = event_obj.system_state_after
128
+
129
+ if isinstance(state_before, dict):
130
+ before_achievements = state_before.get("public_state", {}).get("achievements_status", {})
131
+ if isinstance(state_after, dict):
132
+ after_achievements = state_after.get("public_state", {}).get("achievements_status", {})
133
+
134
+ # Find new hard achievements
135
+ new_hard_achievements = []
136
+ for achievement, status in after_achievements.items():
137
+ if status and not before_achievements.get(achievement, False):
138
+ if achievement in HARD_ACHIEVEMENTS:
139
+ new_hard_achievements.append(achievement)
140
+
141
+ if new_hard_achievements:
142
+ # print(f"🏆 Hard achievement(s) unlocked: {', '.join(new_hard_achievements)}") # Disabled for clean output
143
+ return {
144
+ "achievements": new_hard_achievements,
145
+ "difficulty": "hard",
146
+ "timestamp": datetime.now().isoformat(),
147
+ }
148
+ return None
149
+
150
+
151
+ async def log_invalid_actions(event_obj: BaseEvent, **kwargs) -> Optional[Dict[str, Any]]:
152
+ """Hook that logs invalid actions."""
153
+ from synth_ai.tracing_v3.abstractions import RuntimeEvent
154
+
155
+ # Only process RuntimeEvents
156
+ if not isinstance(event_obj, RuntimeEvent):
157
+ return None
158
+
159
+ # Check if action was invalid
160
+ if event_obj.metadata.get("valid") is False:
161
+ action_name = event_obj.metadata.get("action_name", "unknown")
162
+ # print(f"⚠️ Invalid action attempted: {action_name}") # Disabled for clean output
163
+ return {
164
+ "action": action_name,
165
+ "valid": False,
166
+ "timestamp": datetime.now().isoformat(),
167
+ }
168
+ return None
169
+
170
+
171
+ async def track_reward_milestones(event_obj: BaseEvent, **kwargs) -> Optional[Dict[str, Any]]:
172
+ """Hook that tracks significant reward events."""
173
+ # Only process EnvironmentEvents
174
+ if not isinstance(event_obj, EnvironmentEvent):
175
+ return None
176
+
177
+ reward = event_obj.reward
178
+ if reward and reward >= 1.0: # Significant positive reward
179
+ # print(f"💰 High reward received: {reward}") # Disabled for clean output
180
+ return {
181
+ "reward": reward,
182
+ "timestamp": datetime.now().isoformat(),
183
+ }
184
+ return None
185
+
186
+
187
+ # Create the global CRAFTER_HOOKS instance
188
+ CRAFTER_HOOKS = HookManager()
189
+
190
+ # Register all hooks
191
+ CRAFTER_HOOKS.register(
192
+ "event_recorded",
193
+ check_easy_achievements,
194
+ name="easy_achievements",
195
+ priority=10,
196
+ event_types=["environment"],
197
+ )
198
+
199
+ CRAFTER_HOOKS.register(
200
+ "event_recorded",
201
+ check_medium_achievements,
202
+ name="medium_achievements",
203
+ priority=10,
204
+ event_types=["environment"],
205
+ )
206
+
207
+ CRAFTER_HOOKS.register(
208
+ "event_recorded",
209
+ check_hard_achievements,
210
+ name="hard_achievements",
211
+ priority=10,
212
+ event_types=["environment"],
213
+ )
214
+
215
+ CRAFTER_HOOKS.register(
216
+ "event_recorded",
217
+ log_invalid_actions,
218
+ name="invalid_actions",
219
+ priority=5,
220
+ event_types=["runtime"],
221
+ )
222
+
223
+ CRAFTER_HOOKS.register(
224
+ "event_recorded",
225
+ track_reward_milestones,
226
+ name="reward_milestones",
227
+ priority=5,
228
+ event_types=["environment"],
229
+ )
@@ -0,0 +1,298 @@
1
+ """
2
+ Simplified monkey patch for configurable world generation in crafter.
3
+ This version modifies generation parameters rather than rewriting functions.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ from typing import Dict, Any, Optional
9
+ import crafter
10
+
11
+ print("[PATCH] Attempting to apply simplified Crafter world configuration patch...")
12
+
13
+ # World configuration presets
14
+ WORLD_CONFIGS = {
15
+ "easy": {
16
+ "name": "Easy Mode",
17
+ "description": "More resources, fewer enemies",
18
+ # Modify spawn probabilities by multiplying original values
19
+ "spawn_multipliers": {
20
+ "tree": 1.5, # 50% more trees
21
+ "coal": 1.5, # 50% more coal
22
+ "iron": 1.5, # 50% more iron
23
+ "diamond": 3.0, # 3x more diamonds
24
+ "cow": 2.0, # 2x more cows
25
+ "zombie": 0.3, # 70% fewer zombies
26
+ "skeleton": 0.2, # 80% fewer skeletons
27
+ },
28
+ # Modify spawn distances
29
+ "spawn_distances": {
30
+ "zombie": 15, # Farther away (default 10)
31
+ "skeleton": 10, # Farther away (default varies)
32
+ "cow": 2, # Closer (default 3)
33
+ },
34
+ },
35
+ "normal": {
36
+ "name": "Normal Mode",
37
+ "description": "Standard crafter experience",
38
+ "spawn_multipliers": {
39
+ "tree": 1.0,
40
+ "coal": 1.0,
41
+ "iron": 1.0,
42
+ "diamond": 1.0,
43
+ "cow": 1.0,
44
+ "zombie": 1.0,
45
+ "skeleton": 1.0,
46
+ },
47
+ "spawn_distances": {"zombie": 10, "skeleton": 7, "cow": 3},
48
+ },
49
+ "hard": {
50
+ "name": "Hard Mode",
51
+ "description": "Fewer resources, more enemies",
52
+ "spawn_multipliers": {
53
+ "tree": 0.5, # 50% fewer trees
54
+ "coal": 0.5, # 50% less coal
55
+ "iron": 0.5, # 50% less iron
56
+ "diamond": 0.3, # 70% fewer diamonds
57
+ "cow": 0.3, # 70% fewer cows
58
+ "zombie": 3.0, # 3x more zombies
59
+ "skeleton": 3.0, # 3x more skeletons
60
+ },
61
+ "spawn_distances": {
62
+ "zombie": 5, # Much closer
63
+ "skeleton": 4, # Much closer
64
+ "cow": 8, # Farther away
65
+ },
66
+ },
67
+ "peaceful": {
68
+ "name": "Peaceful Mode",
69
+ "description": "No enemies, more resources",
70
+ "spawn_multipliers": {
71
+ "tree": 2.0,
72
+ "coal": 2.0,
73
+ "iron": 2.0,
74
+ "diamond": 5.0,
75
+ "cow": 3.0,
76
+ "zombie": 0.0, # No zombies
77
+ "skeleton": 0.0, # No skeletons
78
+ },
79
+ "spawn_distances": {"zombie": 100, "skeleton": 100, "cow": 1},
80
+ },
81
+ }
82
+
83
+ # Store active configuration
84
+ _active_config = WORLD_CONFIGS["normal"]
85
+ _original_balance_chunk = None
86
+ _last_loaded_config = None # Track what was last loaded to avoid duplicate prints
87
+
88
+
89
+ def load_world_config(
90
+ config_name: str = "normal", config_path: Optional[str] = None
91
+ ) -> Dict[str, Any]:
92
+ """Load world configuration."""
93
+ global _active_config, _last_loaded_config
94
+
95
+ # Create a config identifier
96
+ config_id = config_path if config_path else config_name
97
+
98
+ # Only print if configuration actually changed
99
+ if _last_loaded_config != config_id:
100
+ if config_path and os.path.exists(config_path):
101
+ with open(config_path, "r") as f:
102
+ _active_config = json.load(f)
103
+ # print(f"[PATCH] Loaded custom world config from {config_path}")
104
+ elif config_name in WORLD_CONFIGS:
105
+ _active_config = WORLD_CONFIGS[config_name]
106
+ # print(f"[PATCH] Loaded '{config_name}' world configuration")
107
+ else:
108
+ _active_config = WORLD_CONFIGS["normal"]
109
+ # print(f"[PATCH] Unknown config '{config_name}', using 'normal'")
110
+
111
+ _last_loaded_config = config_id
112
+ else:
113
+ # Configuration hasn't changed, just return the active one
114
+ if config_path and os.path.exists(config_path):
115
+ with open(config_path, "r") as f:
116
+ _active_config = json.load(f)
117
+ elif config_name in WORLD_CONFIGS:
118
+ _active_config = WORLD_CONFIGS[config_name]
119
+ else:
120
+ _active_config = WORLD_CONFIGS["normal"]
121
+
122
+ return _active_config
123
+
124
+
125
+ # Patch world generation to use configuration
126
+ original_generate_world = crafter.worldgen.generate_world
127
+
128
+
129
+ def patched_generate_world(world, player):
130
+ """Patched world generation that applies spawn multipliers."""
131
+ # Apply configuration without modifying numpy RandomState
132
+ multipliers = _active_config.get("spawn_multipliers", {})
133
+ distances = _active_config.get("spawn_distances", {})
134
+
135
+ # Call original generation first
136
+ result = original_generate_world(world, player)
137
+
138
+ # Post-process to adjust spawns based on multipliers
139
+ # This is a simpler approach that modifies objects after generation
140
+ if multipliers:
141
+ # Remove some objects based on multipliers < 1.0
142
+ objects_to_remove = []
143
+ for obj in world._objects:
144
+ if obj is None or obj is player:
145
+ continue
146
+
147
+ obj_type = type(obj).__name__.lower()
148
+
149
+ # Check if we should remove this object based on multiplier
150
+ multiplier = 1.0
151
+ if "tree" in obj_type or hasattr(obj, "kind") and getattr(obj, "kind") == "tree":
152
+ multiplier = multipliers.get("tree", 1.0)
153
+ elif "cow" in obj_type:
154
+ multiplier = multipliers.get("cow", 1.0)
155
+ elif "zombie" in obj_type:
156
+ multiplier = multipliers.get("zombie", 1.0)
157
+ elif "skeleton" in obj_type:
158
+ multiplier = multipliers.get("skeleton", 1.0)
159
+
160
+ # Remove objects if multiplier < 1.0
161
+ if multiplier < 1.0 and world.random.random() > multiplier:
162
+ objects_to_remove.append(obj)
163
+
164
+ # Remove marked objects
165
+ for obj in objects_to_remove:
166
+ world.remove(obj)
167
+
168
+ # Add extra objects if multiplier > 1.0
169
+ # (This is more complex and would require spawning new objects)
170
+
171
+ # Post-process to adjust spawn distances
172
+ if distances:
173
+ # Adjust initial enemy positions based on distance config
174
+ for obj in list(world._objects):
175
+ if obj is None:
176
+ continue
177
+
178
+ obj_type = type(obj).__name__.lower()
179
+ if obj_type in distances:
180
+ min_dist = distances[obj_type]
181
+ player_pos = player.pos
182
+ obj_pos = obj.pos
183
+ dist = abs(obj_pos[0] - player_pos[0]) + abs(obj_pos[1] - player_pos[1])
184
+
185
+ if dist < min_dist:
186
+ # Remove objects too close to player
187
+ world.remove(obj)
188
+
189
+ return result
190
+
191
+
192
+ # Patch the balance function for dynamic spawning
193
+ def patched_balance_chunk(self, chunk, objs=None):
194
+ """Patched chunk balancing with config support."""
195
+ global _original_balance_chunk
196
+ if _original_balance_chunk is None:
197
+ return
198
+
199
+ multipliers = _active_config.get("spawn_multipliers", {})
200
+ distances = _active_config.get("spawn_distances", {})
201
+
202
+ # Call original balance function with objs parameter
203
+ if objs is not None:
204
+ _original_balance_chunk(self, chunk, objs)
205
+ else:
206
+ _original_balance_chunk(self, chunk)
207
+
208
+ # Post-process spawned objects based on multipliers
209
+ # Check if any new objects were spawned and adjust them
210
+ if multipliers:
211
+ # Get the chunk bounds
212
+ chunk_x = chunk[0] * self._world._chunk_size
213
+ chunk_y = chunk[1] * self._world._chunk_size
214
+ chunk_w = self._world._chunk_size
215
+ chunk_h = self._world._chunk_size
216
+
217
+ objects_to_remove = []
218
+ for obj in self._world._objects:
219
+ if obj is None:
220
+ continue
221
+
222
+ # Check if object is in this chunk
223
+ try:
224
+ pos_x, pos_y = obj.pos[0], obj.pos[1]
225
+ if chunk_x <= pos_x < chunk_x + chunk_w and chunk_y <= pos_y < chunk_y + chunk_h:
226
+ obj_type = type(obj).__name__.lower()
227
+ multiplier = 1.0
228
+
229
+ if "zombie" in obj_type:
230
+ multiplier = multipliers.get("zombie", 1.0)
231
+ elif "skeleton" in obj_type:
232
+ multiplier = multipliers.get("skeleton", 1.0)
233
+ elif "cow" in obj_type:
234
+ multiplier = multipliers.get("cow", 1.0)
235
+
236
+ # Remove objects based on multiplier
237
+ if multiplier < 1.0 and self._world.random.random() > multiplier:
238
+ objects_to_remove.append(obj)
239
+ except (ValueError, AttributeError):
240
+ # Skip objects with invalid position data
241
+ continue
242
+
243
+ # Remove marked objects
244
+ for obj in objects_to_remove:
245
+ self._world.remove(obj)
246
+
247
+
248
+ # Apply patches
249
+ crafter.worldgen.generate_world = patched_generate_world
250
+
251
+ # Store original balance_chunk
252
+ _original_balance_chunk = crafter.Env._balance_chunk
253
+ crafter.Env._balance_chunk = patched_balance_chunk
254
+
255
+ # Extend Env.__init__ to accept world_config
256
+ original_env_init = crafter.Env.__init__
257
+
258
+
259
+ def patched_env_init(
260
+ self,
261
+ area=(64, 64),
262
+ view=(9, 9),
263
+ length=10000,
264
+ seed=None,
265
+ world_config="normal",
266
+ world_config_path=None,
267
+ ):
268
+ """Extended Env.__init__ that accepts world configuration."""
269
+ # Load configuration
270
+ load_world_config(world_config, world_config_path)
271
+
272
+ # Call original init
273
+ original_env_init(self, area=area, view=view, length=length, seed=seed)
274
+
275
+ # Store config name
276
+ self._world_config_name = world_config if not world_config_path else "custom"
277
+
278
+
279
+ crafter.Env.__init__ = patched_env_init
280
+
281
+ print("[PATCH] Simplified Crafter world configuration patch complete.")
282
+ print("[PATCH] Available configs: easy, normal, hard, peaceful")
283
+
284
+ # Example custom config
285
+ EXAMPLE_CUSTOM_CONFIG = {
286
+ "name": "Custom Config",
287
+ "description": "My custom world",
288
+ "spawn_multipliers": {
289
+ "tree": 1.2,
290
+ "coal": 1.3,
291
+ "iron": 1.4,
292
+ "diamond": 2.0,
293
+ "cow": 1.5,
294
+ "zombie": 0.5,
295
+ "skeleton": 0.5,
296
+ },
297
+ "spawn_distances": {"zombie": 12, "skeleton": 8, "cow": 4},
298
+ }
@@ -0,0 +1,4 @@
1
+ """Custom Crafter environments with configurable world generation."""
2
+
3
+ # Note: Registration happens in the service app.py file
4
+ # This module just provides the CrafterCustomEnvironment class
@@ -0,0 +1,7 @@
1
+ from .env import Env
2
+ from .recorder import Recorder
3
+ from .config import WorldGenConfig, PRESETS
4
+ from . import constants
5
+
6
+ # Note: We don't register with gym since this is a custom version
7
+ # Users should import directly from this module