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
@@ -1,188 +1,70 @@
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.core import (
19
+ MAP_MISSION_DELIMITER,
20
+ CoGameMission,
21
+ CoGameMissionVariant,
22
+ CoGameSite,
23
+ )
21
24
  from mettagrid.config.action_config import (
22
25
  ActionsConfig,
23
26
  ChangeVibeActionConfig,
24
27
  MoveActionConfig,
25
28
  NoopActionConfig,
26
29
  )
27
- from mettagrid.config.event_config import EventConfig, periodic
28
- from mettagrid.config.filter import isAlignedTo, isNear
29
30
  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
31
+ from mettagrid.config.mettagrid_config import GameConfig, MettaGridConfig
39
32
  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
33
  from mettagrid.map_builder.map_builder import AnyMapBuilderConfig
44
34
 
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"),
35
+ __all__ = [
36
+ "MAP_MISSION_DELIMITER",
37
+ "CoGameMission",
38
+ "CoGameMissionVariant",
39
+ "CoGameSite",
40
+ "CvCMission",
41
+ "NumCogsVariant",
155
42
  ]
156
43
 
157
44
 
158
- class Mission(MissionBase):
45
+ class CvCMission(CoGameMission):
159
46
  """Mission configuration for CogsGuard game mode."""
160
47
 
161
- # Agent configuration
162
- cog: CogConfig = Field(default_factory=CogConfig)
163
-
164
- wealth: int = Field(default=1)
48
+ max_steps: int = Field(default=10000)
165
49
 
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)
50
+ cog: CogConfig = Field(default_factory=lambda: CogConfig())
51
+ teams: dict[str, CogTeam] = Field(
52
+ default_factory=lambda: {
53
+ "cogs": CogTeam(name="cogs", num_agents=8, wealth=1),
54
+ }
55
+ )
172
56
 
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)
57
+ clips: ClipsConfig = Field(default_factory=lambda: ClipsConfig())
178
58
 
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)
59
+ @property
60
+ def num_agents(self) -> int:
61
+ if self.num_cogs is not None:
62
+ return self.num_cogs
63
+ return sum(team.num_agents for team in self.teams.values())
183
64
 
184
- # Station configs
185
- wall: CvCWallConfig = Field(default_factory=CvCWallConfig)
65
+ def map_builder(self) -> AnyMapBuilderConfig:
66
+ """Return the map builder config. Override in subclasses for custom map generation."""
67
+ return self.site.map_builder
186
68
 
187
69
  def make_env(self) -> MettaGridConfig:
188
70
  """Create a MettaGridConfig from this mission.
@@ -192,129 +74,41 @@ class Mission(MissionBase):
192
74
  Returns:
193
75
  MettaGridConfig ready for environment creation
194
76
  """
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
77
 
211
78
  game = GameConfig(
212
- map_builder=map_builder,
79
+ map_builder=self.map_builder(),
213
80
  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)),
81
+ num_agents=self.num_agents,
82
+ resource_names=CvCConfig.RESOURCES,
83
+ vibe_names=CvCConfig.VIBE_NAMES,
84
+ obs=ObsConfig(
85
+ global_obs=GlobalObsConfig(
86
+ obs=[inv(f"collective.{resource}") for resource in CvCConfig.ELEMENTS], local_position=True
87
+ )
88
+ ),
218
89
  actions=ActionsConfig(
219
- move=MoveActionConfig(consumed_resources={"energy": self.cog.move_energy_cost}),
90
+ move=MoveActionConfig(consumed_resources=self.cog.action_cost),
220
91
  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
- }
92
+ change_vibe=ChangeVibeActionConfig(vibes=CvCConfig.VIBES),
233
93
  ),
94
+ agent=self.cog.agent_config(team="cogs", max_steps=self.max_steps),
95
+ agents=[self.cog.agent_config(team="cogs", max_steps=self.max_steps) for _ in range(self.num_agents)],
234
96
  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,
97
+ "wall": CvCWallConfig().station_cfg(),
98
+ "hub": CvCHubConfig().station_cfg(team="cogs"),
99
+ "junction": CvCJunctionConfig().station_cfg(),
100
+ "chest": CvCChestConfig().station_cfg(team="cogs"),
101
+ **{
102
+ f"{resource}_extractor": CvCExtractorConfig(resource=resource).station_cfg()
103
+ for resource in CvCConfig.ELEMENTS
104
+ },
105
+ **{f"{g}_station": CvCGearStationConfig(gear_type=g).station_cfg(team="cogs") for g in CvCConfig.GEAR},
241
106
  },
242
107
  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
- ),
108
+ **{team.name: team.collective_config() for team in self.teams.values()},
109
+ "clips": self.clips.collective_config(),
317
110
  },
111
+ events=self.clips.events(max_steps=self.max_steps),
318
112
  )
319
113
 
320
114
  env = MettaGridConfig(game=game)
@@ -328,9 +122,3 @@ class Mission(MissionBase):
328
122
  env.label += f".{variant.name}"
329
123
 
330
124
  return env
331
-
332
-
333
- # Backwards compatibility alias
334
- CogsGuardMission = Mission
335
-
336
- AnyMission = Mission
@@ -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
52
+ def _build_eval_missions() -> list[CvCMission]:
62
53
  from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS
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()
@@ -1,14 +1,34 @@
1
1
  """Central site definitions shared across mission modules."""
2
2
 
3
+ from pathlib import Path
3
4
  from typing import cast
4
5
 
5
- from cogames.cogs_vs_clips.mission import Site
6
- from cogames.cogs_vs_clips.mission_utils import get_map
7
- from cogames.cogs_vs_clips.procedural import MachinaArena, RandomTransform, SequentialMachinaArena
6
+ from cogames.cogs_vs_clips.terrain import MachinaArena, RandomTransform, SequentialMachinaArena
7
+ from cogames.core import CoGameSite
8
+ from mettagrid.map_builder.map_builder import MapBuilderConfig
8
9
  from mettagrid.mapgen.mapgen import MapGen, MapGenConfig
9
10
  from mettagrid.mapgen.scenes.base_hub import BaseHub, BaseHubConfig
10
11
 
11
- TRAINING_FACILITY = Site(
12
+ MAPS_DIR = Path(__file__).resolve().parent.parent / "maps"
13
+
14
+
15
+ def get_map(map_name: str) -> MapBuilderConfig:
16
+ """Load a map builder configuration from the maps directory."""
17
+ normalized = map_name
18
+ if normalized.startswith("evals/"):
19
+ normalized = f"diagnostic_evals/{normalized.split('/', 1)[1]}"
20
+ map_path = MAPS_DIR / normalized
21
+ if not map_path.exists():
22
+ raise FileNotFoundError(f"Map not found: {map_path}")
23
+ return MapGen.Config(
24
+ instance=MapBuilderConfig.from_uri(str(map_path)),
25
+ instances=1, # Force single instance - use spawn points from ASCII map directly
26
+ fixed_spawn_order=False,
27
+ instance_border_width=0, # Don't add border - maps already have borders built in
28
+ )
29
+
30
+
31
+ TRAINING_FACILITY = CoGameSite(
12
32
  name="training_facility",
13
33
  description="COG Training Facility. Basic training facility with open spaces and no obstacles.",
14
34
  map_builder=MapGen.Config(
@@ -25,6 +45,7 @@ TRAINING_FACILITY = Site(
25
45
  "silicon_extractor",
26
46
  ],
27
47
  cross_bundle="none",
48
+ junction_object="junction",
28
49
  )
29
50
  ),
30
51
  ),
@@ -32,7 +53,7 @@ TRAINING_FACILITY = Site(
32
53
  max_cogs=4,
33
54
  )
34
55
 
35
- HELLO_WORLD = Site(
56
+ HELLO_WORLD = CoGameSite(
36
57
  name="hello_world",
37
58
  description="Welcome to space.",
38
59
  map_builder=MapGen.Config(width=100, height=100, instance=MachinaArena.Config(spawn_count=20)),
@@ -40,7 +61,7 @@ HELLO_WORLD = Site(
40
61
  max_cogs=20,
41
62
  )
42
63
 
43
- MACHINA_1 = Site(
64
+ MACHINA_1 = CoGameSite(
44
65
  name="machina_1",
45
66
  description="Your first mission. Collect resources and assemble HEARTs.",
46
67
  map_builder=MapGen.Config(width=88, height=88, instance=SequentialMachinaArena.Config(spawn_count=20)),
@@ -54,6 +75,7 @@ def _cogsguard_hub_config() -> BaseHubConfig:
54
75
  corner_bundle="extractors",
55
76
  cross_bundle="none",
56
77
  cross_distance=7,
78
+ junction_object="junction",
57
79
  stations=[
58
80
  "aligner_station",
59
81
  "scrambler_station",
@@ -66,7 +88,7 @@ def _cogsguard_hub_config() -> BaseHubConfig:
66
88
 
67
89
  # Evals site used by diagnostic evaluation missions
68
90
  # Note: Individual diagnostic missions override this with their own specific maps
69
- EVALS = Site(
91
+ EVALS = CoGameSite(
70
92
  name="evals",
71
93
  description="Diagnostic evaluation arenas.",
72
94
  map_builder=get_map("diagnostic_evals/diagnostic_radial.map"), # Default map (rarely used)
@@ -75,7 +97,7 @@ EVALS = Site(
75
97
  )
76
98
 
77
99
 
78
- def make_cogsguard_arena_site(num_agents: int = 10) -> Site:
100
+ def make_cogsguard_arena_site(num_agents: int = 10) -> CoGameSite:
79
101
  """Create a CogsGuard arena site with configurable agent count."""
80
102
  map_builder = MapGen.Config(
81
103
  width=50,
@@ -86,7 +108,7 @@ def make_cogsguard_arena_site(num_agents: int = 10) -> Site:
86
108
  hub=_cogsguard_hub_config(),
87
109
  ),
88
110
  )
89
- return Site(
111
+ return CoGameSite(
90
112
  name="cogsguard_arena",
91
113
  description="CogsGuard arena map",
92
114
  map_builder=map_builder,
@@ -111,9 +133,9 @@ def _build_cogsguard_machina1_map_builder(spawn_count: int) -> MapGenConfig:
111
133
  )
112
134
 
113
135
 
114
- def make_cogsguard_machina1_site(num_agents: int = 10) -> Site:
136
+ def make_cogsguard_machina1_site(num_agents: int = 10) -> CoGameSite:
115
137
  """Create a CogsGuard Machina1 site with configurable agent count."""
116
- return Site(
138
+ return CoGameSite(
117
139
  name="cogsguard_machina_1",
118
140
  description="CogsGuard Machina1 layout with gear stations.",
119
141
  map_builder=_build_cogsguard_machina1_map_builder(num_agents),
@@ -123,7 +145,7 @@ def make_cogsguard_machina1_site(num_agents: int = 10) -> Site:
123
145
 
124
146
 
125
147
  # Default CogsGuard Machina1 site with flexible agent count
126
- COGSGUARD_MACHINA_1 = Site(
148
+ COGSGUARD_MACHINA_1 = CoGameSite(
127
149
  name="cogsguard_machina_1",
128
150
  description="CogsGuard Machina1 layout with gear stations.",
129
151
  map_builder=_build_cogsguard_machina1_map_builder(20),
@@ -132,7 +154,7 @@ COGSGUARD_MACHINA_1 = Site(
132
154
  )
133
155
 
134
156
  # Default CogsGuard arena site with flexible agent count
135
- COGSGUARD_ARENA = Site(
157
+ COGSGUARD_ARENA = CoGameSite(
136
158
  name="cogsguard_arena",
137
159
  description="CogsGuard arena - compact training map with gear stations.",
138
160
  map_builder=MapGen.Config(
@@ -149,20 +171,8 @@ COGSGUARD_ARENA = Site(
149
171
  )
150
172
 
151
173
 
152
- # Feature flag: Set to True to include legacy (pre-CogsGuard) sites in the CLI.
153
- # To enable, add TRAINING_FACILITY, HELLO_WORLD, MACHINA_1 to SITES below.
154
- # Also set _INCLUDE_LEGACY_MISSIONS = True in missions.py.
155
- _INCLUDE_LEGACY_SITES = False
156
-
157
- _LEGACY_SITES = [
158
- TRAINING_FACILITY,
159
- HELLO_WORLD,
160
- MACHINA_1,
161
- ]
162
-
163
174
  SITES = [
164
175
  COGSGUARD_MACHINA_1,
165
176
  COGSGUARD_ARENA,
166
177
  EVALS,
167
- *(_LEGACY_SITES if _INCLUDE_LEGACY_SITES else []),
168
178
  ]