cogames 0.3.64__py3-none-any.whl → 0.3.68__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 (141) hide show
  1. cogames/cli/client.py +0 -3
  2. cogames/cli/docsync/docsync.py +7 -1
  3. cogames/cli/mission.py +68 -53
  4. cogames/cli/policy.py +26 -10
  5. cogames/cli/submit.py +128 -142
  6. cogames/cli/utils.py +5 -0
  7. cogames/cogs_vs_clips/clip_difficulty.py +57 -0
  8. cogames/cogs_vs_clips/clips.py +103 -0
  9. cogames/cogs_vs_clips/cog.py +29 -11
  10. cogames/cogs_vs_clips/cogsguard_curriculum.py +122 -0
  11. cogames/cogs_vs_clips/cogsguard_tutorial.py +15 -16
  12. cogames/cogs_vs_clips/config.py +38 -0
  13. cogames/cogs_vs_clips/{cogs_vs_clips_mapgen.md → docs/cogs_vs_clips_mapgen.md} +8 -10
  14. cogames/cogs_vs_clips/evals/README.md +11 -35
  15. cogames/cogs_vs_clips/evals/cogsguard_evals.py +21 -6
  16. cogames/cogs_vs_clips/evals/diagnostic_evals.py +13 -101
  17. cogames/cogs_vs_clips/evals/difficulty_variants.py +16 -28
  18. cogames/cogs_vs_clips/evals/integrated_evals.py +8 -60
  19. cogames/cogs_vs_clips/evals/spanning_evals.py +48 -54
  20. cogames/cogs_vs_clips/mission.py +93 -277
  21. cogames/cogs_vs_clips/missions.py +17 -27
  22. cogames/cogs_vs_clips/{cogsguard_reward_variants.py → reward_variants.py} +22 -2
  23. cogames/cogs_vs_clips/sites.py +41 -30
  24. cogames/cogs_vs_clips/stations.py +39 -84
  25. cogames/cogs_vs_clips/team.py +46 -0
  26. cogames/cogs_vs_clips/{procedural.py → terrain.py} +14 -8
  27. cogames/cogs_vs_clips/variants.py +201 -107
  28. cogames/cogs_vs_clips/weather.py +52 -0
  29. cogames/core.py +87 -0
  30. cogames/docs/SCRIPTED_AGENT.md +3 -3
  31. cogames/evaluate.py +4 -2
  32. cogames/main.py +357 -51
  33. cogames/maps/canidate1_1000.map +1 -1
  34. cogames/maps/canidate1_1000_stations.map +2 -2
  35. cogames/maps/canidate1_500.map +1 -1
  36. cogames/maps/canidate1_500_stations.map +2 -2
  37. cogames/maps/canidate2_1000.map +1 -1
  38. cogames/maps/canidate2_1000_stations.map +2 -2
  39. cogames/maps/canidate2_500.map +1 -1
  40. cogames/maps/canidate2_500_stations.map +1 -1
  41. cogames/maps/canidate3_1000.map +1 -1
  42. cogames/maps/canidate3_1000_stations.map +2 -2
  43. cogames/maps/canidate3_500.map +1 -1
  44. cogames/maps/canidate3_500_stations.map +2 -2
  45. cogames/maps/canidate4_500.map +1 -1
  46. cogames/maps/canidate4_500_stations.map +2 -2
  47. cogames/maps/cave_base_50.map +2 -2
  48. cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
  49. cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
  50. cogames/maps/diagnostic_evals/diagnostic_charge_up.map +6 -6
  51. cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +6 -6
  52. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +6 -6
  53. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +6 -6
  54. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +6 -6
  55. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +6 -6
  56. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +6 -6
  57. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +6 -6
  58. cogames/maps/diagnostic_evals/diagnostic_chest_near.map +6 -6
  59. cogames/maps/diagnostic_evals/diagnostic_chest_search.map +6 -6
  60. cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +6 -6
  61. cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +6 -6
  62. cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +6 -6
  63. cogames/maps/diagnostic_evals/diagnostic_memory.map +6 -6
  64. cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +6 -6
  65. cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
  66. cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
  67. cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +6 -6
  68. cogames/maps/diagnostic_evals/diagnostic_unclip.map +6 -6
  69. cogames/maps/evals/eval_balanced_spread.map +6 -6
  70. cogames/maps/evals/eval_clip_oxygen.map +6 -6
  71. cogames/maps/evals/eval_collect_resources.map +6 -6
  72. cogames/maps/evals/eval_collect_resources_hard.map +6 -6
  73. cogames/maps/evals/eval_collect_resources_medium.map +6 -6
  74. cogames/maps/evals/eval_divide_and_conquer.map +6 -6
  75. cogames/maps/evals/eval_energy_starved.map +6 -6
  76. cogames/maps/evals/eval_multi_coordinated_collect_hard.map +6 -6
  77. cogames/maps/evals/eval_oxygen_bottleneck.map +6 -6
  78. cogames/maps/evals/eval_single_use_world.map +6 -6
  79. cogames/maps/evals/extractor_hub_100x100.map +6 -6
  80. cogames/maps/evals/extractor_hub_30x30.map +6 -6
  81. cogames/maps/evals/extractor_hub_50x50.map +6 -6
  82. cogames/maps/evals/extractor_hub_70x70.map +6 -6
  83. cogames/maps/evals/extractor_hub_80x80.map +6 -6
  84. cogames/maps/machina_100_stations.map +2 -2
  85. cogames/maps/machina_200_stations.map +2 -2
  86. cogames/maps/machina_200_stations_small.map +2 -2
  87. cogames/maps/machina_eval_exp01.map +2 -2
  88. cogames/maps/machina_eval_template_large.map +2 -2
  89. cogames/maps/machinatrainer4agents.map +2 -2
  90. cogames/maps/machinatrainer4agentsbase.map +2 -2
  91. cogames/maps/machinatrainerbig.map +2 -2
  92. cogames/maps/machinatrainersmall.map +2 -2
  93. cogames/maps/planky_evals/aligner_avoid_aoe.map +6 -6
  94. cogames/maps/planky_evals/aligner_full_cycle.map +6 -6
  95. cogames/maps/planky_evals/aligner_gear.map +6 -6
  96. cogames/maps/planky_evals/aligner_hearts.map +6 -6
  97. cogames/maps/planky_evals/aligner_junction.map +6 -6
  98. cogames/maps/planky_evals/exploration_distant.map +6 -6
  99. cogames/maps/planky_evals/maze.map +6 -6
  100. cogames/maps/planky_evals/miner_best_resource.map +6 -6
  101. cogames/maps/planky_evals/miner_deposit.map +6 -6
  102. cogames/maps/planky_evals/miner_extract.map +6 -6
  103. cogames/maps/planky_evals/miner_full_cycle.map +6 -6
  104. cogames/maps/planky_evals/miner_gear.map +6 -6
  105. cogames/maps/planky_evals/multi_role.map +6 -6
  106. cogames/maps/planky_evals/resource_chain.map +6 -6
  107. cogames/maps/planky_evals/scout_explore.map +6 -6
  108. cogames/maps/planky_evals/scout_gear.map +6 -6
  109. cogames/maps/planky_evals/scrambler_full_cycle.map +6 -6
  110. cogames/maps/planky_evals/scrambler_gear.map +6 -6
  111. cogames/maps/planky_evals/scrambler_target.map +6 -6
  112. cogames/maps/planky_evals/stuck_corridor.map +6 -6
  113. cogames/maps/planky_evals/survive_retreat.map +6 -6
  114. cogames/maps/training_facility_clipped.map +2 -2
  115. cogames/maps/training_facility_open_1.map +2 -2
  116. cogames/maps/training_facility_open_2.map +2 -2
  117. cogames/maps/training_facility_open_3.map +2 -2
  118. cogames/maps/training_facility_tight_4.map +2 -2
  119. cogames/maps/training_facility_tight_5.map +2 -2
  120. cogames/maps/vanilla_large.map +2 -2
  121. cogames/maps/vanilla_small.map +2 -2
  122. cogames/pickup.py +6 -5
  123. cogames/play.py +14 -16
  124. cogames/policy/nim_agents/__init__.py +0 -2
  125. cogames/policy/nim_agents/agents.py +0 -11
  126. cogames/policy/starter_agent.py +4 -1
  127. cogames/verbose.py +2 -2
  128. {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/METADATA +45 -29
  129. cogames-0.3.68.dist-info/RECORD +160 -0
  130. metta_alo/scoring.py +7 -7
  131. cogames/cogs_vs_clips/mission_utils.py +0 -19
  132. cogames/cogs_vs_clips/tutorial_missions.py +0 -25
  133. cogames-0.3.64.dist-info/RECORD +0 -159
  134. metta_alo/job_specs.py +0 -17
  135. metta_alo/policy.py +0 -16
  136. metta_alo/pure_single_episode_runner.py +0 -75
  137. metta_alo/rollout.py +0 -322
  138. {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/WHEEL +0 -0
  139. {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/entry_points.txt +0 -0
  140. {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/licenses/LICENSE +0 -0
  141. {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/top_level.txt +0 -0
@@ -1,188 +1,73 @@
1
1
  from __future__ import annotations
2
2
 
3
- from abc import ABC
4
- from typing import TypeVar, override
5
-
6
3
  from pydantic import Field
7
- from typing_extensions import Self
8
4
 
5
+ from cogames.cogs_vs_clips.clips import ClipsConfig
9
6
  from cogames.cogs_vs_clips.cog import CogConfig
7
+ from cogames.cogs_vs_clips.config import CvCConfig
10
8
  from cogames.cogs_vs_clips.stations import (
11
- ELEMENTS,
12
- GEAR,
13
- CogsGuardChestConfig,
9
+ CvCChestConfig,
10
+ CvCExtractorConfig,
11
+ CvCGearStationConfig,
12
+ CvCHubConfig,
13
+ CvCJunctionConfig,
14
14
  CvCWallConfig,
15
- GearStationConfig,
16
- HubConfig,
17
- JunctionConfig,
18
- SimpleExtractorConfig,
19
15
  )
20
- from mettagrid.base_config import Config
16
+ from cogames.cogs_vs_clips.team import CogTeam
17
+ from cogames.cogs_vs_clips.variants import NumCogsVariant
18
+ from cogames.cogs_vs_clips.weather import WeatherConfig
19
+ from cogames.core import (
20
+ MAP_MISSION_DELIMITER,
21
+ CoGameMission,
22
+ CoGameMissionVariant,
23
+ CoGameSite,
24
+ )
21
25
  from mettagrid.config.action_config import (
22
26
  ActionsConfig,
23
27
  ChangeVibeActionConfig,
24
28
  MoveActionConfig,
25
29
  NoopActionConfig,
26
30
  )
27
- from mettagrid.config.event_config import EventConfig, periodic
28
- from mettagrid.config.filter import isAlignedTo, isNear
29
31
  from mettagrid.config.game_value import inv
30
- from mettagrid.config.game_value import stat as game_stat
31
- from mettagrid.config.mettagrid_config import (
32
- CollectiveConfig,
33
- GameConfig,
34
- InventoryConfig,
35
- MettaGridConfig,
36
- ResourceLimitsConfig,
37
- )
38
- from mettagrid.config.mutation import alignTo
32
+ from mettagrid.config.mettagrid_config import CollectiveConfig, GameConfig, MettaGridConfig
39
33
  from mettagrid.config.obs_config import GlobalObsConfig, ObsConfig
40
- from mettagrid.config.reward_config import numObjects, reward
41
- from mettagrid.config.tag import typeTag
42
- from mettagrid.config.vibes import Vibe
43
34
  from mettagrid.map_builder.map_builder import AnyMapBuilderConfig
44
35
 
45
- # Type variable for mission types
46
- TMission = TypeVar("TMission", bound="MissionBase")
47
-
48
-
49
- class MissionVariant(Config, ABC):
50
- # Note: we could derive the name from the class name automatically, but it would make it
51
- # harder to find the variant source code based on CLI interactions.
52
- name: str
53
- description: str = Field(default="")
54
-
55
- def modify_mission(self, mission: MissionBase) -> None:
56
- # Override this method to modify the mission.
57
- # Variants are allowed to modify the mission in-place - it's guaranteed to be a one-time only instance.
58
- pass
59
-
60
- def modify_env(self, mission: MissionBase, env: MettaGridConfig) -> None:
61
- # Override this method to modify the produced environment.
62
- # Variants are allowed to modify the environment in-place.
63
- pass
64
-
65
- def compat(self, mission: MissionBase) -> bool:
66
- """Check if this variant is compatible with the given mission.
67
-
68
- Returns True if the variant can be safely applied to the mission.
69
- Override this method to add compatibility checks.
70
- """
71
- return True
72
-
73
- def apply(self, mission: TMission) -> TMission:
74
- mission = mission.model_copy(deep=True)
75
- mission.variants.append(self)
76
- self.modify_mission(mission)
77
- return mission
78
-
79
- # Temporary helper useful as long as we have one-time variants in missions.py file.
80
- def as_mission(self, name: str, description: str, site: Site) -> Mission:
81
- return Mission(
82
- name=name,
83
- description=description,
84
- site=site,
85
- variants=[self],
86
- )
87
-
88
-
89
- class NumCogsVariant(MissionVariant):
90
- name: str = "num_cogs"
91
- description: str = "Set the number of cogs for the mission."
92
- num_cogs: int
93
-
94
- @override
95
- def modify_mission(self, mission: Mission) -> None:
96
- if self.num_cogs < mission.site.min_cogs or self.num_cogs > mission.site.max_cogs:
97
- raise ValueError(
98
- f"Invalid number of cogs for {mission.site.name}: {self.num_cogs}. "
99
- + f"Must be between {mission.site.min_cogs} and {mission.site.max_cogs}"
100
- )
101
-
102
- mission.num_cogs = self.num_cogs
103
-
104
-
105
- class Site(Config):
106
- name: str
107
- description: str
108
- map_builder: AnyMapBuilderConfig
109
-
110
- min_cogs: int = Field(default=1, ge=1)
111
- max_cogs: int = Field(default=1000, ge=1)
112
-
113
-
114
- MAP_MISSION_DELIMITER = "."
115
-
116
-
117
- class MissionBase(Config, ABC):
118
- """Base class for Mission configurations with common fields and methods."""
119
-
120
- name: str
121
- description: str
122
- site: Site
123
- num_cogs: int | None = None
124
-
125
- # Variants are applied to the mission immediately, and to its env when make_env is called
126
- variants: list[MissionVariant] = Field(default_factory=list)
127
-
128
- max_steps: int = Field(default=10000)
129
-
130
- def __init__(self, **kwargs):
131
- super().__init__(**kwargs)
132
- # Can't call `variant.apply` here because it will create a new mission instance
133
- for variant in self.variants:
134
- variant.modify_mission(self)
135
-
136
- def with_variants(self, variants: list[MissionVariant]) -> Self:
137
- mission = self
138
- for variant in variants:
139
- mission = variant.apply(mission)
140
- return mission
141
-
142
- def full_name(self) -> str:
143
- return f"{self.site.name}{MAP_MISSION_DELIMITER}{self.name}"
144
-
145
-
146
- # CogsGuard vibes
147
- COGSGUARD_VIBES = [
148
- Vibe("😐", "default"),
149
- Vibe("❤️", "heart"),
150
- Vibe("⚙️", "gear"),
151
- Vibe("🌀", "scrambler"),
152
- Vibe("🔗", "aligner"),
153
- Vibe("⛏️", "miner"),
154
- Vibe("🔭", "scout"),
36
+ __all__ = [
37
+ "MAP_MISSION_DELIMITER",
38
+ "CoGameMission",
39
+ "CoGameMissionVariant",
40
+ "CoGameSite",
41
+ "CvCMission",
42
+ "NumCogsVariant",
155
43
  ]
156
44
 
157
45
 
158
- class Mission(MissionBase):
46
+ class CvCMission(CoGameMission):
159
47
  """Mission configuration for CogsGuard game mode."""
160
48
 
161
- # Agent configuration
162
- cog: CogConfig = Field(default_factory=CogConfig)
163
-
164
- wealth: int = Field(default=1)
49
+ max_steps: int = Field(default=10000)
50
+ total_junctions: int = Field(default=118, description="Total junctions on the map (for curriculum scaling)")
165
51
 
166
- # Collective initial resources
167
- collective_initial_carbon: int = Field(default=10)
168
- collective_initial_oxygen: int = Field(default=10)
169
- collective_initial_germanium: int = Field(default=10)
170
- collective_initial_silicon: int = Field(default=10)
171
- collective_initial_heart: int = Field(default=5)
52
+ cog: CogConfig = Field(default_factory=lambda: CogConfig())
53
+ teams: dict[str, CogTeam] = Field(
54
+ default_factory=lambda: {
55
+ "cogs": CogTeam(name="cogs", num_agents=8, wealth=1),
56
+ }
57
+ )
172
58
 
173
- # Clips Behavior - scramble cogs junctions to neutral
174
- # Note: must start after initial_clips fires at timestep 10 (events fire alphabetically)
175
- clips_scramble_start: int = Field(default=50)
176
- clips_scramble_interval: int = Field(default=100)
177
- clips_scramble_radius: int = Field(default=25)
59
+ clips: ClipsConfig = Field(default_factory=lambda: ClipsConfig())
60
+ weather: WeatherConfig = Field(default_factory=lambda: WeatherConfig())
178
61
 
179
- # Clips Behavior - align neutral junctions to clips
180
- clips_align_start: int = Field(default=100)
181
- clips_align_interval: int = Field(default=100)
182
- clips_align_radius: int = Field(default=25)
62
+ @property
63
+ def num_agents(self) -> int:
64
+ if self.num_cogs is not None:
65
+ return self.num_cogs
66
+ return sum(team.num_agents for team in self.teams.values())
183
67
 
184
- # Station configs
185
- wall: CvCWallConfig = Field(default_factory=CvCWallConfig)
68
+ def map_builder(self) -> AnyMapBuilderConfig:
69
+ """Return the map builder config. Override in subclasses for custom map generation."""
70
+ return self.site.map_builder
186
71
 
187
72
  def make_env(self) -> MettaGridConfig:
188
73
  """Create a MettaGridConfig from this mission.
@@ -192,129 +77,57 @@ class Mission(MissionBase):
192
77
  Returns:
193
78
  MettaGridConfig ready for environment creation
194
79
  """
195
- map_builder = self.site.map_builder
196
- num_cogs = self.num_cogs if self.num_cogs is not None else self.site.min_cogs
197
-
198
- gear = GEAR
199
- elements = ELEMENTS
200
- resources_list = ["energy", "heart", "hp", "influence", *elements, *gear]
201
- vibe_names = [vibe.name for vibe in COGSGUARD_VIBES]
202
-
203
- extractor_objects = {
204
- f"{resource}_extractor": SimpleExtractorConfig(resource=resource).station_cfg() for resource in elements
205
- }
206
- gear_objects = {f"{g}_station": GearStationConfig(gear_type=g).station_cfg() for g in gear}
207
-
208
- # Create inventory observations for collective resources
209
- collective_obs = [inv(f"collective.{resource}") for resource in elements]
210
-
80
+ team_objs = list(self.teams.values())
211
81
  game = GameConfig(
212
- map_builder=map_builder,
82
+ map_builder=self.map_builder(),
213
83
  max_steps=self.max_steps,
214
- num_agents=num_cogs,
215
- resource_names=resources_list,
216
- vibe_names=vibe_names,
217
- obs=ObsConfig(global_obs=GlobalObsConfig(obs=collective_obs, local_position=True)),
84
+ num_agents=self.num_agents,
85
+ resource_names=CvCConfig.RESOURCES,
86
+ vibe_names=CvCConfig.VIBE_NAMES,
87
+ obs=ObsConfig(
88
+ global_obs=GlobalObsConfig(
89
+ obs=[inv(f"collective.{resource}") for resource in CvCConfig.ELEMENTS], local_position=True
90
+ )
91
+ ),
218
92
  actions=ActionsConfig(
219
- move=MoveActionConfig(consumed_resources={"energy": self.cog.move_energy_cost}),
93
+ move=MoveActionConfig(consumed_resources=self.cog.action_cost),
220
94
  noop=NoopActionConfig(),
221
- change_vibe=ChangeVibeActionConfig(vibes=COGSGUARD_VIBES),
222
- ),
223
- agent=self.cog.agent_config(gear=gear, elements=elements).model_copy(
224
- update={
225
- "rewards": {
226
- "aligned_junction_held": reward(
227
- game_stat("collective.aligned.junction.held"),
228
- weight=1.0 / self.max_steps,
229
- denoms=[numObjects("junction")],
230
- ),
231
- },
232
- }
95
+ change_vibe=ChangeVibeActionConfig(vibes=CvCConfig.VIBES),
233
96
  ),
97
+ agents=[
98
+ self.cog.agent_config(team=t.name, max_steps=self.max_steps)
99
+ for t in team_objs
100
+ for _ in range(t.num_agents)
101
+ ],
234
102
  objects={
235
- "wall": self.wall.station_cfg(),
236
- "hub": HubConfig(map_name="hub", team="cogs").station_cfg(),
237
- "junction": JunctionConfig(map_name="junction").station_cfg(),
238
- "chest": CogsGuardChestConfig().station_cfg(),
239
- **extractor_objects,
240
- **gear_objects,
103
+ "wall": CvCWallConfig().station_cfg(),
104
+ "junction": CvCJunctionConfig().station_cfg(),
105
+ **{
106
+ f"{resource}_extractor": CvCExtractorConfig(resource=resource).station_cfg()
107
+ for resource in CvCConfig.ELEMENTS
108
+ },
109
+ **{
110
+ f"{t.short_name}:hub": CvCHubConfig().station_cfg(team=t.short_name, collective=t.name)
111
+ for t in team_objs
112
+ },
113
+ **{
114
+ f"{t.short_name}:chest": CvCChestConfig().station_cfg(team=t.short_name, collective=t.name)
115
+ for t in team_objs
116
+ },
117
+ **{
118
+ f"{t.short_name}:{g}": CvCGearStationConfig(gear_type=g).station_cfg(
119
+ team=t.short_name, collective=t.name
120
+ )
121
+ for t in team_objs
122
+ for g in CvCConfig.GEAR
123
+ },
241
124
  },
242
125
  collectives={
243
- "cogs": CollectiveConfig(
244
- inventory=InventoryConfig(
245
- limits={
246
- "resources": ResourceLimitsConfig(min=10000, resources=elements),
247
- "hearts": ResourceLimitsConfig(min=65535, resources=["heart"]),
248
- },
249
- initial={
250
- "carbon": self.collective_initial_carbon * self.wealth,
251
- "oxygen": self.collective_initial_oxygen * self.wealth,
252
- "germanium": self.collective_initial_germanium * self.wealth,
253
- "silicon": self.collective_initial_silicon * self.wealth,
254
- "heart": self.collective_initial_heart * self.wealth,
255
- },
256
- ),
257
- ),
258
- "clips": CollectiveConfig(),
259
- },
260
- events={
261
- "initial_clips": EventConfig(
262
- name="initial_clips",
263
- target_tag=typeTag("junction"),
264
- timesteps=[10],
265
- mutations=[alignTo("clips")],
266
- max_targets=1,
267
- ),
268
- "cogs_to_neutral": EventConfig(
269
- name="cogs_to_neutral",
270
- target_tag=typeTag("junction"),
271
- timesteps=periodic(
272
- start=self.clips_scramble_start,
273
- period=self.clips_scramble_interval,
274
- end_period=self.clips_scramble_interval // 5,
275
- end=self.max_steps,
276
- ),
277
- filters=[
278
- isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.clips_scramble_radius),
279
- isAlignedTo("cogs"),
280
- ],
281
- mutations=[alignTo(None)],
282
- max_targets=1,
283
- ),
284
- "neutral_to_clips": EventConfig(
285
- name="neutral_to_clips",
286
- target_tag=typeTag("junction"),
287
- timesteps=periodic(
288
- start=self.clips_align_start,
289
- period=self.clips_align_interval,
290
- end_period=self.clips_align_interval // 5,
291
- end=self.max_steps,
292
- ),
293
- filters=[
294
- isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.clips_align_radius),
295
- isAlignedTo(None),
296
- ],
297
- mutations=[alignTo("clips")],
298
- max_targets=1,
299
- fallback="cogs_to_neutral",
300
- ),
301
- # If the Clips can't find any junctions near them, align a random junction
302
- "presence_check": EventConfig(
303
- name="presence_check",
304
- target_tag=typeTag("junction"),
305
- timesteps=periodic(
306
- start=self.clips_scramble_start,
307
- period=self.clips_scramble_interval,
308
- end=self.max_steps,
309
- ),
310
- filters=[
311
- isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.clips_scramble_radius),
312
- ],
313
- mutations=[],
314
- max_targets=1,
315
- fallback="initial_clips",
316
- ),
126
+ **{t.name: t.collective_config() for t in team_objs},
127
+ **self.clips.collectives(),
128
+ "neutral": CollectiveConfig(name="neutral"),
317
129
  },
130
+ events=self._merge_events(),
318
131
  )
319
132
 
320
133
  env = MettaGridConfig(game=game)
@@ -329,8 +142,11 @@ class Mission(MissionBase):
329
142
 
330
143
  return env
331
144
 
332
-
333
- # Backwards compatibility alias
334
- CogsGuardMission = Mission
335
-
336
- AnyMission = Mission
145
+ def _merge_events(self) -> dict:
146
+ """Merge clips and weather events, raising on key conflicts."""
147
+ clips_events = self.clips.events(max_steps=self.max_steps)
148
+ weather_events = self.weather.events(max_steps=self.max_steps)
149
+ overlap = set(clips_events) & set(weather_events)
150
+ if overlap:
151
+ raise ValueError(f"Overlapping event keys between clips and weather: {overlap}")
152
+ return {**clips_events, **weather_events}
@@ -1,6 +1,6 @@
1
1
  from functools import lru_cache
2
2
 
3
- from cogames.cogs_vs_clips.mission import AnyMission, Mission
3
+ from cogames.cogs_vs_clips.mission import CvCMission
4
4
  from cogames.cogs_vs_clips.sites import (
5
5
  COGSGUARD_ARENA,
6
6
  COGSGUARD_MACHINA_1,
@@ -11,9 +11,9 @@ from mettagrid.config.mettagrid_config import MettaGridConfig
11
11
  # CogsGuard Missions
12
12
 
13
13
 
14
- def make_cogsguard_mission(num_agents: int = 10, max_steps: int = 10000) -> Mission:
14
+ def make_cogsguard_mission(num_agents: int = 10, max_steps: int = 10000) -> CvCMission:
15
15
  """Create a CogsGuard mission with configurable parameters (Machina1 layout)."""
16
- return Mission(
16
+ return CvCMission(
17
17
  name="basic",
18
18
  description="Basic CogsGuard mission (Machina1 layout)",
19
19
  site=make_cogsguard_machina1_site(num_agents),
@@ -22,7 +22,7 @@ def make_cogsguard_mission(num_agents: int = 10, max_steps: int = 10000) -> Miss
22
22
  )
23
23
 
24
24
 
25
- CogsGuardMachina1Mission = Mission(
25
+ CogsGuardMachina1Mission = CvCMission(
26
26
  name="basic",
27
27
  description="CogsGuard Machina1 - compete to control junctions with gear abilities.",
28
28
  site=COGSGUARD_MACHINA_1,
@@ -30,7 +30,7 @@ CogsGuardMachina1Mission = Mission(
30
30
  max_steps=10000,
31
31
  )
32
32
 
33
- CogsGuardBasicMission = Mission(
33
+ CogsGuardBasicMission = CvCMission(
34
34
  name="basic",
35
35
  description="CogsGuard Arena - compact training map with gear abilities.",
36
36
  site=COGSGUARD_ARENA,
@@ -39,48 +39,38 @@ CogsGuardBasicMission = Mission(
39
39
  )
40
40
 
41
41
 
42
- _CORE_MISSIONS: list[AnyMission] = [
42
+ _CORE_MISSIONS: list[CvCMission] = [
43
43
  CogsGuardMachina1Mission,
44
44
  CogsGuardBasicMission,
45
45
  ]
46
46
 
47
47
 
48
- def get_core_missions() -> list[AnyMission]:
48
+ def get_core_missions() -> list[CvCMission]:
49
49
  return list(_CORE_MISSIONS)
50
50
 
51
51
 
52
- def get_legacy_missions() -> list[Mission]:
53
- """Get legacy (pre-CogsGuard) missions for backward compatibility.
54
-
55
- Legacy missions have been removed. Returns empty list.
56
- """
57
- return []
58
-
59
-
60
- def _build_eval_missions() -> list[AnyMission]:
61
- from cogames.cogs_vs_clips.evals.cogsguard_evals import COGSGUARD_EVAL_MISSIONS
62
- from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS
52
+ def _build_eval_missions() -> list[CvCMission]:
53
+ from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS # noqa: PLC0415
63
54
 
64
55
  return [
65
- *COGSGUARD_EVAL_MISSIONS,
66
56
  *INTEGRATED_EVAL_MISSIONS,
67
57
  ]
68
58
 
69
59
 
70
60
  @lru_cache(maxsize=1)
71
- def get_missions() -> list[AnyMission]:
61
+ def get_missions() -> list[CvCMission]:
72
62
  return [*_CORE_MISSIONS, *_build_eval_missions()]
73
63
 
74
64
 
75
- def __getattr__(name: str) -> list[AnyMission]:
65
+ def make_game(num_cogs: int = 2, map_name: str = "training_facility_open_1.map") -> MettaGridConfig:
66
+ """Create a default CogsGuard game configuration."""
67
+ mission = make_cogsguard_mission(num_agents=num_cogs)
68
+ return mission.make_env()
69
+
70
+
71
+ def __getattr__(name: str) -> list[CvCMission]:
76
72
  if name == "MISSIONS":
77
73
  missions = get_missions()
78
74
  globals()["MISSIONS"] = missions
79
75
  return missions
80
76
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
81
-
82
-
83
- def make_game(num_cogs: int = 2, map_name: str = "training_facility_open_1.map") -> MettaGridConfig:
84
- """Create a default CogsGuard game configuration."""
85
- mission = make_cogsguard_mission(num_agents=num_cogs)
86
- return mission.make_env()
@@ -7,13 +7,20 @@ mission's default objective rewards.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import json
10
11
  from typing import Literal, Sequence, cast
11
12
 
12
13
  from mettagrid.config.game_value import stat
13
14
  from mettagrid.config.mettagrid_config import MettaGridConfig
14
15
  from mettagrid.config.reward_config import AgentReward, reward
15
16
 
16
- CogsGuardRewardVariant = Literal["credit", "milestones", "no_objective", "penalize_vibe_change", "objective"]
17
+ CogsGuardRewardVariant = Literal[
18
+ "credit",
19
+ "milestones",
20
+ "no_objective",
21
+ "penalize_vibe_change",
22
+ "objective",
23
+ ]
17
24
 
18
25
  AVAILABLE_REWARD_VARIANTS: tuple[CogsGuardRewardVariant, ...] = (
19
26
  "objective",
@@ -82,7 +89,9 @@ def _apply_credit(rewards: dict[str, AgentReward]) -> None:
82
89
  gain_rewards: dict[str, AgentReward] = {
83
90
  "heart_gained": reward(stat("heart.gained"), weight=w_heart, max=cap_heart),
84
91
  "aligner_gained": reward(stat("aligner.gained"), weight=w_align_gear, max=cap_align_gear),
92
+ "aligner_lost": reward(stat("aligner.lost"), weight=-w_align_gear, max=-cap_align_gear),
85
93
  "scrambler_gained": reward(stat("scrambler.gained"), weight=w_scramble_gear, max=cap_scramble_gear),
94
+ "scrambler_lost": reward(stat("scrambler.lost"), weight=-w_scramble_gear, max=-cap_scramble_gear),
86
95
  "carbon_gained": reward(stat("carbon.gained"), weight=w_element_gain, max=cap_element_gain),
87
96
  "oxygen_gained": reward(stat("oxygen.gained"), weight=w_element_gain, max=cap_element_gain),
88
97
  "germanium_gained": reward(stat("germanium.gained"), weight=w_element_gain, max=cap_element_gain),
@@ -115,7 +124,18 @@ def apply_reward_variants(env: MettaGridConfig, *, variants: str | Sequence[str]
115
124
  if not variants:
116
125
  return
117
126
 
118
- variant_names = [variants] if isinstance(variants, str) else list(variants)
127
+ # Parse JSON-encoded list strings (e.g., '["milestones"]' from sweeps)
128
+ if isinstance(variants, str):
129
+ if variants.startswith("["):
130
+ try:
131
+ parsed = json.loads(variants)
132
+ variant_names = list(parsed) if isinstance(parsed, list) else [variants]
133
+ except json.JSONDecodeError:
134
+ variant_names = [variants]
135
+ else:
136
+ variant_names = [variants]
137
+ else:
138
+ variant_names = list(variants)
119
139
 
120
140
  reward_variants: list[CogsGuardRewardVariant] = []
121
141
  for variant_name in variant_names: