cogames 0.3.64__py3-none-any.whl → 0.3.65__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 (31) hide show
  1. cogames/cli/mission.py +25 -35
  2. cogames/cli/submit.py +1 -1
  3. cogames/cogs_vs_clips/clips.py +86 -0
  4. cogames/cogs_vs_clips/cog.py +14 -7
  5. cogames/cogs_vs_clips/cogsguard_tutorial.py +10 -11
  6. cogames/cogs_vs_clips/config.py +38 -0
  7. cogames/cogs_vs_clips/{cogs_vs_clips_mapgen.md → docs/cogs_vs_clips_mapgen.md} +6 -7
  8. cogames/cogs_vs_clips/evals/README.md +4 -4
  9. cogames/cogs_vs_clips/evals/cogsguard_evals.py +21 -6
  10. cogames/cogs_vs_clips/evals/diagnostic_evals.py +13 -100
  11. cogames/cogs_vs_clips/evals/difficulty_variants.py +9 -18
  12. cogames/cogs_vs_clips/evals/integrated_evals.py +8 -60
  13. cogames/cogs_vs_clips/evals/spanning_evals.py +48 -54
  14. cogames/cogs_vs_clips/mission.py +65 -277
  15. cogames/cogs_vs_clips/missions.py +16 -26
  16. cogames/cogs_vs_clips/sites.py +35 -25
  17. cogames/cogs_vs_clips/stations.py +33 -82
  18. cogames/cogs_vs_clips/team.py +44 -0
  19. cogames/cogs_vs_clips/{procedural.py → terrain.py} +12 -6
  20. cogames/cogs_vs_clips/variants.py +41 -118
  21. cogames/core.py +87 -0
  22. cogames/verbose.py +2 -2
  23. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/METADATA +2 -2
  24. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/RECORD +28 -27
  25. cogames/cogs_vs_clips/cogsguard_reward_variants.py +0 -153
  26. cogames/cogs_vs_clips/mission_utils.py +0 -19
  27. cogames/cogs_vs_clips/tutorial_missions.py +0 -25
  28. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/WHEEL +0 -0
  29. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/entry_points.txt +0 -0
  30. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/licenses/LICENSE +0 -0
  31. {cogames-0.3.64.dist-info → cogames-0.3.65.dist-info}/top_level.txt +0 -0
@@ -2,8 +2,8 @@ from typing import Optional
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from cogames.cogs_vs_clips.config import CvCConfig
5
6
  from mettagrid.base_config import Config
6
- from mettagrid.config import vibes
7
7
  from mettagrid.config.handler_config import (
8
8
  AOEConfig,
9
9
  ClearInventoryMutation,
@@ -25,48 +25,11 @@ from mettagrid.config.handler_config import (
25
25
  withdraw,
26
26
  )
27
27
  from mettagrid.config.mettagrid_config import (
28
- ChestConfig,
29
28
  GridObjectConfig,
30
29
  InventoryConfig,
31
30
  WallConfig,
32
31
  )
33
32
 
34
- resources = [
35
- "energy",
36
- "carbon",
37
- "oxygen",
38
- "germanium",
39
- "silicon",
40
- "heart",
41
- "decoder",
42
- "modulator",
43
- "resonator",
44
- "scrambler",
45
- ]
46
-
47
- # CogsGuard constants
48
- GEAR = ["aligner", "scrambler", "miner", "scout"]
49
- ELEMENTS = ["oxygen", "carbon", "germanium", "silicon"]
50
-
51
-
52
- HEART_COST = {e: 10 for e in ELEMENTS}
53
- COGSGUARD_ALIGN_COST = {"heart": 1}
54
- COGSGUARD_SCRAMBLE_COST = {"heart": 1}
55
-
56
- GEAR_COSTS = {
57
- "aligner": {"carbon": 3, "oxygen": 1, "germanium": 1, "silicon": 1},
58
- "scrambler": {"carbon": 1, "oxygen": 3, "germanium": 1, "silicon": 1},
59
- "miner": {"carbon": 1, "oxygen": 1, "germanium": 3, "silicon": 1},
60
- "scout": {"carbon": 1, "oxygen": 1, "germanium": 1, "silicon": 3},
61
- }
62
-
63
- GEAR_SYMBOLS = {
64
- "aligner": "🔗",
65
- "scrambler": "🌀",
66
- "miner": "⛏️",
67
- "scout": "🔭",
68
- }
69
-
70
33
 
71
34
  def _neg(recipe: dict[str, int]) -> dict[str, int]:
72
35
  return {k: -v for k, v in recipe.items()}
@@ -79,15 +42,10 @@ class CvCStationConfig(Config):
79
42
 
80
43
  class CvCWallConfig(CvCStationConfig):
81
44
  def station_cfg(self) -> WallConfig:
82
- return WallConfig(name="wall", render_symbol=vibes.VIBE_BY_NAME["wall"].symbol)
83
-
45
+ return WallConfig(name="wall", render_symbol="")
84
46
 
85
- # ==============================================================================
86
- # CogsGuard Station Configs
87
- # ==============================================================================
88
47
 
89
-
90
- class SimpleExtractorConfig(CvCStationConfig):
48
+ class CvCExtractorConfig(CvCStationConfig):
91
49
  """Simple resource extractor with inventory that transfers resources to actors."""
92
50
 
93
51
  resource: str = Field(description="The resource to extract")
@@ -95,10 +53,9 @@ class SimpleExtractorConfig(CvCStationConfig):
95
53
  small_amount: int = Field(default=1, description="Amount extracted without mining equipment")
96
54
  large_amount: int = Field(default=10, description="Amount extracted with mining equipment")
97
55
 
98
- def station_cfg(self) -> ChestConfig:
99
- return ChestConfig(
56
+ def station_cfg(self) -> GridObjectConfig:
57
+ return GridObjectConfig(
100
58
  name=f"{self.resource}_extractor",
101
- map_name=f"{self.resource}_extractor",
102
59
  render_symbol="📦",
103
60
  on_use_handlers={
104
61
  # Order matters: miner first so agents with miner gear get the bonus
@@ -115,24 +72,18 @@ class SimpleExtractorConfig(CvCStationConfig):
115
72
  )
116
73
 
117
74
 
118
- class JunctionConfig(CvCStationConfig):
75
+ class CvCJunctionConfig(CvCStationConfig):
119
76
  """Supply depot that receives element resources via default vibe into collective."""
120
77
 
121
- map_name: str = Field(description="Map name for this junction")
122
- team: Optional[str] = Field(default=None, description="Team/collective this junction belongs to")
123
78
  aoe_range: int = Field(default=10, description="Range for AOE effects")
124
79
  influence_deltas: dict[str, int] = Field(default_factory=lambda: {"influence": 10, "energy": 100, "hp": 100})
125
80
  attack_deltas: dict[str, int] = Field(default_factory=lambda: {"hp": -1, "influence": -100})
126
- elements: list[str] = Field(default_factory=lambda: ELEMENTS)
127
- align_cost: dict[str, int] = Field(default_factory=lambda: COGSGUARD_ALIGN_COST)
128
- scramble_cost: dict[str, int] = Field(default_factory=lambda: COGSGUARD_SCRAMBLE_COST)
129
81
 
130
- def station_cfg(self) -> GridObjectConfig:
82
+ def station_cfg(self, team: Optional[str] = None) -> GridObjectConfig:
131
83
  return GridObjectConfig(
132
84
  name="junction",
133
- map_name=self.map_name,
134
85
  render_symbol="📦",
135
- collective=self.team,
86
+ collective=team,
136
87
  aoes={
137
88
  "influence": AOEConfig(
138
89
  radius=self.aoe_range,
@@ -148,30 +99,33 @@ class JunctionConfig(CvCStationConfig):
148
99
  on_use_handlers={
149
100
  "deposit": Handler(
150
101
  filters=[isAlignedToActor()],
151
- mutations=[collectiveDeposit({resource: 100 for resource in self.elements})],
102
+ mutations=[collectiveDeposit({resource: 100 for resource in CvCConfig.ELEMENTS})],
152
103
  ),
153
104
  "align": Handler(
154
- filters=[isNeutral(), actorHas({"aligner": 1, "influence": 1, **self.align_cost})],
155
- mutations=[updateActor(_neg(self.align_cost)), alignToActor()],
105
+ filters=[isNeutral(), actorHas({"aligner": 1, "influence": 1, **CvCConfig.ALIGN_COST})],
106
+ mutations=[updateActor(_neg(CvCConfig.ALIGN_COST)), alignToActor()],
156
107
  ),
157
108
  "scramble": Handler(
158
- filters=[isEnemy(), actorHas({"scrambler": 1, **self.scramble_cost})],
159
- mutations=[removeAlignment(), updateActor(_neg(self.scramble_cost))],
109
+ filters=[isEnemy(), actorHas({"scrambler": 1, **CvCConfig.SCRAMBLE_COST})],
110
+ mutations=[removeAlignment(), updateActor(_neg(CvCConfig.SCRAMBLE_COST))],
160
111
  ),
161
112
  },
162
113
  )
163
114
 
164
115
 
165
- class HubConfig(JunctionConfig):
166
- """Main hub with influence AOE effect. A junction without align/scramble handlers."""
116
+ class CvCHubConfig(CvCStationConfig):
117
+ """Hub station that provides AOE influence/attack and accepts deposits."""
167
118
 
168
- def station_cfg(self) -> GridObjectConfig:
119
+ aoe_range: int = Field(default=10, description="Range for AOE effects")
120
+ influence_deltas: dict[str, int] = Field(default_factory=lambda: {"influence": 10, "energy": 100, "hp": 100})
121
+ attack_deltas: dict[str, int] = Field(default_factory=lambda: {"hp": -1, "influence": -100})
122
+ elements: list[str] = Field(default_factory=lambda: CvCConfig.ELEMENTS)
123
+
124
+ def station_cfg(self, team: str) -> GridObjectConfig:
169
125
  return GridObjectConfig(
170
126
  name="hub",
171
- map_name=self.map_name,
172
- render_name="hub",
173
127
  render_symbol="📦",
174
- collective=self.team,
128
+ collective=team,
175
129
  aoes={
176
130
  "influence": AOEConfig(
177
131
  radius=self.aoe_range,
@@ -193,18 +147,16 @@ class HubConfig(JunctionConfig):
193
147
  )
194
148
 
195
149
 
196
- class CogsGuardChestConfig(CvCStationConfig):
197
- """Chest for heart management in CogsGuard."""
150
+ class CvCChestConfig(CvCStationConfig):
151
+ """Chest station for heart management."""
198
152
 
199
- collective: str = Field(default="cogs", description="Collective this chest belongs to")
200
- heart_cost: dict[str, int] = Field(default_factory=lambda: HEART_COST)
153
+ heart_cost: dict[str, int] = Field(default_factory=lambda: CvCConfig.HEART_COST)
201
154
 
202
- def station_cfg(self) -> GridObjectConfig:
155
+ def station_cfg(self, team: str) -> GridObjectConfig:
203
156
  return GridObjectConfig(
204
157
  name="chest",
205
- map_name="chest",
206
158
  render_symbol="📦",
207
- collective=self.collective,
159
+ collective=team,
208
160
  on_use_handlers={
209
161
  "get_heart": Handler(
210
162
  filters=[isAlignedToActor(), targetCollectiveHas({"heart": 1})],
@@ -221,20 +173,19 @@ class CogsGuardChestConfig(CvCStationConfig):
221
173
  )
222
174
 
223
175
 
224
- class GearStationConfig(CvCStationConfig):
176
+ class CvCGearStationConfig(CvCStationConfig):
225
177
  """Gear station that clears all gear and adds the specified gear type."""
226
178
 
227
179
  gear_type: str = Field(description="Type of gear this station provides")
228
- collective: str = Field(default="cogs", description="Collective this station belongs to")
229
- gear_costs: dict[str, dict[str, int]] = Field(default_factory=lambda: GEAR_COSTS)
180
+ gear_costs: dict[str, dict[str, int]] = Field(default_factory=lambda: CvCConfig.GEAR_COSTS)
181
+ gear_symbols: dict[str, str] = Field(default_factory=lambda: CvCConfig.GEAR_SYMBOLS)
230
182
 
231
- def station_cfg(self) -> GridObjectConfig:
183
+ def station_cfg(self, team: str) -> GridObjectConfig:
232
184
  cost = self.gear_costs.get(self.gear_type, {})
233
185
  return GridObjectConfig(
234
186
  name=f"{self.gear_type}_station",
235
- map_name=f"{self.gear_type}_station",
236
- render_symbol=GEAR_SYMBOLS.get(self.gear_type, "⚙️"),
237
- collective=self.collective,
187
+ render_symbol=self.gear_symbols[self.gear_type],
188
+ collective=team,
238
189
  on_use_handlers={
239
190
  "keep_gear": Handler(
240
191
  filters=[isAlignedToActor(), actorHas({self.gear_type: 1})],
@@ -0,0 +1,44 @@
1
+ """Team configuration for CogsGuard missions.
2
+
3
+ Teams are named collectives (resource pools) shared by agents.
4
+ """
5
+
6
+ from pydantic import Field
7
+
8
+ from cogames.cogs_vs_clips.config import CvCConfig
9
+ from mettagrid.base_config import Config
10
+ from mettagrid.config.mettagrid_config import (
11
+ CollectiveConfig,
12
+ InventoryConfig,
13
+ ResourceLimitsConfig,
14
+ )
15
+
16
+
17
+ class CogTeam(Config):
18
+ """Configuration for a cogs team."""
19
+
20
+ name: str = Field(default="cogs", description="Team name")
21
+ wealth: int = Field(default=1, description="Wealth multiplier for initial resources")
22
+ num_agents: int = Field(default=8, ge=1, description="Number of agents in the team")
23
+
24
+ def collective_config(self) -> CollectiveConfig:
25
+ """Create a CollectiveConfig for this team.
26
+
27
+ Returns:
28
+ CollectiveConfig with resource limits and initial inventory.
29
+ """
30
+ return CollectiveConfig(
31
+ inventory=InventoryConfig(
32
+ limits={
33
+ "resources": ResourceLimitsConfig(min=10000, resources=CvCConfig.ELEMENTS),
34
+ "hearts": ResourceLimitsConfig(min=65535, resources=["heart"]),
35
+ },
36
+ initial={
37
+ "carbon": 10 * self.wealth,
38
+ "oxygen": 10 * self.wealth,
39
+ "germanium": 10 * self.wealth,
40
+ "silicon": 10 * self.wealth,
41
+ "heart": 5 * self.wealth,
42
+ },
43
+ ),
44
+ )
@@ -1,9 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
2
- from typing import Any, Literal, override
4
+ from typing import TYPE_CHECKING, Any, Literal, override
3
5
 
4
6
  import numpy as np
5
7
 
6
- from cogames.cogs_vs_clips.mission import Mission, MissionVariant
8
+ from cogames.core import CoGameMissionVariant
9
+
10
+ if TYPE_CHECKING:
11
+ from cogames.cogs_vs_clips.mission import CvCMission
7
12
  from mettagrid.config.mettagrid_config import MettaGridConfig
8
13
  from mettagrid.mapgen.area import AreaWhere
9
14
  from mettagrid.mapgen.mapgen import MapGen, MapGenConfig
@@ -65,6 +70,7 @@ class MachinaArenaConfig(SceneConfig):
65
70
  corner_bundle="extractors",
66
71
  cross_bundle="none",
67
72
  cross_distance=7,
73
+ junction_object="junction",
68
74
  )
69
75
 
70
76
  # Optional asteroid-shaped boundary mask.
@@ -547,7 +553,7 @@ class RandomTransform(Scene[RandomTransformConfig]):
547
553
  ]
548
554
 
549
555
 
550
- class EnvNodeVariant[T](MissionVariant, ABC):
556
+ class EnvNodeVariant[T](CoGameMissionVariant, ABC):
551
557
  @abstractmethod
552
558
  def extract_node(self, env: MettaGridConfig) -> T: ...
553
559
 
@@ -590,7 +596,7 @@ class MapSeedVariant(MapGenVariant):
590
596
 
591
597
  class BaseHubVariant(EnvNodeVariant[BaseHubConfig]):
592
598
  @override
593
- def compat(self, mission: Mission) -> bool:
599
+ def compat(self, mission: CvCMission) -> bool:
594
600
  env = mission.make_env()
595
601
  if not isinstance(env.game.map_builder, MapGen.Config):
596
602
  return False
@@ -618,7 +624,7 @@ class BaseHubVariant(EnvNodeVariant[BaseHubConfig]):
618
624
 
619
625
 
620
626
  class MachinaArenaVariant(EnvNodeVariant[MachinaArenaConfig]):
621
- def compat(self, mission: Mission) -> bool:
627
+ def compat(self, mission: CvCMission) -> bool:
622
628
  env = mission.make_env()
623
629
  return isinstance(env.game.map_builder, MapGen.Config) and isinstance(
624
630
  env.game.map_builder.instance, MachinaArena.Config
@@ -632,7 +638,7 @@ class MachinaArenaVariant(EnvNodeVariant[MachinaArenaConfig]):
632
638
 
633
639
 
634
640
  class SequentialMachinaArenaVariant(EnvNodeVariant[SequentialMachinaArenaConfig]):
635
- def compat(self, mission: Mission) -> bool:
641
+ def compat(self, mission: CvCMission) -> bool:
636
642
  env = mission.make_env()
637
643
  return isinstance(env.game.map_builder, MapGen.Config) and isinstance(
638
644
  env.game.map_builder.instance, SequentialMachinaArena.Config
@@ -1,95 +1,74 @@
1
- from typing import override
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, override
2
4
 
3
5
  from cogames.cogs_vs_clips.evals.difficulty_variants import DIFFICULTY_VARIANTS
4
- from cogames.cogs_vs_clips.mission import Mission, MissionVariant
5
- from cogames.cogs_vs_clips.procedural import BaseHubVariant, MachinaArenaVariant
6
- from mettagrid.config.action_config import VibeTransfer
7
- from mettagrid.config.game_value import stat
8
- from mettagrid.config.reward_config import reward
6
+ from cogames.cogs_vs_clips.terrain import BaseHubVariant, MachinaArenaVariant
7
+ from cogames.core import CoGameMissionVariant
9
8
  from mettagrid.map_builder.map_builder import MapBuilderConfig
10
9
  from mettagrid.mapgen.mapgen import MapGen
11
10
  from mettagrid.mapgen.scenes.base_hub import DEFAULT_EXTRACTORS as HUB_EXTRACTORS
12
11
  from mettagrid.mapgen.scenes.building_distributions import DistributionConfig, DistributionType
13
12
 
13
+ if TYPE_CHECKING:
14
+ from cogames.cogs_vs_clips.mission import CvCMission
15
+
16
+
17
+ class NumCogsVariant(CoGameMissionVariant):
18
+ name: str = "num_cogs"
19
+ description: str = "Set the number of cogs for the mission."
20
+ num_cogs: int
14
21
 
15
- class DarkSideVariant(MissionVariant):
22
+ @override
23
+ def modify_mission(self, mission: CvCMission) -> None:
24
+ if self.num_cogs < mission.site.min_cogs or self.num_cogs > mission.site.max_cogs:
25
+ raise ValueError(
26
+ f"Invalid number of cogs for {mission.site.name}: {self.num_cogs}. "
27
+ + f"Must be between {mission.site.min_cogs} and {mission.site.max_cogs}"
28
+ )
29
+
30
+ mission.num_cogs = self.num_cogs
31
+
32
+
33
+ class DarkSideVariant(CoGameMissionVariant):
16
34
  name: str = "dark_side"
17
35
  description: str = "You're on the dark side of the asteroid. You recharge slower."
18
36
 
19
37
  @override
20
- def modify_mission(self, mission):
21
- assert isinstance(mission, Mission)
38
+ def modify_mission(self, mission: CvCMission) -> None:
22
39
  mission.cog.energy_regen = 0
23
40
 
24
41
 
25
- class SuperChargedVariant(MissionVariant):
42
+ class SuperChargedVariant(CoGameMissionVariant):
26
43
  name: str = "super_charged"
27
44
  description: str = "The sun is shining on you. You recharge faster."
28
45
 
29
46
  @override
30
- def modify_mission(self, mission):
31
- assert isinstance(mission, Mission)
47
+ def modify_mission(self, mission: CvCMission) -> None:
32
48
  mission.cog.energy_regen += 2
33
49
 
34
50
 
35
- class RoughTerrainVariant(MissionVariant):
36
- name: str = "rough_terrain"
37
- description: str = "The terrain is rough. Moving is more energy intensive."
38
-
39
- @override
40
- def modify_mission(self, mission):
41
- assert isinstance(mission, Mission)
42
- mission.cog.move_energy_cost += 2
43
-
44
-
45
- class PackRatVariant(MissionVariant):
46
- name: str = "pack_rat"
47
- description: str = "Raise heart, cargo, energy, and gear caps to 255."
48
-
49
- @override
50
- def modify_mission(self, mission):
51
- assert isinstance(mission, Mission)
52
- mission.cog.heart_limit = max(mission.cog.heart_limit, 255)
53
- mission.cog.energy_limit = max(mission.cog.energy_limit, 255)
54
- mission.cog.cargo_limit = max(mission.cog.cargo_limit, 255)
55
- mission.cog.gear_limit = max(mission.cog.gear_limit, 255)
56
-
57
-
58
- class EnergizedVariant(MissionVariant):
51
+ class EnergizedVariant(CoGameMissionVariant):
59
52
  name: str = "energized"
60
53
  description: str = "Max energy and full regen so agents never run dry."
61
54
 
62
55
  @override
63
- def modify_mission(self, mission):
64
- assert isinstance(mission, Mission)
56
+ def modify_mission(self, mission: CvCMission) -> None:
65
57
  mission.cog.energy_limit = max(mission.cog.energy_limit, 255)
66
58
  mission.cog.energy_regen = mission.cog.energy_limit
67
59
 
68
60
 
69
- class CompassVariant(MissionVariant):
70
- name: str = "compass"
71
- description: str = "Enable compass observation."
72
-
73
- @override
74
- def modify_env(self, mission, env):
75
- env.game.obs.global_obs.compass = True
76
-
77
-
78
- class Small50Variant(MissionVariant):
61
+ class Small50Variant(CoGameMissionVariant):
79
62
  name: str = "small_50"
80
63
  description: str = "Set map size to 50x50 for quick runs."
81
64
 
82
65
  def modify_env(self, mission, env) -> None:
83
66
  map_builder = env.game.map_builder
84
- # Only set width/height if instance is a SceneConfig, not a MapBuilderConfig
85
- # When instance is a MapBuilderConfig, width and height must be None
86
67
  if isinstance(map_builder, MapGen.Config) and isinstance(map_builder.instance, MapBuilderConfig):
87
- # Skip setting width/height for MapBuilderConfig instances
88
68
  return
89
69
  env.game.map_builder = map_builder.model_copy(update={"width": 50, "height": 50})
90
70
 
91
71
 
92
- # Biome variants (weather) for procedural maps
93
72
  class DesertVariant(MachinaArenaVariant):
94
73
  name: str = "desert"
95
74
  description: str = "The desert sands make navigation challenging."
@@ -117,7 +96,6 @@ class CityVariant(MachinaArenaVariant):
117
96
  def modify_node(self, node):
118
97
  node.biome_weights = {"city": 1.0, "caves": 0.0, "desert": 0.0, "forest": 0.0}
119
98
  node.base_biome = "city"
120
- # Fill almost the entire map with the city layer
121
99
  node.density_scale = 1.0
122
100
  node.biome_count = 1
123
101
  node.max_biome_zone_fraction = 0.95
@@ -140,32 +118,29 @@ class DistantResourcesVariant(MachinaArenaVariant):
140
118
 
141
119
  @override
142
120
  def modify_node(self, node):
143
- # Bias buildings toward the map edges using bimodal clusters centered at
144
121
  node.building_coverage = 0.01
145
122
 
146
123
  vertical_edges = DistributionConfig(
147
124
  type=DistributionType.BIMODAL,
148
- center1_x=0.92, # top right corner
125
+ center1_x=0.92,
149
126
  center1_y=0.08,
150
- center2_x=0.08, # bottom left corner
127
+ center2_x=0.08,
151
128
  center2_y=0.92,
152
129
  cluster_std=0.18,
153
130
  )
154
131
  horizontal_edges = DistributionConfig(
155
132
  type=DistributionType.BIMODAL,
156
- center1_x=0.08, # top left corner
133
+ center1_x=0.08,
157
134
  center1_y=0.08,
158
- center2_x=0.92, # bottom right corner
135
+ center2_x=0.92,
159
136
  center2_y=0.92,
160
137
  cluster_std=0.18,
161
138
  )
162
139
 
163
- # Apply edge-biased distributions to extractors; other buildings follow the global distribution
164
140
  names = list(self.building_names)
165
141
  node.building_distributions = {
166
142
  name: (vertical_edges if i % 2 == 0 else horizontal_edges) for i, name in enumerate(names)
167
143
  }
168
- # Fallback for any unspecified building types
169
144
  node.distribution = DistributionConfig(type=DistributionType.UNIFORM)
170
145
 
171
146
 
@@ -180,10 +155,10 @@ class QuadrantBuildingsVariant(MachinaArenaVariant):
180
155
 
181
156
  names = list(node.building_names or self.building_names)
182
157
  centers = [
183
- (0.25, 0.25), # top-left
184
- (0.75, 0.25), # top-right
185
- (0.25, 0.75), # bottom-left
186
- (0.75, 0.75), # bottom-right
158
+ (0.25, 0.25),
159
+ (0.75, 0.25),
160
+ (0.25, 0.75),
161
+ (0.75, 0.75),
187
162
  ]
188
163
  dists: dict[str, DistributionConfig] = {}
189
164
  for i, name in enumerate(names):
@@ -206,8 +181,6 @@ class SingleResourceUniformVariant(MachinaArenaVariant):
206
181
 
207
182
  @override
208
183
  def modify_node(self, node):
209
- # Resolve resource to a concrete building name
210
- # Restrict building set to only the chosen building and enforce uniform distribution
211
184
  node.building_names = [self.building_name]
212
185
  node.building_weights = {self.building_name: 1.0}
213
186
  node.building_distributions = None
@@ -217,12 +190,10 @@ class SingleResourceUniformVariant(MachinaArenaVariant):
217
190
  class EmptyBaseVariant(BaseHubVariant):
218
191
  name: str = "empty_base"
219
192
  description: str = "Base hub with extractors removed from the four corners."
220
- # Extractor object names to remove, e.g., ["oxygen_extractor"]
221
193
  missing: list[str] = list(HUB_EXTRACTORS)
222
194
 
223
195
  @override
224
196
  def modify_node(self, node):
225
- # Use the default extractor order and blank out any that are missing
226
197
  missing_set = set(self.missing or [])
227
198
  corner_objects = [name if name not in missing_set else "" for name in HUB_EXTRACTORS]
228
199
  node.corner_objects = corner_objects
@@ -230,8 +201,6 @@ class EmptyBaseVariant(BaseHubVariant):
230
201
 
231
202
 
232
203
  class BalancedCornersVariant(MachinaArenaVariant):
233
- """Enable corner balancing to ensure fair spawn distances."""
234
-
235
204
  name: str = "balanced_corners"
236
205
  description: str = "Balance path distances from center to corners for fair spawns."
237
206
  balance_tolerance: float = 1.5
@@ -244,65 +213,19 @@ class BalancedCornersVariant(MachinaArenaVariant):
244
213
  node.max_balance_shortcuts = self.max_balance_shortcuts
245
214
 
246
215
 
247
- class TraderVariant(MissionVariant):
248
- name: str = "trader"
249
- description: str = "Agents can trade resources with each other."
250
-
251
- @override
252
- def modify_env(self, mission, env):
253
- # Define vibe transfers for trading resources (actor gives, target receives)
254
- trade_transfers = [
255
- VibeTransfer(vibe="carbon_a", target={"carbon": 1}, actor={"carbon": -1}),
256
- VibeTransfer(vibe="carbon_b", target={"carbon": 10}, actor={"carbon": -10}),
257
- VibeTransfer(vibe="oxygen_a", target={"oxygen": 1}, actor={"oxygen": -1}),
258
- VibeTransfer(vibe="oxygen_b", target={"oxygen": 10}, actor={"oxygen": -10}),
259
- VibeTransfer(vibe="germanium_a", target={"germanium": 1}, actor={"germanium": -1}),
260
- VibeTransfer(vibe="germanium_b", target={"germanium": 4}, actor={"germanium": -4}),
261
- VibeTransfer(vibe="silicon_a", target={"silicon": 10}, actor={"silicon": -10}),
262
- VibeTransfer(vibe="silicon_b", target={"silicon": 50}, actor={"silicon": -50}),
263
- VibeTransfer(vibe="heart_a", target={"heart": 1}, actor={"heart": -1}),
264
- VibeTransfer(vibe="heart_b", target={"heart": 4}, actor={"heart": -4}),
265
- ]
266
- # Enable transfer action with these vibes
267
- env.game.actions.transfer.enabled = True
268
- env.game.actions.transfer.vibe_transfers.extend(trade_transfers)
269
-
270
-
271
- class SharedRewardsVariant(MissionVariant):
272
- name: str = "shared_rewards"
273
- description: str = "Rewards for deposited hearts are shared among all agents."
274
-
275
- @override
276
- def modify_env(self, mission, env):
277
- num_cogs = mission.num_cogs if mission.num_cogs is not None else mission.site.min_cogs
278
- env.game.agent.rewards["chest_heart_deposited_by_agent"] = reward(
279
- stat("chest.heart.deposited_by_agent"), weight=0
280
- )
281
- env.game.agent.rewards["chest_heart_amount"] = reward(stat("chest.heart.amount"), weight=1 / num_cogs)
282
-
283
-
284
- # TODO - validate that all variant names are unique
285
- VARIANTS: list[MissionVariant] = [
216
+ VARIANTS: list[CoGameMissionVariant] = [
286
217
  CavesVariant(),
287
218
  CityVariant(),
288
- CompassVariant(),
289
219
  DarkSideVariant(),
290
220
  DesertVariant(),
291
221
  EmptyBaseVariant(),
292
222
  EnergizedVariant(),
293
223
  ForestVariant(),
294
- PackRatVariant(),
295
224
  QuadrantBuildingsVariant(),
296
- RoughTerrainVariant(),
297
- SharedRewardsVariant(),
298
225
  SingleResourceUniformVariant(),
299
226
  Small50Variant(),
300
227
  SuperChargedVariant(),
301
- TraderVariant(),
302
228
  *DIFFICULTY_VARIANTS,
303
229
  ]
304
230
 
305
- # Hidden variants registry: Remains usable but will NOT appear in `cogames variants` listing
306
- HIDDEN_VARIANTS: list[MissionVariant] = [
307
- # Example: ExperimentalVariant(), # keep empty by default
308
- ]
231
+ HIDDEN_VARIANTS: list[CoGameMissionVariant] = []