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
@@ -2,14 +2,20 @@ from __future__ import annotations
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
7
+ from mettagrid.config.game_value import InventoryValue
8
+ from mettagrid.config.game_value import stat as game_stat
6
9
  from mettagrid.config.handler_config import Handler
7
10
  from mettagrid.config.mettagrid_config import (
8
11
  AgentConfig,
9
12
  InventoryConfig,
10
13
  ResourceLimitsConfig,
11
14
  )
15
+ from mettagrid.config.mutation.game_value_mutation import SetGameValueMutation
16
+ from mettagrid.config.mutation.mutation import EntityTarget
12
17
  from mettagrid.config.mutation.resource_mutation import updateActor
18
+ from mettagrid.config.reward_config import reward
13
19
 
14
20
 
15
21
  class CogConfig(Config):
@@ -32,48 +38,60 @@ class CogConfig(Config):
32
38
  # Initial inventory
33
39
  initial_energy: int = Field(default=100)
34
40
  initial_hp: int = Field(default=50)
41
+ initial_solar: int = Field(default=1)
35
42
 
36
43
  # Regen amounts
37
- energy_regen: int = Field(default=1)
38
44
  hp_regen: int = Field(default=-1)
39
45
  influence_regen: int = Field(default=-1)
46
+ action_cost: dict[str, int] = Field(default_factory=lambda: {"energy": 4})
40
47
 
41
- # Movement cost
42
- move_energy_cost: int = Field(default=3)
43
-
44
- def agent_config(self, gear: list[str], elements: list[str]) -> AgentConfig:
48
+ def agent_config(self, team: str, max_steps: int) -> AgentConfig:
45
49
  """Create an AgentConfig for this cog configuration."""
46
50
  return AgentConfig(
47
- collective="cogs",
51
+ collective=team,
48
52
  inventory=InventoryConfig(
49
53
  limits={
50
54
  "hp": ResourceLimitsConfig(min=self.hp_limit, resources=["hp"], modifiers=self.hp_modifiers),
51
55
  # when hp == 0, the cog can't hold gear or hearts
52
- "gear": ResourceLimitsConfig(max=self.gear_limit, resources=gear, modifiers={"hp": 100}),
56
+ "gear": ResourceLimitsConfig(max=self.gear_limit, resources=CvCConfig.GEAR, modifiers={"hp": 100}),
53
57
  "heart": ResourceLimitsConfig(max=self.heart_limit, resources=["heart"], modifiers={"hp": 100}),
54
58
  "energy": ResourceLimitsConfig(
55
59
  min=self.energy_limit, resources=["energy"], modifiers=self.energy_modifiers
56
60
  ),
57
61
  "cargo": ResourceLimitsConfig(
58
- min=self.cargo_limit, resources=elements, modifiers=self.cargo_modifiers
62
+ min=self.cargo_limit, resources=CvCConfig.ELEMENTS, modifiers=self.cargo_modifiers
59
63
  ),
60
64
  "influence": ResourceLimitsConfig(
61
65
  min=self.influence_limit, resources=["influence"], modifiers=self.influence_modifiers
62
66
  ),
63
67
  },
64
- initial={"energy": self.initial_energy, "hp": self.initial_hp},
68
+ initial={"energy": self.initial_energy, "hp": self.initial_hp, "solar": self.initial_solar},
65
69
  ),
66
70
  on_tick={
67
71
  "regen": Handler(
68
72
  mutations=[
69
73
  updateActor(
70
74
  {
71
- "energy": self.energy_regen,
72
75
  "hp": self.hp_regen,
73
76
  "influence": self.influence_regen,
74
77
  }
75
78
  )
76
79
  ]
77
- )
80
+ ),
81
+ "solar_to_energy": Handler(
82
+ mutations=[
83
+ SetGameValueMutation(
84
+ value=InventoryValue(item="energy"),
85
+ source=InventoryValue(item="solar"),
86
+ target=EntityTarget.ACTOR,
87
+ )
88
+ ]
89
+ ),
90
+ },
91
+ rewards={
92
+ "aligned_junction_held": reward(
93
+ game_stat("collective.aligned.junction.held"),
94
+ weight=1.0 / max_steps,
95
+ ),
78
96
  },
79
97
  )
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import Sequence
6
+
7
+ from cogames.cogs_vs_clips.mission import CvCMission
8
+ from cogames.cogs_vs_clips.reward_variants import AVAILABLE_REWARD_VARIANTS
9
+ from cogames.cogs_vs_clips.variants import HIDDEN_VARIANTS, VARIANTS
10
+ from cogames.core import CoGameMissionVariant
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class EventProfile:
15
+ name: str
16
+ clips_overrides: dict[str, object]
17
+ weather_overrides: dict[str, object]
18
+
19
+
20
+ COGSGUARD_FIXED_MAPS: list[str] = [
21
+ "machina_100_stations.map",
22
+ "machina_200_stations.map",
23
+ "cave_base_50.map",
24
+ "vanilla_large.map",
25
+ ]
26
+
27
+ DEFAULT_EVENT_PROFILE = EventProfile("events_baseline", {}, {})
28
+ COGSGUARD_EVENT_PROFILES: list[EventProfile] = [
29
+ DEFAULT_EVENT_PROFILE,
30
+ EventProfile(
31
+ "events_fast_clips_short_day",
32
+ {
33
+ "initial_clips_start": 5,
34
+ "initial_clips_spots": 2,
35
+ "scramble_start": 25,
36
+ "scramble_interval": 50,
37
+ "scramble_radius": 35,
38
+ "align_start": 50,
39
+ "align_interval": 50,
40
+ "align_radius": 35,
41
+ },
42
+ {"day_length": 100},
43
+ ),
44
+ EventProfile(
45
+ "events_slow_clips_long_day",
46
+ {
47
+ "initial_clips_start": 50,
48
+ "initial_clips_spots": 1,
49
+ "scramble_start": 200,
50
+ "scramble_interval": 200,
51
+ "scramble_radius": 15,
52
+ "align_start": 300,
53
+ "align_interval": 200,
54
+ "align_radius": 15,
55
+ },
56
+ {"day_length": 400},
57
+ ),
58
+ EventProfile(
59
+ "events_no_clips",
60
+ {"disabled": True},
61
+ {"day_length": 200},
62
+ ),
63
+ ]
64
+
65
+
66
+ def normalize_variant_names(variants: str | Sequence[str] | None) -> list[str]:
67
+ if variants is None:
68
+ return []
69
+ if isinstance(variants, str):
70
+ if variants.startswith("["):
71
+ parsed = json.loads(variants)
72
+ if isinstance(parsed, list):
73
+ return [str(name) for name in parsed]
74
+ return [variants]
75
+ return list(variants)
76
+
77
+
78
+ def split_variants(
79
+ variants: str | Sequence[str] | None,
80
+ ) -> tuple[list[CoGameMissionVariant], list[str]]:
81
+ if variants is None:
82
+ names: list[str] = []
83
+ else:
84
+ names = normalize_variant_names(variants)
85
+ all_variants = {variant.name: variant for variant in [*VARIANTS, *HIDDEN_VARIANTS]}
86
+ reward_variants = set(AVAILABLE_REWARD_VARIANTS)
87
+
88
+ resolved: list[CoGameMissionVariant] = []
89
+ resolved_rewards: list[str] = []
90
+ unknown: list[str] = []
91
+ for name in names:
92
+ if name in reward_variants:
93
+ resolved_rewards.append(name)
94
+ continue
95
+ variant = all_variants.get(name)
96
+ if variant is None:
97
+ unknown.append(name)
98
+ continue
99
+ resolved.append(variant)
100
+
101
+ if unknown:
102
+ available_mission = ", ".join(v.name for v in VARIANTS)
103
+ available_reward = ", ".join(AVAILABLE_REWARD_VARIANTS)
104
+ missing = ", ".join(unknown)
105
+ raise ValueError(
106
+ f"Unknown variant(s): {missing}. Mission variants: {available_mission}. "
107
+ f"Reward variants: {available_reward}."
108
+ )
109
+
110
+ return resolved, resolved_rewards
111
+
112
+
113
+ def resolve_event_profiles(event_profiles: Sequence[EventProfile] | None) -> list[EventProfile]:
114
+ if event_profiles is None:
115
+ return [DEFAULT_EVENT_PROFILE]
116
+ return list(event_profiles)
117
+
118
+
119
+ def filter_compatible_variants(
120
+ mission: CvCMission, variants: Sequence[CoGameMissionVariant]
121
+ ) -> list[CoGameMissionVariant]:
122
+ return [variant for variant in variants if variant.compat(mission)]
@@ -1,12 +1,14 @@
1
1
  """CogsGuard tutorial mission configuration."""
2
2
 
3
- from cogames.cogs_vs_clips.mission import Mission, Site
4
- from cogames.cogs_vs_clips.procedural import MachinaArena
3
+ from cogames.cogs_vs_clips.mission import CvCMission
4
+ from cogames.cogs_vs_clips.team import CogTeam
5
+ from cogames.cogs_vs_clips.terrain import MachinaArena
6
+ from cogames.core import CoGameSite
5
7
  from mettagrid.mapgen.mapgen import MapGen
6
8
  from mettagrid.mapgen.scenes.base_hub import BaseHubConfig
7
9
 
8
10
 
9
- def make_cogsguard_tutorial_site() -> Site:
11
+ def make_cogsguard_tutorial_site() -> CoGameSite:
10
12
  """Create a smaller, simpler CogsGuard arena for the tutorial."""
11
13
  hub_config = BaseHubConfig(
12
14
  corner_bundle="extractors",
@@ -16,11 +18,11 @@ def make_cogsguard_tutorial_site() -> Site:
16
18
  hub_height=15,
17
19
  outer_clearance=2,
18
20
  stations=[
19
- "aligner_station",
20
- "scrambler_station",
21
- "miner_station",
22
- "scout_station",
23
- "chest",
21
+ "c:aligner",
22
+ "c:scrambler",
23
+ "c:miner",
24
+ "c:scout",
25
+ "c:chest",
24
26
  ],
25
27
  )
26
28
  map_builder = MapGen.Config(
@@ -32,7 +34,7 @@ def make_cogsguard_tutorial_site() -> Site:
32
34
  hub=hub_config,
33
35
  ),
34
36
  )
35
- return Site(
37
+ return CoGameSite(
36
38
  name="cogsguard_tutorial",
37
39
  description="CogsGuard tutorial arena - small map for learning",
38
40
  map_builder=map_builder,
@@ -41,16 +43,13 @@ def make_cogsguard_tutorial_site() -> Site:
41
43
  )
42
44
 
43
45
 
44
- CogsGuardTutorialMission = Mission(
46
+ CogsGuardTutorialMission = CvCMission(
45
47
  name="tutorial",
46
48
  description="Learn the basics of CogsGuard: Roles, Resources, and Territory Control.",
47
49
  site=make_cogsguard_tutorial_site(),
48
50
  num_cogs=1,
49
51
  max_steps=2000,
50
- # Generous initial resources for learning
51
- collective_initial_carbon=50,
52
- collective_initial_oxygen=50,
53
- collective_initial_germanium=50,
54
- collective_initial_silicon=50,
55
- collective_initial_heart=10,
52
+ teams={
53
+ "cogs": CogTeam(name="cogs", num_agents=1, wealth=1),
54
+ },
56
55
  )
@@ -0,0 +1,38 @@
1
+ from types import SimpleNamespace
2
+
3
+ from mettagrid.config.vibes import Vibe
4
+
5
+ _GEAR = ["aligner", "scrambler", "miner", "scout"]
6
+ _ELEMENTS = ["oxygen", "carbon", "germanium", "silicon"]
7
+ _VIBES = [
8
+ Vibe("😐", "default"),
9
+ Vibe("❤️", "heart"),
10
+ Vibe("⚙️", "gear"),
11
+ Vibe("🌀", "scrambler"),
12
+ Vibe("🔗", "aligner"),
13
+ Vibe("⛏️", "miner"),
14
+ Vibe("🔭", "scout"),
15
+ ]
16
+
17
+ CvCConfig = SimpleNamespace(
18
+ GEAR=_GEAR,
19
+ ELEMENTS=_ELEMENTS,
20
+ HEART_COST={e: 10 for e in _ELEMENTS},
21
+ ALIGN_COST={"heart": 1},
22
+ SCRAMBLE_COST={"heart": 1},
23
+ GEAR_COSTS={
24
+ "aligner": {"carbon": 3, "oxygen": 1, "germanium": 1, "silicon": 1},
25
+ "scrambler": {"carbon": 1, "oxygen": 3, "germanium": 1, "silicon": 1},
26
+ "miner": {"carbon": 1, "oxygen": 1, "germanium": 3, "silicon": 1},
27
+ "scout": {"carbon": 1, "oxygen": 1, "germanium": 1, "silicon": 3},
28
+ },
29
+ GEAR_SYMBOLS={
30
+ "aligner": "🔗",
31
+ "scrambler": "🌀",
32
+ "miner": "⛏️",
33
+ "scout": "🔭",
34
+ },
35
+ RESOURCES=["energy", "heart", "hp", "influence", "solar", *_ELEMENTS, *_GEAR],
36
+ VIBES=_VIBES,
37
+ VIBE_NAMES=[vibe.name for vibe in _VIBES],
38
+ )
@@ -324,7 +324,6 @@ Key design choices:
324
324
 
325
325
  - Pack the base hub lightly (`EmptyBaseVariant`) where appropriate to encourage early exploration without
326
326
  over-constraining.
327
- - Add guidance (`CompassVariant`) on distance-heavy tasks to reduce pure exploration failure modes.
328
327
  - Raise agent caps modestly (`PackRatVariant`) to avoid early inventory stalls but keep routing relevant.
329
328
  - Shape reward on vibe missions (`HeartChorusVariant`) so partial progress is scored.
330
329
  - Keep vibe mechanics intact unless the mission explicitly focuses on vibe manipulation.
@@ -334,21 +333,20 @@ Included missions and variants:
334
333
  - oxygen_bottleneck: `EmptyBaseVariant(missing=["oxygen_extractor"])`, `ResourceBottleneckVariant(["oxygen"])`,
335
334
  `SingleResourceUniformVariant("oxygen_extractor")`, `PackRatVariant`
336
335
  - energy_starved: `EmptyBaseVariant`, `DarkSideVariant`, `PackRatVariant`
337
- - distant_resources: `EmptyBaseVariant`, `CompassVariant`, `DistantResourcesVariant`
338
- - quadrant_buildings: `EmptyBaseVariant`, `QuadrantBuildingsVariant`, `CompassVariant`
339
- - single_use_swarm: `EmptyBaseVariant`, `SingleUseSwarmVariant`, `CompassVariant`, `PackRatVariant`
336
+ - distant_resources: `EmptyBaseVariant`, `DistantResourcesVariant`
337
+ - quadrant_buildings: `EmptyBaseVariant`, `QuadrantBuildingsVariant`
338
+ - single_use_swarm: `EmptyBaseVariant`, `SingleUseSwarmVariant`, `PackRatVariant`
340
339
  - vibe_check: `HeartChorusVariant`, `VibeCheckMin2Variant`
341
340
 
342
341
  Usage example:
343
342
 
344
343
  ```bash
345
- uv run python packages/cogames/scripts/run_evaluation.py \
346
- --policy thinky \
344
+ uv run cogames diagnose thinky \
347
345
  --mission-set integrated_evals \
348
346
  --cogs 4 \
349
- --repeats 2
347
+ --episodes 2
350
348
  ```
351
349
 
352
- Recommendation: When designing new scorable baselines, combine one shaping variant (e.g., `CompassVariant`,
353
- `HeartChorusVariant`, `PackRatVariant`) with one constraint variant (e.g., `DarkSideVariant`,
354
- `ResourceBottleneckVariant`, `SingleUseSwarmVariant`) to keep tasks legible yet challenging.
350
+ Recommendation: When designing new scorable baselines, combine one "shaping" variant (e.g., `HeartChorusVariant`,
351
+ `PackRatVariant`) with one "constraint" variant (e.g., `DarkSideVariant`, `ResourceBottleneckVariant`,
352
+ `SingleUseSwarmVariant`) to keep tasks legible yet challenging.
@@ -179,11 +179,11 @@ uv run cogames play --mission hello_world.easy_hearts --cogs 2
179
179
  You can apply additional variants on top of the mission's built-in variants:
180
180
 
181
181
  ```bash
182
- # Add compass variant
183
- uv run cogames play --mission hello_world.oxygen_bottleneck --cogs 2 --variant compass
182
+ # Add pack_rat variant
183
+ uv run cogames play --mission hello_world.oxygen_bottleneck --cogs 2 --variant pack_rat
184
184
 
185
185
  # Add multiple variants
186
- uv run cogames play --mission hello_world.energy_starved --cogs 2 --variant compass --variant small_50
186
+ uv run cogames play --mission hello_world.energy_starved --cogs 2 --variant pack_rat --variant small_50
187
187
 
188
188
  # With policy
189
189
  uv run cogames play --mission hello_world.single_use_swarm --cogs 4 -p baseline
@@ -193,49 +193,25 @@ uv run cogames play --mission hello_world.single_use_swarm --cogs 4 -p baseline
193
193
 
194
194
  ## Programmatic Evaluation
195
195
 
196
- ### Using run_evaluation.py
196
+ ### Using cogames diagnose / cogames run
197
197
 
198
198
  For systematic evaluation across multiple missions and configurations:
199
199
 
200
200
  ```bash
201
201
  # Evaluate on integrated eval suite
202
- uv run python packages/cogames/scripts/run_evaluation.py \
203
- --policy thinky \
202
+ uv run cogames diagnose thinky \
204
203
  --mission-set integrated_evals \
205
204
  --cogs 4 \
206
- --repeats 2
205
+ --episodes 2
207
206
 
208
- # Evaluate specific agent
209
- uv run python packages/cogames/scripts/run_evaluation.py \
207
+ # Evaluate specific agent with structured output
208
+ uv run cogames run \
209
+ --mission-set integrated_evals \
210
210
  --policy baseline \
211
- --steps 1000 \
212
- --output eval_baseline.json
211
+ --episodes 10 \
212
+ --format json
213
213
  ```
214
214
 
215
- ### Using in Curriculum Training
216
-
217
- Both diagnostic and integrated missions can be used in curriculum training via `mission_variant_curriculum.py`:
218
-
219
- ```python
220
- from recipes.experiment.cvc import mission_variant_curriculum
221
-
222
- # Train on diagnostic missions
223
- mission_variant_curriculum.train(
224
- base_missions=["diagnostic_missions"],
225
- num_cogs=4,
226
- variants="all"
227
- )
228
-
229
- # Train on specific integrated missions
230
- mission_variant_curriculum.train(
231
- base_missions=["oxygen_bottleneck", "energy_starved"],
232
- num_cogs=4,
233
- variants=["compass", "pack_rat"]
234
- )
235
- ```
236
-
237
- ---
238
-
239
215
  ## Design Philosophy
240
216
 
241
217
  ### Diagnostic Missions
@@ -2,15 +2,30 @@ from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from cogames.cogs_vs_clips.mission import CogsGuardMission, Site
6
- from cogames.cogs_vs_clips.mission_utils import get_map
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
7
9
 
8
10
  MAPS_DIR = Path(__file__).resolve().parent.parent.parent / "maps"
9
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
+
10
25
  COGSGUARD_EVALS_BASE = Site(
11
26
  name="cogsguard_evals",
12
27
  description="CogsGuard evaluation arenas.",
13
- map_builder=get_map("evals/eval_balanced_spread.map"),
28
+ map_builder=_load_map("evals/eval_balanced_spread.map"),
14
29
  min_cogs=1,
15
30
  max_cogs=20,
16
31
  )
@@ -30,7 +45,7 @@ def _count_spawn_pads(map_path: Path) -> int:
30
45
  def _make_eval_site(map_name: str, num_cogs: int) -> Site:
31
46
  site = COGSGUARD_EVALS_BASE.model_copy(
32
47
  update={
33
- "map_builder": get_map(map_name),
48
+ "map_builder": _load_map(map_name),
34
49
  "min_cogs": num_cogs,
35
50
  "max_cogs": num_cogs,
36
51
  }
@@ -66,13 +81,13 @@ COGSGUARD_EVAL_MAPS: list[str] = [
66
81
 
67
82
  COGSGUARD_EVAL_COGS = {map_name: _count_spawn_pads(MAPS_DIR / map_name) for map_name in COGSGUARD_EVAL_MAPS}
68
83
 
69
- COGSGUARD_EVAL_MISSIONS: list[CogsGuardMission] = []
84
+ COGSGUARD_EVAL_MISSIONS: list[CvCMission] = []
70
85
  for map_name in COGSGUARD_EVAL_MAPS:
71
86
  stem = Path(map_name).stem
72
87
  num_cogs = COGSGUARD_EVAL_COGS[map_name]
73
88
  site = _make_eval_site(map_name, num_cogs)
74
89
  COGSGUARD_EVAL_MISSIONS.append(
75
- CogsGuardMission(
90
+ CvCMission(
76
91
  name=stem,
77
92
  description=_description_from_stem(stem),
78
93
  site=site,
@@ -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(
@@ -59,30 +26,15 @@ _GENEROUS_COG = CogConfig(
59
26
  cargo_limit=255,
60
27
  initial_energy=255,
61
28
  initial_hp=100,
62
- energy_regen=255,
63
- hp_regen=0,
64
- influence_regen=0,
65
- )
66
-
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
29
  hp_regen=0,
78
30
  influence_regen=0,
79
31
  )
80
32
 
81
33
 
82
- class _DiagnosticMissionBase(Mission):
34
+ class _DiagnosticMissionBase(CvCMission):
83
35
  """Base class for minimal diagnostic evaluation missions."""
84
36
 
85
- site: Site = EVALS
37
+ site: CoGameSite = EVALS
86
38
  cog: CogConfig = Field(default_factory=lambda: _GENEROUS_COG.model_copy())
87
39
 
88
40
  map_name: str = Field(default="evals/diagnostic_eval_template.map")
@@ -90,15 +42,9 @@ class _DiagnosticMissionBase(Mission):
90
42
  required_agents: int | None = Field(default=None)
91
43
 
92
44
  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
45
  # If True, give agents high energy capacity and regen (overridden by specific missions)
96
46
  generous_energy: bool = Field(default=True)
97
47
 
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
48
  def configure_env(self, cfg: MettaGridConfig) -> None: # pragma: no cover - hook for subclasses
103
49
  """Hook for mission-specific environment alterations."""
104
50
 
@@ -114,10 +60,6 @@ class _DiagnosticMissionBase(Mission):
114
60
  cfg.game.map_builder = forced_map
115
61
  cfg.game.max_steps = self.max_steps
116
62
  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
63
  self.configure_env(cfg)
122
64
  return cfg
123
65
  finally:
@@ -134,45 +76,15 @@ class _DiagnosticMissionBase(Mission):
134
76
  seed = dict(cfg.game.agent.inventory.initial)
135
77
  seed.update(self.inventory_seed)
136
78
  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
79
+ # Also apply to per-agent configs (used by CvCMission)
80
+ for agent_cfg in cfg.game.agents:
81
+ agent_seed = dict(agent_cfg.inventory.initial)
82
+ agent_seed.update(self.inventory_seed)
83
+ agent_cfg.inventory.initial = agent_seed
172
84
 
173
85
 
174
86
  # ----------------------------------------------------------------------
175
- # Diagnostics (non-hub)
87
+ # Diagnostic missions (no assemblers)
176
88
  # ----------------------------------------------------------------------
177
89
 
178
90
 
@@ -234,7 +146,7 @@ class DiagnosticChargeUp(_DiagnosticMissionBase):
234
146
  max_steps: int = Field(default=250)
235
147
 
236
148
  def configure_env(self, cfg: MettaGridConfig) -> None:
237
- # Set starting energy to 30 and no regen
149
+ # Set starting energy to 60 and no regen
238
150
  agent = cfg.game.agent
239
151
  agent.inventory.initial = dict(agent.inventory.initial)
240
152
  agent.inventory.initial["energy"] = 60
@@ -302,7 +214,7 @@ class DiagnosticChargeUpHard(_DiagnosticMissionBase):
302
214
  max_steps: int = Field(default=350)
303
215
 
304
216
  def configure_env(self, cfg: MettaGridConfig) -> None:
305
- # Set starting energy to 30 and no regen
217
+ # Set starting energy to 60 and no regen
306
218
  agent = cfg.game.agent
307
219
  agent.inventory.initial = dict(agent.inventory.initial)
308
220
  agent.inventory.initial["energy"] = 60