cogames 0.3.65__py3-none-any.whl → 0.3.69__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.
- cogames/cli/client.py +0 -3
- cogames/cli/docsync/docsync.py +7 -1
- cogames/cli/mission.py +44 -19
- cogames/cli/policy.py +26 -10
- cogames/cli/submit.py +201 -495
- cogames/cli/utils.py +5 -0
- cogames/cogs_vs_clips/clip_difficulty.py +57 -0
- cogames/cogs_vs_clips/clips.py +23 -6
- cogames/cogs_vs_clips/cog.py +16 -5
- cogames/cogs_vs_clips/cogsguard_curriculum.py +122 -0
- cogames/cogs_vs_clips/cogsguard_tutorial.py +5 -5
- cogames/cogs_vs_clips/config.py +1 -1
- cogames/cogs_vs_clips/docs/cogs_vs_clips_mapgen.md +2 -3
- cogames/cogs_vs_clips/evals/README.md +8 -32
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +0 -1
- cogames/cogs_vs_clips/evals/difficulty_variants.py +7 -10
- cogames/cogs_vs_clips/mission.py +38 -10
- cogames/cogs_vs_clips/missions.py +1 -1
- cogames/cogs_vs_clips/reward_variants.py +173 -0
- cogames/cogs_vs_clips/sites.py +6 -5
- cogames/cogs_vs_clips/stations.py +13 -9
- cogames/cogs_vs_clips/team.py +3 -1
- cogames/cogs_vs_clips/terrain.py +2 -2
- cogames/cogs_vs_clips/variants.py +175 -4
- cogames/cogs_vs_clips/weather.py +52 -0
- cogames/docs/SCRIPTED_AGENT.md +3 -3
- cogames/evaluate.py +4 -2
- cogames/main.py +420 -84
- cogames/maps/canidate1_1000.map +1 -1
- cogames/maps/canidate1_1000_stations.map +2 -2
- cogames/maps/canidate1_500.map +1 -1
- cogames/maps/canidate1_500_stations.map +2 -2
- cogames/maps/canidate2_1000.map +1 -1
- cogames/maps/canidate2_1000_stations.map +2 -2
- cogames/maps/canidate2_500.map +1 -1
- cogames/maps/canidate2_500_stations.map +1 -1
- cogames/maps/canidate3_1000.map +1 -1
- cogames/maps/canidate3_1000_stations.map +2 -2
- cogames/maps/canidate3_500.map +1 -1
- cogames/maps/canidate3_500_stations.map +2 -2
- cogames/maps/canidate4_500.map +1 -1
- cogames/maps/canidate4_500_stations.map +2 -2
- cogames/maps/cave_base_50.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_charge_up.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_near.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_unclip.map +6 -6
- cogames/maps/evals/eval_balanced_spread.map +6 -6
- cogames/maps/evals/eval_clip_oxygen.map +6 -6
- cogames/maps/evals/eval_collect_resources.map +6 -6
- cogames/maps/evals/eval_collect_resources_hard.map +6 -6
- cogames/maps/evals/eval_collect_resources_medium.map +6 -6
- cogames/maps/evals/eval_divide_and_conquer.map +6 -6
- cogames/maps/evals/eval_energy_starved.map +6 -6
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +6 -6
- cogames/maps/evals/eval_oxygen_bottleneck.map +6 -6
- cogames/maps/evals/eval_single_use_world.map +6 -6
- cogames/maps/evals/extractor_hub_100x100.map +6 -6
- cogames/maps/evals/extractor_hub_30x30.map +6 -6
- cogames/maps/evals/extractor_hub_50x50.map +6 -6
- cogames/maps/evals/extractor_hub_70x70.map +6 -6
- cogames/maps/evals/extractor_hub_80x80.map +6 -6
- cogames/maps/machina_100_stations.map +2 -2
- cogames/maps/machina_200_stations.map +2 -2
- cogames/maps/machina_200_stations_small.map +2 -2
- cogames/maps/machina_eval_exp01.map +2 -2
- cogames/maps/machina_eval_template_large.map +2 -2
- cogames/maps/machinatrainer4agents.map +2 -2
- cogames/maps/machinatrainer4agentsbase.map +2 -2
- cogames/maps/machinatrainerbig.map +2 -2
- cogames/maps/machinatrainersmall.map +2 -2
- cogames/maps/planky_evals/aligner_avoid_aoe.map +6 -6
- cogames/maps/planky_evals/aligner_full_cycle.map +6 -6
- cogames/maps/planky_evals/aligner_gear.map +6 -6
- cogames/maps/planky_evals/aligner_hearts.map +6 -6
- cogames/maps/planky_evals/aligner_junction.map +6 -6
- cogames/maps/planky_evals/exploration_distant.map +6 -6
- cogames/maps/planky_evals/maze.map +6 -6
- cogames/maps/planky_evals/miner_best_resource.map +6 -6
- cogames/maps/planky_evals/miner_deposit.map +6 -6
- cogames/maps/planky_evals/miner_extract.map +6 -6
- cogames/maps/planky_evals/miner_full_cycle.map +6 -6
- cogames/maps/planky_evals/miner_gear.map +6 -6
- cogames/maps/planky_evals/multi_role.map +6 -6
- cogames/maps/planky_evals/resource_chain.map +6 -6
- cogames/maps/planky_evals/scout_explore.map +6 -6
- cogames/maps/planky_evals/scout_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_full_cycle.map +6 -6
- cogames/maps/planky_evals/scrambler_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_target.map +6 -6
- cogames/maps/planky_evals/stuck_corridor.map +6 -6
- cogames/maps/planky_evals/survive_retreat.map +6 -6
- cogames/maps/training_facility_clipped.map +2 -2
- cogames/maps/training_facility_open_1.map +2 -2
- cogames/maps/training_facility_open_2.map +2 -2
- cogames/maps/training_facility_open_3.map +2 -2
- cogames/maps/training_facility_tight_4.map +2 -2
- cogames/maps/training_facility_tight_5.map +2 -2
- cogames/maps/vanilla_large.map +2 -2
- cogames/maps/vanilla_small.map +2 -2
- cogames/pickup.py +6 -5
- cogames/play.py +14 -16
- cogames/policy/nim_agents/__init__.py +0 -2
- cogames/policy/nim_agents/agents.py +0 -11
- cogames/policy/starter_agent.py +4 -1
- {cogames-0.3.65.dist-info → cogames-0.3.69.dist-info}/METADATA +45 -29
- cogames-0.3.69.dist-info/RECORD +160 -0
- metta_alo/scoring.py +7 -7
- cogames-0.3.65.dist-info/RECORD +0 -160
- metta_alo/job_specs.py +0 -17
- metta_alo/policy.py +0 -16
- metta_alo/pure_single_episode_runner.py +0 -75
- metta_alo/rollout.py +0 -322
- {cogames-0.3.65.dist-info → cogames-0.3.69.dist-info}/WHEEL +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.69.dist-info}/entry_points.txt +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.69.dist-info}/licenses/LICENSE +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.69.dist-info}/top_level.txt +0 -0
cogames/cli/utils.py
CHANGED
|
@@ -11,6 +11,11 @@ def suppress_noisy_logs() -> None:
|
|
|
11
11
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="pkg_resources")
|
|
12
12
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="pygame.pkgdata")
|
|
13
13
|
|
|
14
|
+
# Pyro docstrings use LaTeX math notation (\ge for ≥) which Python 3.12+ warns about:
|
|
15
|
+
# pyro/ops/stats.py:527: SyntaxWarning: invalid escape sequence '\g'
|
|
16
|
+
# Note: module= filter doesn't work for SyntaxWarnings (emitted at parse time before module loads)
|
|
17
|
+
warnings.filterwarnings("ignore", category=SyntaxWarning, message=r".*invalid escape sequence.*")
|
|
18
|
+
|
|
14
19
|
# Silence PyTorch distributed elastic warning about redirects on MacOS/Windows
|
|
15
20
|
logging.getLogger("torch.distributed.elastic.multiprocessing.redirects").setLevel(logging.ERROR)
|
|
16
21
|
warnings.filterwarnings(
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Final, Optional
|
|
4
|
+
|
|
5
|
+
from cogames.cogs_vs_clips.mission import CvCMission
|
|
6
|
+
from cogames.core import CoGameMissionVariant
|
|
7
|
+
|
|
8
|
+
MEDIUM_CLIPS_END: Final[int] = 300
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CogsGuardDifficulty(CoGameMissionVariant):
|
|
12
|
+
name: str
|
|
13
|
+
description: str = ""
|
|
14
|
+
disable_clips: bool = False
|
|
15
|
+
scramble_end: Optional[int] = None
|
|
16
|
+
align_end: Optional[int] = None
|
|
17
|
+
presence_end: Optional[int] = None
|
|
18
|
+
|
|
19
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
20
|
+
if self.disable_clips:
|
|
21
|
+
mission.clips.disabled = True
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
mission.clips.disabled = False
|
|
25
|
+
mission.clips.scramble_end = self.scramble_end
|
|
26
|
+
mission.clips.align_end = self.align_end
|
|
27
|
+
mission.clips.presence_end = self.presence_end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
EASY = CogsGuardDifficulty(
|
|
31
|
+
name="easy",
|
|
32
|
+
description="No clips events.",
|
|
33
|
+
disable_clips=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
MEDIUM = CogsGuardDifficulty(
|
|
37
|
+
name="medium",
|
|
38
|
+
description="A few early clips events, then none.",
|
|
39
|
+
scramble_end=MEDIUM_CLIPS_END,
|
|
40
|
+
align_end=MEDIUM_CLIPS_END,
|
|
41
|
+
presence_end=MEDIUM_CLIPS_END,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
HARD = CogsGuardDifficulty(
|
|
45
|
+
name="hard",
|
|
46
|
+
description="Standard clips event system.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
COGSGUARD_DIFFICULTIES: tuple[CogsGuardDifficulty, ...] = (EASY, MEDIUM, HARD)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_cogsguard_difficulty(name: str) -> CogsGuardDifficulty:
|
|
53
|
+
for difficulty in COGSGUARD_DIFFICULTIES:
|
|
54
|
+
if difficulty.name == name:
|
|
55
|
+
return difficulty
|
|
56
|
+
available = ", ".join(d.name for d in COGSGUARD_DIFFICULTIES)
|
|
57
|
+
raise ValueError(f"Unknown difficulty '{name}'. Available difficulties: {available}")
|
cogames/cogs_vs_clips/clips.py
CHANGED
|
@@ -4,6 +4,8 @@ Clips are a non-player faction that gradually takes over neutral junctions.
|
|
|
4
4
|
These events create the spreading/scrambling behavior that pressures players.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
7
9
|
from pydantic import Field
|
|
8
10
|
|
|
9
11
|
from mettagrid.base_config import Config
|
|
@@ -18,6 +20,8 @@ from mettagrid.config.tag import typeTag
|
|
|
18
20
|
class ClipsConfig(Config):
|
|
19
21
|
"""Configuration for clips behavior in CogsGuard game mode."""
|
|
20
22
|
|
|
23
|
+
disabled: bool = Field(default=False)
|
|
24
|
+
|
|
21
25
|
# Clips Behavior - scramble cogs junctions to neutral
|
|
22
26
|
initial_clips_start: int = Field(default=10)
|
|
23
27
|
initial_clips_spots: int = Field(default=1)
|
|
@@ -25,11 +29,16 @@ class ClipsConfig(Config):
|
|
|
25
29
|
scramble_start: int = Field(default=50)
|
|
26
30
|
scramble_interval: int = Field(default=100)
|
|
27
31
|
scramble_radius: int = Field(default=25)
|
|
32
|
+
scramble_end: Optional[int] = Field(default=None)
|
|
28
33
|
|
|
29
34
|
# Clips Behavior - align neutral junctions to clips
|
|
30
35
|
align_start: int = Field(default=100)
|
|
31
36
|
align_interval: int = Field(default=100)
|
|
32
37
|
align_radius: int = Field(default=25)
|
|
38
|
+
align_end: Optional[int] = Field(default=None)
|
|
39
|
+
|
|
40
|
+
# Clips Behavior - presence check for re-invasion
|
|
41
|
+
presence_end: Optional[int] = Field(default=None)
|
|
33
42
|
|
|
34
43
|
def events(self, max_steps: int) -> dict[str, EventConfig]:
|
|
35
44
|
"""Create all clips events for a mission.
|
|
@@ -37,6 +46,12 @@ class ClipsConfig(Config):
|
|
|
37
46
|
Returns:
|
|
38
47
|
Dictionary of event name to EventConfig.
|
|
39
48
|
"""
|
|
49
|
+
if self.disabled:
|
|
50
|
+
return {}
|
|
51
|
+
scramble_end = max_steps if self.scramble_end is None else min(self.scramble_end, max_steps)
|
|
52
|
+
align_end = max_steps if self.align_end is None else min(self.align_end, max_steps)
|
|
53
|
+
presence_end = max_steps if self.presence_end is None else min(self.presence_end, max_steps)
|
|
54
|
+
|
|
40
55
|
return {
|
|
41
56
|
"initial_clips": EventConfig(
|
|
42
57
|
name="initial_clips",
|
|
@@ -48,7 +63,7 @@ class ClipsConfig(Config):
|
|
|
48
63
|
"cogs_to_neutral": EventConfig(
|
|
49
64
|
name="cogs_to_neutral",
|
|
50
65
|
target_tag=typeTag("junction"),
|
|
51
|
-
timesteps=periodic(start=self.scramble_start, period=self.scramble_interval, end=
|
|
66
|
+
timesteps=periodic(start=self.scramble_start, period=self.scramble_interval, end=scramble_end),
|
|
52
67
|
# near a clips-aligned junction
|
|
53
68
|
filters=[
|
|
54
69
|
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.scramble_radius),
|
|
@@ -61,7 +76,7 @@ class ClipsConfig(Config):
|
|
|
61
76
|
"neutral_to_clips": EventConfig(
|
|
62
77
|
name="neutral_to_clips",
|
|
63
78
|
target_tag=typeTag("junction"),
|
|
64
|
-
timesteps=periodic(start=self.align_start, period=self.align_interval, end=
|
|
79
|
+
timesteps=periodic(start=self.align_start, period=self.align_interval, end=align_end),
|
|
65
80
|
# neutral junctions near a clips-aligned junction
|
|
66
81
|
filters=[
|
|
67
82
|
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.align_radius),
|
|
@@ -74,13 +89,15 @@ class ClipsConfig(Config):
|
|
|
74
89
|
"presence_check": EventConfig(
|
|
75
90
|
name="presence_check",
|
|
76
91
|
target_tag=typeTag("junction"),
|
|
77
|
-
timesteps=periodic(start=self.initial_clips_start, period=self.scramble_interval * 2, end=
|
|
92
|
+
timesteps=periodic(start=self.initial_clips_start, period=self.scramble_interval * 2, end=presence_end),
|
|
78
93
|
filters=[isNear(typeTag("junction"), [isAlignedTo("clips")], radius=1000)],
|
|
79
94
|
max_targets=1,
|
|
80
95
|
fallback="initial_clips",
|
|
81
96
|
),
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
def
|
|
85
|
-
"""Create
|
|
86
|
-
|
|
99
|
+
def collectives(self) -> dict[str, CollectiveConfig]:
|
|
100
|
+
"""Create collectives for clips."""
|
|
101
|
+
if self.disabled:
|
|
102
|
+
return {}
|
|
103
|
+
return {"clips": CollectiveConfig(name="clips")}
|
cogames/cogs_vs_clips/cog.py
CHANGED
|
@@ -4,6 +4,7 @@ from pydantic import Field
|
|
|
4
4
|
|
|
5
5
|
from cogames.cogs_vs_clips.config import CvCConfig
|
|
6
6
|
from mettagrid.base_config import Config
|
|
7
|
+
from mettagrid.config.game_value import InventoryValue
|
|
7
8
|
from mettagrid.config.game_value import stat as game_stat
|
|
8
9
|
from mettagrid.config.handler_config import Handler
|
|
9
10
|
from mettagrid.config.mettagrid_config import (
|
|
@@ -11,6 +12,8 @@ from mettagrid.config.mettagrid_config import (
|
|
|
11
12
|
InventoryConfig,
|
|
12
13
|
ResourceLimitsConfig,
|
|
13
14
|
)
|
|
15
|
+
from mettagrid.config.mutation.game_value_mutation import SetGameValueMutation
|
|
16
|
+
from mettagrid.config.mutation.mutation import EntityTarget
|
|
14
17
|
from mettagrid.config.mutation.resource_mutation import updateActor
|
|
15
18
|
from mettagrid.config.reward_config import reward
|
|
16
19
|
|
|
@@ -35,12 +38,12 @@ class CogConfig(Config):
|
|
|
35
38
|
# Initial inventory
|
|
36
39
|
initial_energy: int = Field(default=100)
|
|
37
40
|
initial_hp: int = Field(default=50)
|
|
41
|
+
initial_solar: int = Field(default=1)
|
|
38
42
|
|
|
39
43
|
# Regen amounts
|
|
40
|
-
energy_regen: int = Field(default=1)
|
|
41
44
|
hp_regen: int = Field(default=-1)
|
|
42
45
|
influence_regen: int = Field(default=-1)
|
|
43
|
-
action_cost: dict[str, int] = Field(default_factory=lambda: {"energy":
|
|
46
|
+
action_cost: dict[str, int] = Field(default_factory=lambda: {"energy": 4})
|
|
44
47
|
|
|
45
48
|
def agent_config(self, team: str, max_steps: int) -> AgentConfig:
|
|
46
49
|
"""Create an AgentConfig for this cog configuration."""
|
|
@@ -62,20 +65,28 @@ class CogConfig(Config):
|
|
|
62
65
|
min=self.influence_limit, resources=["influence"], modifiers=self.influence_modifiers
|
|
63
66
|
),
|
|
64
67
|
},
|
|
65
|
-
initial={"energy": self.initial_energy, "hp": self.initial_hp},
|
|
68
|
+
initial={"energy": self.initial_energy, "hp": self.initial_hp, "solar": self.initial_solar},
|
|
66
69
|
),
|
|
67
70
|
on_tick={
|
|
68
71
|
"regen": Handler(
|
|
69
72
|
mutations=[
|
|
70
73
|
updateActor(
|
|
71
74
|
{
|
|
72
|
-
"energy": self.energy_regen,
|
|
73
75
|
"hp": self.hp_regen,
|
|
74
76
|
"influence": self.influence_regen,
|
|
75
77
|
}
|
|
76
78
|
)
|
|
77
79
|
]
|
|
78
|
-
)
|
|
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
|
+
),
|
|
79
90
|
},
|
|
80
91
|
rewards={
|
|
81
92
|
"aligned_junction_held": reward(
|
|
@@ -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)]
|
|
@@ -18,11 +18,11 @@ def make_cogsguard_tutorial_site() -> CoGameSite:
|
|
|
18
18
|
hub_height=15,
|
|
19
19
|
outer_clearance=2,
|
|
20
20
|
stations=[
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"chest",
|
|
21
|
+
"c:aligner",
|
|
22
|
+
"c:scrambler",
|
|
23
|
+
"c:miner",
|
|
24
|
+
"c:scout",
|
|
25
|
+
"c:chest",
|
|
26
26
|
],
|
|
27
27
|
)
|
|
28
28
|
map_builder = MapGen.Config(
|
cogames/cogs_vs_clips/config.py
CHANGED
|
@@ -32,7 +32,7 @@ CvCConfig = SimpleNamespace(
|
|
|
32
32
|
"miner": "⛏️",
|
|
33
33
|
"scout": "🔭",
|
|
34
34
|
},
|
|
35
|
-
RESOURCES=["energy", "heart", "hp", "influence", *_ELEMENTS, *_GEAR],
|
|
35
|
+
RESOURCES=["energy", "heart", "hp", "influence", "solar", *_ELEMENTS, *_GEAR],
|
|
36
36
|
VIBES=_VIBES,
|
|
37
37
|
VIBE_NAMES=[vibe.name for vibe in _VIBES],
|
|
38
38
|
)
|
|
@@ -341,11 +341,10 @@ Included missions and variants:
|
|
|
341
341
|
Usage example:
|
|
342
342
|
|
|
343
343
|
```bash
|
|
344
|
-
uv run
|
|
345
|
-
--policy thinky \
|
|
344
|
+
uv run cogames diagnose thinky \
|
|
346
345
|
--mission-set integrated_evals \
|
|
347
346
|
--cogs 4 \
|
|
348
|
-
--
|
|
347
|
+
--episodes 2
|
|
349
348
|
```
|
|
350
349
|
|
|
351
350
|
Recommendation: When designing new scorable baselines, combine one "shaping" variant (e.g., `HeartChorusVariant`,
|
|
@@ -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
|
|
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
|
|
203
|
-
--policy thinky \
|
|
202
|
+
uv run cogames diagnose thinky \
|
|
204
203
|
--mission-set integrated_evals \
|
|
205
204
|
--cogs 4 \
|
|
206
|
-
--
|
|
205
|
+
--episodes 2
|
|
207
206
|
|
|
208
|
-
# Evaluate specific agent
|
|
209
|
-
uv run
|
|
207
|
+
# Evaluate specific agent with structured output
|
|
208
|
+
uv run cogames run \
|
|
209
|
+
--mission-set integrated_evals \
|
|
210
210
|
--policy baseline \
|
|
211
|
-
--
|
|
212
|
-
--
|
|
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=["pack_rat", "energized"]
|
|
234
|
-
)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
215
|
## Design Philosophy
|
|
240
216
|
|
|
241
217
|
### Diagnostic Missions
|
|
@@ -37,11 +37,8 @@ class DifficultyLevel(CoGameMissionVariant):
|
|
|
37
37
|
name: str = Field(description="Difficulty name (easy, medium, hard, brutal, etc.)")
|
|
38
38
|
description: str = Field(description="What makes this difficulty challenging", default="")
|
|
39
39
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Absolute overrides (if set, ignore multipliers)
|
|
44
|
-
energy_regen_override: int | None = Field(default=None)
|
|
40
|
+
# Solar override (if set, overrides weather day/night deltas)
|
|
41
|
+
solar_override: int | None = Field(default=None)
|
|
45
42
|
move_energy_cost_override: int | None = Field(default=None)
|
|
46
43
|
energy_capacity_override: int | None = Field(default=None)
|
|
47
44
|
cargo_capacity_override: int | None = Field(default=None)
|
|
@@ -70,20 +67,20 @@ STANDARD = DifficultyLevel(
|
|
|
70
67
|
HARD = DifficultyLevel(
|
|
71
68
|
name="hard",
|
|
72
69
|
description="Minimal passive regen and higher move cost",
|
|
73
|
-
|
|
70
|
+
solar_override=1, # Minimal regen prevents deadlock
|
|
74
71
|
move_energy_cost_override=2,
|
|
75
72
|
)
|
|
76
73
|
|
|
77
74
|
SINGLE_USE = DifficultyLevel(
|
|
78
75
|
name="single_use",
|
|
79
76
|
description="Minimal regen - no second chances",
|
|
80
|
-
|
|
77
|
+
solar_override=1,
|
|
81
78
|
)
|
|
82
79
|
|
|
83
80
|
SPEED_RUN = DifficultyLevel(
|
|
84
81
|
name="speed_run",
|
|
85
82
|
description="Short clock, cheap movement",
|
|
86
|
-
|
|
83
|
+
solar_override=2,
|
|
87
84
|
move_energy_cost_override=1,
|
|
88
85
|
max_steps_override=600,
|
|
89
86
|
)
|
|
@@ -91,7 +88,7 @@ SPEED_RUN = DifficultyLevel(
|
|
|
91
88
|
ENERGY_CRISIS = DifficultyLevel(
|
|
92
89
|
name="energy_crisis",
|
|
93
90
|
description="Minimal passive regen - plan every move",
|
|
94
|
-
|
|
91
|
+
solar_override=1, # Minimal regen prevents deadlock
|
|
95
92
|
)
|
|
96
93
|
|
|
97
94
|
# Export variants for use with --variant CLI flag.
|
|
@@ -116,7 +113,7 @@ def list_difficulties() -> None:
|
|
|
116
113
|
print("=" * 80)
|
|
117
114
|
for diff in DIFFICULTY_VARIANTS:
|
|
118
115
|
print(f"\n{diff.name.upper()}: {diff.description}")
|
|
119
|
-
print(f"
|
|
116
|
+
print(f" Solar override: {diff.solar_override}")
|
|
120
117
|
|
|
121
118
|
|
|
122
119
|
if __name__ == "__main__":
|
cogames/cogs_vs_clips/mission.py
CHANGED
|
@@ -15,6 +15,7 @@ from cogames.cogs_vs_clips.stations import (
|
|
|
15
15
|
)
|
|
16
16
|
from cogames.cogs_vs_clips.team import CogTeam
|
|
17
17
|
from cogames.cogs_vs_clips.variants import NumCogsVariant
|
|
18
|
+
from cogames.cogs_vs_clips.weather import WeatherConfig
|
|
18
19
|
from cogames.core import (
|
|
19
20
|
MAP_MISSION_DELIMITER,
|
|
20
21
|
CoGameMission,
|
|
@@ -28,7 +29,7 @@ from mettagrid.config.action_config import (
|
|
|
28
29
|
NoopActionConfig,
|
|
29
30
|
)
|
|
30
31
|
from mettagrid.config.game_value import inv
|
|
31
|
-
from mettagrid.config.mettagrid_config import GameConfig, MettaGridConfig
|
|
32
|
+
from mettagrid.config.mettagrid_config import CollectiveConfig, GameConfig, MettaGridConfig
|
|
32
33
|
from mettagrid.config.obs_config import GlobalObsConfig, ObsConfig
|
|
33
34
|
from mettagrid.map_builder.map_builder import AnyMapBuilderConfig
|
|
34
35
|
|
|
@@ -46,6 +47,7 @@ class CvCMission(CoGameMission):
|
|
|
46
47
|
"""Mission configuration for CogsGuard game mode."""
|
|
47
48
|
|
|
48
49
|
max_steps: int = Field(default=10000)
|
|
50
|
+
total_junctions: int = Field(default=118, description="Total junctions on the map (for curriculum scaling)")
|
|
49
51
|
|
|
50
52
|
cog: CogConfig = Field(default_factory=lambda: CogConfig())
|
|
51
53
|
teams: dict[str, CogTeam] = Field(
|
|
@@ -55,6 +57,7 @@ class CvCMission(CoGameMission):
|
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
clips: ClipsConfig = Field(default_factory=lambda: ClipsConfig())
|
|
60
|
+
weather: WeatherConfig = Field(default_factory=lambda: WeatherConfig())
|
|
58
61
|
|
|
59
62
|
@property
|
|
60
63
|
def num_agents(self) -> int:
|
|
@@ -74,7 +77,7 @@ class CvCMission(CoGameMission):
|
|
|
74
77
|
Returns:
|
|
75
78
|
MettaGridConfig ready for environment creation
|
|
76
79
|
"""
|
|
77
|
-
|
|
80
|
+
team_objs = list(self.teams.values())
|
|
78
81
|
game = GameConfig(
|
|
79
82
|
map_builder=self.map_builder(),
|
|
80
83
|
max_steps=self.max_steps,
|
|
@@ -91,24 +94,40 @@ class CvCMission(CoGameMission):
|
|
|
91
94
|
noop=NoopActionConfig(),
|
|
92
95
|
change_vibe=ChangeVibeActionConfig(vibes=CvCConfig.VIBES),
|
|
93
96
|
),
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
],
|
|
96
102
|
objects={
|
|
97
103
|
"wall": CvCWallConfig().station_cfg(),
|
|
98
|
-
"hub": CvCHubConfig().station_cfg(team="cogs"),
|
|
99
104
|
"junction": CvCJunctionConfig().station_cfg(),
|
|
100
|
-
"chest": CvCChestConfig().station_cfg(team="cogs"),
|
|
101
105
|
**{
|
|
102
106
|
f"{resource}_extractor": CvCExtractorConfig(resource=resource).station_cfg()
|
|
103
107
|
for resource in CvCConfig.ELEMENTS
|
|
104
108
|
},
|
|
105
|
-
**{
|
|
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
|
+
},
|
|
106
124
|
},
|
|
107
125
|
collectives={
|
|
108
|
-
**{
|
|
109
|
-
|
|
126
|
+
**{t.name: t.collective_config() for t in team_objs},
|
|
127
|
+
**self.clips.collectives(),
|
|
128
|
+
"neutral": CollectiveConfig(name="neutral"),
|
|
110
129
|
},
|
|
111
|
-
events=self.
|
|
130
|
+
events=self._merge_events(),
|
|
112
131
|
)
|
|
113
132
|
|
|
114
133
|
env = MettaGridConfig(game=game)
|
|
@@ -122,3 +141,12 @@ class CvCMission(CoGameMission):
|
|
|
122
141
|
env.label += f".{variant.name}"
|
|
123
142
|
|
|
124
143
|
return env
|
|
144
|
+
|
|
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}
|
|
@@ -50,7 +50,7 @@ def get_core_missions() -> list[CvCMission]:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def _build_eval_missions() -> list[CvCMission]:
|
|
53
|
-
from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS
|
|
53
|
+
from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS # noqa: PLC0415
|
|
54
54
|
|
|
55
55
|
return [
|
|
56
56
|
*INTEGRATED_EVAL_MISSIONS,
|