cogames 0.3.59.post1.dev2__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 (49) hide show
  1. cogames/cli/client.py +12 -0
  2. cogames/cli/leaderboard.py +40 -0
  3. cogames/cli/mission.py +31 -34
  4. cogames/cli/submit.py +1 -1
  5. cogames/cogs_vs_clips/clips.py +86 -0
  6. cogames/cogs_vs_clips/cog.py +14 -7
  7. cogames/cogs_vs_clips/cogsguard_tutorial.py +10 -11
  8. cogames/cogs_vs_clips/config.py +38 -0
  9. cogames/cogs_vs_clips/{cogs_vs_clips_mapgen.md → docs/cogs_vs_clips_mapgen.md} +6 -7
  10. cogames/cogs_vs_clips/evals/README.md +4 -4
  11. cogames/cogs_vs_clips/evals/cogsguard_evals.py +96 -0
  12. cogames/cogs_vs_clips/evals/diagnostic_evals.py +13 -100
  13. cogames/cogs_vs_clips/evals/difficulty_variants.py +9 -18
  14. cogames/cogs_vs_clips/evals/integrated_evals.py +8 -60
  15. cogames/cogs_vs_clips/evals/spanning_evals.py +48 -54
  16. cogames/cogs_vs_clips/mission.py +65 -277
  17. cogames/cogs_vs_clips/missions.py +16 -24
  18. cogames/cogs_vs_clips/sites.py +35 -25
  19. cogames/cogs_vs_clips/stations.py +33 -82
  20. cogames/cogs_vs_clips/team.py +44 -0
  21. cogames/cogs_vs_clips/{procedural.py → terrain.py} +12 -6
  22. cogames/cogs_vs_clips/variants.py +41 -118
  23. cogames/core.py +87 -0
  24. cogames/main.py +5 -1
  25. cogames/maps/evals/eval_balanced_spread.map +7 -3
  26. cogames/maps/evals/eval_clip_oxygen.map +7 -3
  27. cogames/maps/evals/eval_collect_resources.map +7 -3
  28. cogames/maps/evals/eval_collect_resources_hard.map +7 -3
  29. cogames/maps/evals/eval_collect_resources_medium.map +7 -3
  30. cogames/maps/evals/eval_divide_and_conquer.map +7 -3
  31. cogames/maps/evals/eval_energy_starved.map +7 -3
  32. cogames/maps/evals/eval_multi_coordinated_collect_hard.map +7 -3
  33. cogames/maps/evals/eval_oxygen_bottleneck.map +7 -3
  34. cogames/maps/evals/eval_single_use_world.map +7 -3
  35. cogames/maps/evals/extractor_hub_100x100.map +7 -3
  36. cogames/maps/evals/extractor_hub_30x30.map +7 -3
  37. cogames/maps/evals/extractor_hub_50x50.map +7 -3
  38. cogames/maps/evals/extractor_hub_70x70.map +7 -3
  39. cogames/maps/evals/extractor_hub_80x80.map +7 -3
  40. cogames/verbose.py +2 -2
  41. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/METADATA +19 -3
  42. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/RECORD +46 -44
  43. cogames/cogs_vs_clips/cogsguard_reward_variants.py +0 -138
  44. cogames/cogs_vs_clips/mission_utils.py +0 -19
  45. cogames/cogs_vs_clips/tutorial_missions.py +0 -25
  46. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/WHEEL +0 -0
  47. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/entry_points.txt +0 -0
  48. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/licenses/LICENSE +0 -0
  49. {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from cogames.cogs_vs_clips.mission import CvCMission
6
+ from cogames.core import CoGameSite as Site
7
+ from mettagrid.map_builder.map_builder import MapBuilderConfig
8
+ from mettagrid.mapgen.mapgen import MapGen, MapGenConfig
9
+
10
+ MAPS_DIR = Path(__file__).resolve().parent.parent.parent / "maps"
11
+
12
+
13
+ def _load_map(map_name: str) -> MapGenConfig:
14
+ map_path = MAPS_DIR / map_name
15
+ if not map_path.exists():
16
+ raise FileNotFoundError(f"Map not found: {map_path}")
17
+ return MapGen.Config(
18
+ instance=MapBuilderConfig.from_uri(str(map_path)),
19
+ instances=1,
20
+ fixed_spawn_order=False,
21
+ instance_border_width=0,
22
+ )
23
+
24
+
25
+ COGSGUARD_EVALS_BASE = Site(
26
+ name="cogsguard_evals",
27
+ description="CogsGuard evaluation arenas.",
28
+ map_builder=_load_map("evals/eval_balanced_spread.map"),
29
+ min_cogs=1,
30
+ max_cogs=20,
31
+ )
32
+
33
+
34
+ def _count_spawn_pads(map_path: Path) -> int:
35
+ text = map_path.read_text()
36
+ if "map_data:" not in text:
37
+ raise ValueError(f"Missing map_data block in {map_path}")
38
+ map_section = text.split("map_data:", 1)[1].split("char_to_map_name:", 1)[0]
39
+ count = map_section.count("@")
40
+ if count <= 0:
41
+ raise ValueError(f"No spawn pads found in {map_path}")
42
+ return count
43
+
44
+
45
+ def _make_eval_site(map_name: str, num_cogs: int) -> Site:
46
+ site = COGSGUARD_EVALS_BASE.model_copy(
47
+ update={
48
+ "map_builder": _load_map(map_name),
49
+ "min_cogs": num_cogs,
50
+ "max_cogs": num_cogs,
51
+ }
52
+ )
53
+ return site
54
+
55
+
56
+ def _description_from_stem(stem: str) -> str:
57
+ display = stem
58
+ if display.startswith("eval_"):
59
+ display = display[len("eval_") :]
60
+ display = display.replace("_", " ")
61
+ return f"CogsGuard eval: {display}."
62
+
63
+
64
+ COGSGUARD_EVAL_MAPS: list[str] = [
65
+ "evals/eval_balanced_spread.map",
66
+ "evals/eval_clip_oxygen.map",
67
+ "evals/eval_collect_resources.map",
68
+ "evals/eval_collect_resources_medium.map",
69
+ "evals/eval_collect_resources_hard.map",
70
+ "evals/eval_divide_and_conquer.map",
71
+ "evals/eval_energy_starved.map",
72
+ "evals/eval_multi_coordinated_collect_hard.map",
73
+ "evals/eval_oxygen_bottleneck.map",
74
+ "evals/eval_single_use_world.map",
75
+ "evals/extractor_hub_30x30.map",
76
+ "evals/extractor_hub_50x50.map",
77
+ "evals/extractor_hub_70x70.map",
78
+ "evals/extractor_hub_80x80.map",
79
+ "evals/extractor_hub_100x100.map",
80
+ ]
81
+
82
+ COGSGUARD_EVAL_COGS = {map_name: _count_spawn_pads(MAPS_DIR / map_name) for map_name in COGSGUARD_EVAL_MAPS}
83
+
84
+ COGSGUARD_EVAL_MISSIONS: list[CvCMission] = []
85
+ for map_name in COGSGUARD_EVAL_MAPS:
86
+ stem = Path(map_name).stem
87
+ num_cogs = COGSGUARD_EVAL_COGS[map_name]
88
+ site = _make_eval_site(map_name, num_cogs)
89
+ COGSGUARD_EVAL_MISSIONS.append(
90
+ CvCMission(
91
+ name=stem,
92
+ description=_description_from_stem(stem),
93
+ site=site,
94
+ num_cogs=num_cogs,
95
+ )
96
+ )
@@ -1,54 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pathlib import Path
4
3
  from typing import Dict
5
4
 
6
5
  from pydantic import Field
7
6
 
8
7
  from cogames.cogs_vs_clips.cog import CogConfig
9
- from cogames.cogs_vs_clips.mission import Mission, Site
10
- from mettagrid.config.game_value import stat
8
+ from cogames.cogs_vs_clips.mission import CvCMission
9
+ from cogames.cogs_vs_clips.sites import EVALS, get_map
10
+ from cogames.core import CoGameSite
11
11
  from mettagrid.config.handler_config import Handler
12
12
  from mettagrid.config.mettagrid_config import (
13
- ChestConfig,
14
13
  MettaGridConfig,
15
- ResourceLimitsConfig,
16
14
  )
17
15
  from mettagrid.config.mutation.resource_mutation import updateActor
18
- from mettagrid.config.reward_config import reward
19
- from mettagrid.map_builder.map_builder import MapBuilderConfig
20
- from mettagrid.mapgen.mapgen import MapGen
21
16
 
22
17
  RESOURCE_NAMES: tuple[str, ...] = ("carbon", "oxygen", "germanium", "silicon")
23
18
 
24
- MAPS_DIR = Path(__file__).resolve().parent.parent.parent / "maps"
25
-
26
-
27
- def get_map(map_name: str) -> MapBuilderConfig:
28
- """Load a map builder configuration from the local diagnostics directory."""
29
- normalized = map_name
30
- if normalized.startswith("evals/"):
31
- normalized = f"diagnostic_evals/{normalized.split('/', 1)[1]}"
32
- map_path = MAPS_DIR / normalized
33
- if not map_path.exists():
34
- raise FileNotFoundError(f"Diagnostic map not found: {map_path}")
35
- # Wrap AsciiMapBuilderConfig in MapGen.Config to match standard get_map() behavior
36
- return MapGen.Config(
37
- instance=MapBuilderConfig.from_uri(str(map_path)),
38
- instances=1, # Force single instance - use spawn points from ASCII map directly
39
- fixed_spawn_order=False,
40
- instance_border_width=0, # Don't add border - maps already have borders built in
41
- )
42
-
43
-
44
- EVALS = Site(
45
- name="evals",
46
- description="Diagnostic evaluation arenas.",
47
- map_builder=get_map("evals/diagnostic_radial.map"),
48
- min_cogs=1,
49
- max_cogs=4,
50
- )
51
-
52
19
 
53
20
  # Generous cog config for diagnostic missions: high limits and full energy regen
54
21
  _GENEROUS_COG = CogConfig(
@@ -64,25 +31,11 @@ _GENEROUS_COG = CogConfig(
64
31
  influence_regen=0,
65
32
  )
66
33
 
67
- # Same but without generous energy regen (for charge-up diagnostics)
68
- _MODEST_COG = CogConfig(
69
- gear_limit=255,
70
- hp_limit=255,
71
- heart_limit=255,
72
- energy_limit=255,
73
- cargo_limit=255,
74
- initial_energy=255,
75
- initial_hp=100,
76
- energy_regen=1,
77
- hp_regen=0,
78
- influence_regen=0,
79
- )
80
-
81
34
 
82
- class _DiagnosticMissionBase(Mission):
35
+ class _DiagnosticMissionBase(CvCMission):
83
36
  """Base class for minimal diagnostic evaluation missions."""
84
37
 
85
- site: Site = EVALS
38
+ site: CoGameSite = EVALS
86
39
  cog: CogConfig = Field(default_factory=lambda: _GENEROUS_COG.model_copy())
87
40
 
88
41
  map_name: str = Field(default="evals/diagnostic_eval_template.map")
@@ -90,15 +43,9 @@ class _DiagnosticMissionBase(Mission):
90
43
  required_agents: int | None = Field(default=None)
91
44
 
92
45
  inventory_seed: Dict[str, int] = Field(default_factory=dict)
93
- communal_chest_hearts: int | None = Field(default=None)
94
- resource_chest_stock: Dict[str, int] = Field(default_factory=dict)
95
46
  # If True, give agents high energy capacity and regen (overridden by specific missions)
96
47
  generous_energy: bool = Field(default=True)
97
48
 
98
- # Disable clips events for diagnostic evals
99
- clips_scramble_start: int = Field(default=99999)
100
- clips_align_start: int = Field(default=99999)
101
-
102
49
  def configure_env(self, cfg: MettaGridConfig) -> None: # pragma: no cover - hook for subclasses
103
50
  """Hook for mission-specific environment alterations."""
104
51
 
@@ -114,10 +61,6 @@ class _DiagnosticMissionBase(Mission):
114
61
  cfg.game.map_builder = forced_map
115
62
  cfg.game.max_steps = self.max_steps
116
63
  self._apply_inventory_seed(cfg)
117
- self._apply_communal_chest(cfg)
118
- self._apply_resource_chests(cfg)
119
- # Finally, normalize rewards so a single deposited heart yields at most 1 reward.
120
- self._apply_heart_reward_cap(cfg)
121
64
  self.configure_env(cfg)
122
65
  return cfg
123
66
  finally:
@@ -134,45 +77,15 @@ class _DiagnosticMissionBase(Mission):
134
77
  seed = dict(cfg.game.agent.inventory.initial)
135
78
  seed.update(self.inventory_seed)
136
79
  cfg.game.agent.inventory.initial = seed
137
-
138
- def _apply_communal_chest(self, cfg: MettaGridConfig) -> None:
139
- if self.communal_chest_hearts is None:
140
- return
141
- chest = cfg.game.objects.get("communal_chest")
142
- if isinstance(chest, ChestConfig):
143
- chest.inventory.initial = {"heart": self.communal_chest_hearts}
144
-
145
- def _apply_resource_chests(self, cfg: MettaGridConfig) -> None:
146
- if not self.resource_chest_stock:
147
- return
148
- for resource, amount in self.resource_chest_stock.items():
149
- chest_cfg = cfg.game.objects.get(f"chest_{resource}")
150
- if isinstance(chest_cfg, ChestConfig):
151
- chest_cfg.inventory.initial = {resource: amount}
152
-
153
- def _apply_heart_reward_cap(self, cfg: MettaGridConfig) -> None:
154
- """Normalize diagnostics so a single deposited heart yields at most 1 reward per episode.
155
-
156
- - Make each agent-deposited heart worth exactly 1.0 reward (credited only to the depositor).
157
- - Ensure all chests can store at most 1 heart so total reward per episode cannot exceed 1.
158
- """
159
- agent_cfg = cfg.game.agent
160
- rewards = dict(agent_cfg.rewards)
161
- rewards["chest_heart_deposited_by_agent"] = reward(stat("chest.heart.deposited_by_agent"))
162
- agent_cfg.rewards = rewards
163
-
164
- # Cap heart capacity for every chest used in diagnostics (communal or resource-specific).
165
- for _name, obj in cfg.game.objects.items():
166
- if not isinstance(obj, ChestConfig):
167
- continue
168
- # Find existing heart limit or create new one
169
- heart_limit = obj.inventory.limits.get("heart", ResourceLimitsConfig(min=1, resources=["heart"]))
170
- heart_limit.min = 1
171
- obj.inventory.limits["heart"] = heart_limit
80
+ # Also apply to per-agent configs (used by CvCMission)
81
+ for agent_cfg in cfg.game.agents:
82
+ agent_seed = dict(agent_cfg.inventory.initial)
83
+ agent_seed.update(self.inventory_seed)
84
+ agent_cfg.inventory.initial = agent_seed
172
85
 
173
86
 
174
87
  # ----------------------------------------------------------------------
175
- # Diagnostics (non-hub)
88
+ # Diagnostic missions (no assemblers)
176
89
  # ----------------------------------------------------------------------
177
90
 
178
91
 
@@ -234,7 +147,7 @@ class DiagnosticChargeUp(_DiagnosticMissionBase):
234
147
  max_steps: int = Field(default=250)
235
148
 
236
149
  def configure_env(self, cfg: MettaGridConfig) -> None:
237
- # Set starting energy to 30 and no regen
150
+ # Set starting energy to 60 and no regen
238
151
  agent = cfg.game.agent
239
152
  agent.inventory.initial = dict(agent.inventory.initial)
240
153
  agent.inventory.initial["energy"] = 60
@@ -302,7 +215,7 @@ class DiagnosticChargeUpHard(_DiagnosticMissionBase):
302
215
  max_steps: int = Field(default=350)
303
216
 
304
217
  def configure_env(self, cfg: MettaGridConfig) -> None:
305
- # Set starting energy to 30 and no regen
218
+ # Set starting energy to 60 and no regen
306
219
  agent = cfg.game.agent
307
220
  agent.inventory.initial = dict(agent.inventory.initial)
308
221
  agent.inventory.initial["energy"] = 60
@@ -9,11 +9,14 @@ energy regen, move cost, and capacity limits.
9
9
  from __future__ import annotations
10
10
 
11
11
  import logging
12
- from typing import override
12
+ from typing import TYPE_CHECKING, override
13
13
 
14
14
  from pydantic import Field
15
15
 
16
- from cogames.cogs_vs_clips.mission import Mission, MissionVariant
16
+ from cogames.core import CoGameMissionVariant
17
+
18
+ if TYPE_CHECKING:
19
+ from cogames.cogs_vs_clips.mission import CvCMission
17
20
  from mettagrid.config.mettagrid_config import MettaGridConfig
18
21
 
19
22
  logger = logging.getLogger(__name__)
@@ -28,7 +31,7 @@ ENERGY_REGEN_FLOOR = 0
28
31
  # =============================================================================
29
32
 
30
33
 
31
- class DifficultyLevel(MissionVariant):
34
+ class DifficultyLevel(CoGameMissionVariant):
32
35
  """Configuration for a difficulty level."""
33
36
 
34
37
  name: str = Field(description="Difficulty name (easy, medium, hard, brutal, etc.)")
@@ -45,24 +48,12 @@ class DifficultyLevel(MissionVariant):
45
48
  max_steps_override: int | None = Field(default=None)
46
49
 
47
50
  @override
48
- def modify_mission(self, mission: Mission):
51
+ def modify_mission(self, mission: CvCMission):
49
52
  """Apply a difficulty level to a mission instance."""
50
- # Energy regen
51
- if self.energy_regen_override is not None:
52
- mission.cog.energy_regen = self.energy_regen_override
53
- else:
54
- mission.cog.energy_regen = max(0, int(mission.cog.energy_regen * self.energy_regen_mult))
55
-
56
- # Mission-level overrides
57
- if self.move_energy_cost_override is not None:
58
- mission.cog.move_energy_cost = self.move_energy_cost_override
59
- if self.energy_capacity_override is not None:
60
- mission.cog.energy_limit = self.energy_capacity_override
61
- if self.cargo_capacity_override is not None:
62
- mission.cog.cargo_limit = self.cargo_capacity_override
53
+ pass # No mission-level modifications needed currently
63
54
 
64
55
  @override
65
- def modify_env(self, mission: Mission, env: MettaGridConfig):
56
+ def modify_env(self, mission: CvCMission, env: MettaGridConfig):
66
57
  if self.max_steps_override is not None:
67
58
  env.game.max_steps = self.max_steps_override
68
59
 
@@ -2,22 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
 
5
- from cogames.cogs_vs_clips.mission import Mission, Site
6
- from cogames.cogs_vs_clips.procedural import MachinaArena
5
+ from cogames.cogs_vs_clips.mission import CvCMission
7
6
  from cogames.cogs_vs_clips.sites import HELLO_WORLD
7
+ from cogames.cogs_vs_clips.terrain import MachinaArena
8
8
  from cogames.cogs_vs_clips.variants import (
9
9
  DarkSideVariant,
10
- DistantResourcesVariant,
11
- EmptyBaseVariant,
12
- PackRatVariant,
13
- QuadrantBuildingsVariant,
14
- SingleResourceUniformVariant,
15
10
  )
11
+ from cogames.core import CoGameSite
16
12
  from mettagrid.mapgen.mapgen import MapGen
17
13
 
18
14
  logger = logging.getLogger(__name__)
19
15
 
20
- SMALL_HELLO_WORLD = Site(
16
+ SMALL_HELLO_WORLD = CoGameSite(
21
17
  name="small_hello_world",
22
18
  description="Small hello world map.",
23
19
  map_builder=MapGen.Config(width=50, height=50, instance=MachinaArena.Config(spawn_count=20)),
@@ -25,7 +21,7 @@ SMALL_HELLO_WORLD = Site(
25
21
  max_cogs=20,
26
22
  )
27
23
 
28
- MEDIUM_HELLO_WORLD = Site(
24
+ MEDIUM_HELLO_WORLD = CoGameSite(
29
25
  name="medium_hello_world",
30
26
  description="Medium hello world map.",
31
27
  map_builder=MapGen.Config(width=100, height=100, instance=MachinaArena.Config(spawn_count=20)),
@@ -33,7 +29,7 @@ MEDIUM_HELLO_WORLD = Site(
33
29
  max_cogs=20,
34
30
  )
35
31
 
36
- LARGE_HELLO_WORLD = Site(
32
+ LARGE_HELLO_WORLD = CoGameSite(
37
33
  name="large_hello_world",
38
34
  description="Large hello world map.",
39
35
  map_builder=MapGen.Config(width=150, height=150, instance=MachinaArena.Config(spawn_count=20)),
@@ -41,65 +37,17 @@ LARGE_HELLO_WORLD = Site(
41
37
  max_cogs=20,
42
38
  )
43
39
 
44
- # Resource Bottleneck evals
45
- OxygenBottleneck = Mission(
46
- name="oxygen_bottleneck",
47
- description="Oxygen is the limiting resource; agents must prioritize oxygen over other resources.",
48
- site=HELLO_WORLD,
49
- variants=[
50
- EmptyBaseVariant(missing=["oxygen_extractor"]),
51
- SingleResourceUniformVariant(building_name="oxygen_extractor"),
52
- PackRatVariant(),
53
- ],
54
- )
55
-
56
40
  # Energy Starved evals
57
- EnergyStarved = Mission(
41
+ EnergyStarved = CvCMission(
58
42
  name="energy_starved",
59
43
  description="Energy is the limiting resource; agents must prioritize energy over other resources.",
60
44
  site=HELLO_WORLD,
61
45
  variants=[
62
- EmptyBaseVariant(),
63
46
  DarkSideVariant(),
64
47
  ],
65
48
  )
66
49
 
67
- # Collect Distant Resources evals
68
- DistantResources = Mission(
69
- name="distant_resources",
70
- description="Resources scattered far from base; heavy routing coordination.",
71
- site=HELLO_WORLD,
72
- variants=[
73
- EmptyBaseVariant(),
74
- DistantResourcesVariant(),
75
- ],
76
- )
77
-
78
- # Divide and Conquer evals
79
- QuadrantBuildings = Mission(
80
- name="quadrant_buildings",
81
- description="Place buildings in the four quadrants of the map.",
82
- site=HELLO_WORLD,
83
- variants=[
84
- EmptyBaseVariant(),
85
- QuadrantBuildingsVariant(),
86
- ],
87
- )
88
-
89
- EasyHeartsMission = Mission(
90
- name="easy_hearts",
91
- description="Simplified heart crafting with generous caps and extractor base.",
92
- site=HELLO_WORLD,
93
- variants=[
94
- PackRatVariant(),
95
- ],
96
- )
97
-
98
50
 
99
- EVAL_MISSIONS: list[Mission] = [
100
- OxygenBottleneck,
51
+ EVAL_MISSIONS: list[CvCMission] = [
101
52
  EnergyStarved,
102
- DistantResources,
103
- QuadrantBuildings,
104
- EasyHeartsMission,
105
53
  ]