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.
- cogames/cli/client.py +12 -0
- cogames/cli/leaderboard.py +40 -0
- cogames/cli/mission.py +31 -34
- cogames/cli/submit.py +1 -1
- cogames/cogs_vs_clips/clips.py +86 -0
- cogames/cogs_vs_clips/cog.py +14 -7
- cogames/cogs_vs_clips/cogsguard_tutorial.py +10 -11
- cogames/cogs_vs_clips/config.py +38 -0
- cogames/cogs_vs_clips/{cogs_vs_clips_mapgen.md → docs/cogs_vs_clips_mapgen.md} +6 -7
- cogames/cogs_vs_clips/evals/README.md +4 -4
- cogames/cogs_vs_clips/evals/cogsguard_evals.py +96 -0
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +13 -100
- cogames/cogs_vs_clips/evals/difficulty_variants.py +9 -18
- cogames/cogs_vs_clips/evals/integrated_evals.py +8 -60
- cogames/cogs_vs_clips/evals/spanning_evals.py +48 -54
- cogames/cogs_vs_clips/mission.py +65 -277
- cogames/cogs_vs_clips/missions.py +16 -24
- cogames/cogs_vs_clips/sites.py +35 -25
- cogames/cogs_vs_clips/stations.py +33 -82
- cogames/cogs_vs_clips/team.py +44 -0
- cogames/cogs_vs_clips/{procedural.py → terrain.py} +12 -6
- cogames/cogs_vs_clips/variants.py +41 -118
- cogames/core.py +87 -0
- cogames/main.py +5 -1
- cogames/maps/evals/eval_balanced_spread.map +7 -3
- cogames/maps/evals/eval_clip_oxygen.map +7 -3
- cogames/maps/evals/eval_collect_resources.map +7 -3
- cogames/maps/evals/eval_collect_resources_hard.map +7 -3
- cogames/maps/evals/eval_collect_resources_medium.map +7 -3
- cogames/maps/evals/eval_divide_and_conquer.map +7 -3
- cogames/maps/evals/eval_energy_starved.map +7 -3
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +7 -3
- cogames/maps/evals/eval_oxygen_bottleneck.map +7 -3
- cogames/maps/evals/eval_single_use_world.map +7 -3
- cogames/maps/evals/extractor_hub_100x100.map +7 -3
- cogames/maps/evals/extractor_hub_30x30.map +7 -3
- cogames/maps/evals/extractor_hub_50x50.map +7 -3
- cogames/maps/evals/extractor_hub_70x70.map +7 -3
- cogames/maps/evals/extractor_hub_80x80.map +7 -3
- cogames/verbose.py +2 -2
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/METADATA +19 -3
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/RECORD +46 -44
- cogames/cogs_vs_clips/cogsguard_reward_variants.py +0 -138
- cogames/cogs_vs_clips/mission_utils.py +0 -19
- cogames/cogs_vs_clips/tutorial_missions.py +0 -25
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/WHEEL +0 -0
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/entry_points.txt +0 -0
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/licenses/LICENSE +0 -0
- {cogames-0.3.59.post1.dev2.dist-info → cogames-0.3.65.dist-info}/top_level.txt +0 -0
cogames/cli/client.py
CHANGED
|
@@ -51,6 +51,8 @@ class PoolInfo(BaseModel):
|
|
|
51
51
|
|
|
52
52
|
class SeasonInfo(BaseModel):
|
|
53
53
|
name: str
|
|
54
|
+
version: int
|
|
55
|
+
canonical: bool
|
|
54
56
|
summary: str
|
|
55
57
|
entry_pool: str | None = None
|
|
56
58
|
leaderboard_pool: str | None = None
|
|
@@ -58,6 +60,13 @@ class SeasonInfo(BaseModel):
|
|
|
58
60
|
pools: list[PoolInfo]
|
|
59
61
|
|
|
60
62
|
|
|
63
|
+
class SeasonVersionInfo(BaseModel):
|
|
64
|
+
version: int
|
|
65
|
+
canonical: bool
|
|
66
|
+
disabled_at: str | None
|
|
67
|
+
created_at: str
|
|
68
|
+
|
|
69
|
+
|
|
61
70
|
class LeaderboardEntry(BaseModel):
|
|
62
71
|
rank: int
|
|
63
72
|
policy: PolicyVersionSummary
|
|
@@ -174,6 +183,9 @@ class TournamentServerClient:
|
|
|
174
183
|
def get_config(self, config_id: str) -> dict[str, Any]:
|
|
175
184
|
return self._get(f"/tournament/configs/{config_id}")
|
|
176
185
|
|
|
186
|
+
def get_season_versions(self, season_name: str) -> list[SeasonVersionInfo]:
|
|
187
|
+
return self._get(f"/tournament/seasons/{season_name}/versions", list[SeasonVersionInfo])
|
|
188
|
+
|
|
177
189
|
def get_leaderboard(self, season_name: str, include_hidden_seasons: bool = False) -> list[LeaderboardEntry]:
|
|
178
190
|
return self._get(
|
|
179
191
|
f"/tournament/seasons/{season_name}/leaderboard",
|
cogames/cli/leaderboard.py
CHANGED
|
@@ -36,6 +36,22 @@ def parse_policy_identifier(identifier: str) -> tuple[str, int | None]:
|
|
|
36
36
|
return identifier, None
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def parse_season_ref(season_ref: str) -> tuple[str, int | None]:
|
|
40
|
+
if ":v" in season_ref:
|
|
41
|
+
name, version_str = season_ref.rsplit(":v", 1)
|
|
42
|
+
try:
|
|
43
|
+
return name, int(version_str)
|
|
44
|
+
except ValueError:
|
|
45
|
+
return season_ref, None
|
|
46
|
+
if ":" in season_ref:
|
|
47
|
+
name, version_str = season_ref.rsplit(":", 1)
|
|
48
|
+
try:
|
|
49
|
+
return name, int(version_str)
|
|
50
|
+
except ValueError:
|
|
51
|
+
return season_ref, None
|
|
52
|
+
return season_ref, None
|
|
53
|
+
|
|
54
|
+
|
|
39
55
|
def _format_timestamp(value: Optional[str]) -> str:
|
|
40
56
|
"""Format ISO timestamps for CLI output."""
|
|
41
57
|
if not value:
|
|
@@ -321,6 +337,8 @@ def leaderboard_cmd(
|
|
|
321
337
|
|
|
322
338
|
|
|
323
339
|
def seasons_cmd(
|
|
340
|
+
season_name: Optional[str] = typer.Argument(None, help="Show versions of a specific season"),
|
|
341
|
+
versions: bool = typer.Option(False, "--versions", "-v", help="List all versions of the season"),
|
|
324
342
|
login_server: str = typer.Option(
|
|
325
343
|
DEFAULT_COGAMES_SERVER,
|
|
326
344
|
"--login-server",
|
|
@@ -358,6 +376,28 @@ def seasons_cmd(
|
|
|
358
376
|
|
|
359
377
|
try:
|
|
360
378
|
with client:
|
|
379
|
+
if season_name and versions:
|
|
380
|
+
season_versions = client.get_season_versions(season_name)
|
|
381
|
+
if json_output:
|
|
382
|
+
console.print(json.dumps([v.model_dump() for v in season_versions], indent=2))
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
if not season_versions:
|
|
386
|
+
console.print(f"[yellow]No versions found for season '{season_name}'.[/yellow]")
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
table = Table(title=f"Versions: {season_name}", box=box.SIMPLE_HEAVY, show_lines=False, pad_edge=False)
|
|
390
|
+
table.add_column("Version", style="bold cyan")
|
|
391
|
+
table.add_column("Status", style="white")
|
|
392
|
+
table.add_column("Created", style="dim")
|
|
393
|
+
|
|
394
|
+
for v in season_versions:
|
|
395
|
+
status = "[green]canonical[/green]" if v.canonical else "[dim]historical[/dim]"
|
|
396
|
+
table.add_row(f"v{v.version}", status, _format_timestamp(v.created_at))
|
|
397
|
+
|
|
398
|
+
console.print(table)
|
|
399
|
+
return
|
|
400
|
+
|
|
361
401
|
seasons = client.get_seasons()
|
|
362
402
|
except httpx.HTTPError as exc:
|
|
363
403
|
console.print(f"[red]Failed to reach server:[/red] {exc}")
|
cogames/cli/mission.py
CHANGED
|
@@ -9,53 +9,48 @@ from rich.table import Table
|
|
|
9
9
|
|
|
10
10
|
from cogames.cli.base import console
|
|
11
11
|
from cogames.cogs_vs_clips.mission import (
|
|
12
|
-
|
|
13
|
-
AnyMission,
|
|
14
|
-
Mission,
|
|
15
|
-
MissionVariant,
|
|
12
|
+
CvCMission,
|
|
16
13
|
NumCogsVariant,
|
|
17
|
-
Site,
|
|
18
14
|
)
|
|
19
|
-
from cogames.cogs_vs_clips.procedural import MachinaArena
|
|
20
15
|
from cogames.cogs_vs_clips.sites import SITES
|
|
16
|
+
from cogames.cogs_vs_clips.terrain import MachinaArena
|
|
21
17
|
from cogames.cogs_vs_clips.variants import HIDDEN_VARIANTS, VARIANTS
|
|
18
|
+
from cogames.core import (
|
|
19
|
+
MAP_MISSION_DELIMITER,
|
|
20
|
+
CoGameMissionVariant,
|
|
21
|
+
CoGameSite,
|
|
22
|
+
)
|
|
22
23
|
from cogames.game import load_mission_config, load_mission_config_from_python
|
|
23
24
|
from mettagrid import MettaGridConfig
|
|
24
25
|
from mettagrid.mapgen.mapgen import MapGen
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@lru_cache(maxsize=1)
|
|
28
|
-
def _get_core_missions() -> list[
|
|
29
|
+
def _get_core_missions() -> list[CvCMission]:
|
|
29
30
|
from cogames.cogs_vs_clips.missions import get_core_missions
|
|
30
31
|
|
|
31
32
|
return get_core_missions()
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@lru_cache(maxsize=1)
|
|
35
|
-
def
|
|
36
|
-
from cogames.cogs_vs_clips.missions import get_legacy_missions
|
|
37
|
-
|
|
38
|
-
return get_legacy_missions()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@lru_cache(maxsize=1)
|
|
42
|
-
def _get_eval_missions_all() -> list[Mission]:
|
|
36
|
+
def _get_eval_missions_all() -> list[CvCMission]:
|
|
43
37
|
from cogames.cogs_vs_clips.evals.diagnostic_evals import DIAGNOSTIC_EVALS
|
|
44
38
|
from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS
|
|
45
39
|
from cogames.cogs_vs_clips.evals.spanning_evals import EVAL_MISSIONS as SPANNING_EVAL_MISSIONS
|
|
46
40
|
|
|
47
|
-
missions: list[
|
|
41
|
+
missions: list[CvCMission] = []
|
|
48
42
|
missions.extend(INTEGRATED_EVAL_MISSIONS)
|
|
49
43
|
missions.extend(SPANNING_EVAL_MISSIONS)
|
|
50
44
|
missions.extend(mission_cls() for mission_cls in DIAGNOSTIC_EVALS) # type: ignore[call-arg]
|
|
51
45
|
return missions
|
|
52
46
|
|
|
53
47
|
|
|
54
|
-
def load_mission_set(mission_set: str) -> list[
|
|
48
|
+
def load_mission_set(mission_set: str) -> list[CvCMission]:
|
|
55
49
|
"""Load a predefined set of evaluation missions.
|
|
56
50
|
|
|
57
51
|
Args:
|
|
58
52
|
mission_set: Name of mission set to load. Options:
|
|
53
|
+
- "cogsguard_evals": CogsGuard evaluation missions (map-based)
|
|
59
54
|
- "integrated_evals": Integrated evaluation missions
|
|
60
55
|
- "spanning_evals": Spanning evaluation missions
|
|
61
56
|
- "diagnostic_evals": Diagnostic evaluation missions
|
|
@@ -67,7 +62,7 @@ def load_mission_set(mission_set: str) -> list[AnyMission]:
|
|
|
67
62
|
Raises:
|
|
68
63
|
ValueError: If mission_set name is unknown
|
|
69
64
|
"""
|
|
70
|
-
missions_list: list[
|
|
65
|
+
missions_list: list[CvCMission]
|
|
71
66
|
if mission_set == "all":
|
|
72
67
|
# All missions: eval missions + integrated + spanning + diagnostic + core missions
|
|
73
68
|
missions_list = list(_get_eval_missions_all())
|
|
@@ -82,6 +77,10 @@ def load_mission_set(mission_set: str) -> list[AnyMission]:
|
|
|
82
77
|
from cogames.cogs_vs_clips.evals.diagnostic_evals import DIAGNOSTIC_EVALS
|
|
83
78
|
|
|
84
79
|
missions_list = [mission_cls() for mission_cls in DIAGNOSTIC_EVALS] # type: ignore[call-arg]
|
|
80
|
+
elif mission_set == "cogsguard_evals":
|
|
81
|
+
from cogames.cogs_vs_clips.evals.cogsguard_evals import COGSGUARD_EVAL_MISSIONS
|
|
82
|
+
|
|
83
|
+
missions_list = list(COGSGUARD_EVAL_MISSIONS)
|
|
85
84
|
elif mission_set == "integrated_evals":
|
|
86
85
|
from cogames.cogs_vs_clips.evals.integrated_evals import EVAL_MISSIONS as INTEGRATED_EVAL_MISSIONS
|
|
87
86
|
|
|
@@ -91,20 +90,20 @@ def load_mission_set(mission_set: str) -> list[AnyMission]:
|
|
|
91
90
|
|
|
92
91
|
missions_list = list(SPANNING_EVAL_MISSIONS)
|
|
93
92
|
else:
|
|
94
|
-
available = "integrated_evals, spanning_evals, diagnostic_evals, all"
|
|
93
|
+
available = "cogsguard_evals, integrated_evals, spanning_evals, diagnostic_evals, all"
|
|
95
94
|
raise ValueError(f"Unknown mission set: {mission_set}\nAvailable sets: {available}")
|
|
96
95
|
|
|
97
96
|
return missions_list
|
|
98
97
|
|
|
99
98
|
|
|
100
|
-
def parse_variants(variants_arg: Optional[list[str]]) -> list[
|
|
99
|
+
def parse_variants(variants_arg: Optional[list[str]]) -> list[CoGameMissionVariant]:
|
|
101
100
|
"""Parse variant specifications from command line.
|
|
102
101
|
|
|
103
102
|
Args:
|
|
104
103
|
variants_arg: List of variant names like ["solar_flare", "dark_side"]
|
|
105
104
|
|
|
106
105
|
Returns:
|
|
107
|
-
List of configured
|
|
106
|
+
List of configured CoGameMissionVariant instances
|
|
108
107
|
|
|
109
108
|
Raises:
|
|
110
109
|
ValueError: If variant name is unknown
|
|
@@ -112,11 +111,11 @@ def parse_variants(variants_arg: Optional[list[str]]) -> list[MissionVariant]:
|
|
|
112
111
|
if not variants_arg:
|
|
113
112
|
return []
|
|
114
113
|
|
|
115
|
-
variants: list[
|
|
114
|
+
variants: list[CoGameMissionVariant] = []
|
|
116
115
|
all_variants = [*VARIANTS, *HIDDEN_VARIANTS]
|
|
117
116
|
for name in variants_arg:
|
|
118
117
|
# Find matching variant class by instantiating and checking the name
|
|
119
|
-
variant:
|
|
118
|
+
variant: CoGameMissionVariant | None = None
|
|
120
119
|
for v in all_variants:
|
|
121
120
|
if v.name == name:
|
|
122
121
|
variant = v
|
|
@@ -143,7 +142,7 @@ def get_all_eval_missions() -> list[str]:
|
|
|
143
142
|
return [mission.full_name() for mission in _get_eval_missions_all()]
|
|
144
143
|
|
|
145
144
|
|
|
146
|
-
def get_site_by_name(site_name: str) ->
|
|
145
|
+
def get_site_by_name(site_name: str) -> CoGameSite:
|
|
147
146
|
"""Get a site by name.
|
|
148
147
|
|
|
149
148
|
Raises:
|
|
@@ -159,7 +158,7 @@ def get_site_by_name(site_name: str) -> Site:
|
|
|
159
158
|
|
|
160
159
|
def get_mission_name_and_config(
|
|
161
160
|
ctx: typer.Context, mission_arg: Optional[str], variants_arg: Optional[list[str]] = None, cogs: Optional[int] = None
|
|
162
|
-
) -> tuple[str, MettaGridConfig, Optional[
|
|
161
|
+
) -> tuple[str, MettaGridConfig, Optional[CvCMission]]:
|
|
163
162
|
if not mission_arg:
|
|
164
163
|
console.print(ctx.get_help())
|
|
165
164
|
console.print("[yellow]Missing: --mission / -m[/yellow]\n")
|
|
@@ -246,12 +245,10 @@ def find_mission(
|
|
|
246
245
|
*,
|
|
247
246
|
include_evals: bool = False,
|
|
248
247
|
include_legacy: bool = False,
|
|
249
|
-
) ->
|
|
250
|
-
missions: list[
|
|
248
|
+
) -> CvCMission:
|
|
249
|
+
missions: list[CvCMission] = list(_get_core_missions())
|
|
251
250
|
if include_evals:
|
|
252
251
|
missions = [*missions, *_get_eval_missions_all()]
|
|
253
|
-
if include_legacy:
|
|
254
|
-
missions = [*missions, *_get_legacy_missions()]
|
|
255
252
|
|
|
256
253
|
found_site = False
|
|
257
254
|
for mission in missions:
|
|
@@ -281,7 +278,7 @@ def get_mission(
|
|
|
281
278
|
variants_arg: Optional[list[str]] = None,
|
|
282
279
|
cogs: Optional[int] = None,
|
|
283
280
|
include_legacy: bool = False,
|
|
284
|
-
) -> tuple[str, MettaGridConfig, Optional[
|
|
281
|
+
) -> tuple[str, MettaGridConfig, Optional[CvCMission]]:
|
|
285
282
|
"""Get a specific mission configuration by name or file path.
|
|
286
283
|
|
|
287
284
|
Args:
|
|
@@ -291,7 +288,7 @@ def get_mission(
|
|
|
291
288
|
include_legacy: Whether to include legacy (pre-CogsGuard) missions
|
|
292
289
|
|
|
293
290
|
Returns:
|
|
294
|
-
Tuple of (mission name, MettaGridConfig,
|
|
291
|
+
Tuple of (mission name, MettaGridConfig, CvCMission or None)
|
|
295
292
|
|
|
296
293
|
Raises:
|
|
297
294
|
ValueError: If mission not found or file cannot be loaded
|
|
@@ -323,7 +320,7 @@ def get_mission(
|
|
|
323
320
|
else:
|
|
324
321
|
site_name, mission_name = mission_arg.split(MAP_MISSION_DELIMITER)
|
|
325
322
|
|
|
326
|
-
mission:
|
|
323
|
+
mission: CvCMission = find_mission(site_name, mission_name, include_evals=True, include_legacy=include_legacy)
|
|
327
324
|
|
|
328
325
|
if variants:
|
|
329
326
|
mission = mission.with_variants(variants)
|
|
@@ -447,7 +444,7 @@ def list_evals() -> None:
|
|
|
447
444
|
return
|
|
448
445
|
|
|
449
446
|
# Group missions by site
|
|
450
|
-
missions_by_site: dict[str, list[
|
|
447
|
+
missions_by_site: dict[str, list[CvCMission]] = {}
|
|
451
448
|
for m in evals:
|
|
452
449
|
missions_by_site.setdefault(m.site.name, []).append(m)
|
|
453
450
|
|
|
@@ -499,7 +496,7 @@ def list_evals() -> None:
|
|
|
499
496
|
console.print(" [bold]cogames play[/bold] --mission [blue]evals.divide_and_conquer[/blue]")
|
|
500
497
|
|
|
501
498
|
|
|
502
|
-
def describe_mission(mission_name: str, game_config: MettaGridConfig, mission_cfg:
|
|
499
|
+
def describe_mission(mission_name: str, game_config: MettaGridConfig, mission_cfg: CvCMission | None = None) -> None:
|
|
503
500
|
"""Print detailed information about a specific mission.
|
|
504
501
|
|
|
505
502
|
Args:
|
cogames/cli/submit.py
CHANGED
|
@@ -290,6 +290,7 @@ def validate_policy_in_isolation(
|
|
|
290
290
|
"run",
|
|
291
291
|
"cogames",
|
|
292
292
|
"validate-policy",
|
|
293
|
+
"--policy",
|
|
293
294
|
policy_arg,
|
|
294
295
|
"--season",
|
|
295
296
|
season,
|
|
@@ -298,7 +299,6 @@ def validate_policy_in_isolation(
|
|
|
298
299
|
]
|
|
299
300
|
if setup_script:
|
|
300
301
|
validate_cmd.extend(["--setup-script", setup_script])
|
|
301
|
-
validate_cmd.extend(["--policy", policy_arg])
|
|
302
302
|
|
|
303
303
|
_run_from_tmp_dir(validate_cmd)
|
|
304
304
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Clips behavior events for CogsGuard missions.
|
|
2
|
+
|
|
3
|
+
Clips are a non-player faction that gradually takes over neutral junctions.
|
|
4
|
+
These events create the spreading/scrambling behavior that pressures players.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from mettagrid.base_config import Config
|
|
10
|
+
from mettagrid.config.event_config import EventConfig, once, periodic
|
|
11
|
+
from mettagrid.config.filter import isAlignedTo, isNear
|
|
12
|
+
from mettagrid.config.filter.alignment_filter import isNeutral, isNotAlignedTo, isNotNeutral
|
|
13
|
+
from mettagrid.config.mettagrid_config import CollectiveConfig
|
|
14
|
+
from mettagrid.config.mutation import alignTo, removeAlignment
|
|
15
|
+
from mettagrid.config.tag import typeTag
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClipsConfig(Config):
|
|
19
|
+
"""Configuration for clips behavior in CogsGuard game mode."""
|
|
20
|
+
|
|
21
|
+
# Clips Behavior - scramble cogs junctions to neutral
|
|
22
|
+
initial_clips_start: int = Field(default=10)
|
|
23
|
+
initial_clips_spots: int = Field(default=1)
|
|
24
|
+
|
|
25
|
+
scramble_start: int = Field(default=50)
|
|
26
|
+
scramble_interval: int = Field(default=100)
|
|
27
|
+
scramble_radius: int = Field(default=25)
|
|
28
|
+
|
|
29
|
+
# Clips Behavior - align neutral junctions to clips
|
|
30
|
+
align_start: int = Field(default=100)
|
|
31
|
+
align_interval: int = Field(default=100)
|
|
32
|
+
align_radius: int = Field(default=25)
|
|
33
|
+
|
|
34
|
+
def events(self, max_steps: int) -> dict[str, EventConfig]:
|
|
35
|
+
"""Create all clips events for a mission.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary of event name to EventConfig.
|
|
39
|
+
"""
|
|
40
|
+
return {
|
|
41
|
+
"initial_clips": EventConfig(
|
|
42
|
+
name="initial_clips",
|
|
43
|
+
target_tag=typeTag("junction"),
|
|
44
|
+
timesteps=once(self.initial_clips_start),
|
|
45
|
+
mutations=[alignTo("clips")],
|
|
46
|
+
max_targets=self.initial_clips_spots,
|
|
47
|
+
),
|
|
48
|
+
"cogs_to_neutral": EventConfig(
|
|
49
|
+
name="cogs_to_neutral",
|
|
50
|
+
target_tag=typeTag("junction"),
|
|
51
|
+
timesteps=periodic(start=self.scramble_start, period=self.scramble_interval, end=max_steps),
|
|
52
|
+
# near a clips-aligned junction
|
|
53
|
+
filters=[
|
|
54
|
+
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.scramble_radius),
|
|
55
|
+
isNotAlignedTo("clips"),
|
|
56
|
+
isNotNeutral(),
|
|
57
|
+
],
|
|
58
|
+
mutations=[removeAlignment()],
|
|
59
|
+
max_targets=1,
|
|
60
|
+
),
|
|
61
|
+
"neutral_to_clips": EventConfig(
|
|
62
|
+
name="neutral_to_clips",
|
|
63
|
+
target_tag=typeTag("junction"),
|
|
64
|
+
timesteps=periodic(start=self.align_start, period=self.align_interval, end=max_steps),
|
|
65
|
+
# neutral junctions near a clips-aligned junction
|
|
66
|
+
filters=[
|
|
67
|
+
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.align_radius),
|
|
68
|
+
isNeutral(),
|
|
69
|
+
],
|
|
70
|
+
mutations=[alignTo("clips")],
|
|
71
|
+
max_targets=1,
|
|
72
|
+
),
|
|
73
|
+
# If there are no clips-aligned junctions, re-invade
|
|
74
|
+
"presence_check": EventConfig(
|
|
75
|
+
name="presence_check",
|
|
76
|
+
target_tag=typeTag("junction"),
|
|
77
|
+
timesteps=periodic(start=self.initial_clips_start, period=self.scramble_interval * 2, end=max_steps),
|
|
78
|
+
filters=[isNear(typeTag("junction"), [isAlignedTo("clips")], radius=1000)],
|
|
79
|
+
max_targets=1,
|
|
80
|
+
fallback="initial_clips",
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def collective_config(self) -> CollectiveConfig:
|
|
85
|
+
"""Create a CollectiveConfig for this clips configuration."""
|
|
86
|
+
return CollectiveConfig(name="clips")
|
cogames/cogs_vs_clips/cog.py
CHANGED
|
@@ -2,7 +2,9 @@ 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 stat as game_stat
|
|
6
8
|
from mettagrid.config.handler_config import Handler
|
|
7
9
|
from mettagrid.config.mettagrid_config import (
|
|
8
10
|
AgentConfig,
|
|
@@ -10,6 +12,7 @@ from mettagrid.config.mettagrid_config import (
|
|
|
10
12
|
ResourceLimitsConfig,
|
|
11
13
|
)
|
|
12
14
|
from mettagrid.config.mutation.resource_mutation import updateActor
|
|
15
|
+
from mettagrid.config.reward_config import reward
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class CogConfig(Config):
|
|
@@ -37,25 +40,23 @@ class CogConfig(Config):
|
|
|
37
40
|
energy_regen: int = Field(default=1)
|
|
38
41
|
hp_regen: int = Field(default=-1)
|
|
39
42
|
influence_regen: int = Field(default=-1)
|
|
43
|
+
action_cost: dict[str, int] = Field(default_factory=lambda: {"energy": 3})
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
move_energy_cost: int = Field(default=3)
|
|
43
|
-
|
|
44
|
-
def agent_config(self, gear: list[str], elements: list[str]) -> AgentConfig:
|
|
45
|
+
def agent_config(self, team: str, max_steps: int) -> AgentConfig:
|
|
45
46
|
"""Create an AgentConfig for this cog configuration."""
|
|
46
47
|
return AgentConfig(
|
|
47
|
-
collective=
|
|
48
|
+
collective=team,
|
|
48
49
|
inventory=InventoryConfig(
|
|
49
50
|
limits={
|
|
50
51
|
"hp": ResourceLimitsConfig(min=self.hp_limit, resources=["hp"], modifiers=self.hp_modifiers),
|
|
51
52
|
# when hp == 0, the cog can't hold gear or hearts
|
|
52
|
-
"gear": ResourceLimitsConfig(max=self.gear_limit, resources=
|
|
53
|
+
"gear": ResourceLimitsConfig(max=self.gear_limit, resources=CvCConfig.GEAR, modifiers={"hp": 100}),
|
|
53
54
|
"heart": ResourceLimitsConfig(max=self.heart_limit, resources=["heart"], modifiers={"hp": 100}),
|
|
54
55
|
"energy": ResourceLimitsConfig(
|
|
55
56
|
min=self.energy_limit, resources=["energy"], modifiers=self.energy_modifiers
|
|
56
57
|
),
|
|
57
58
|
"cargo": ResourceLimitsConfig(
|
|
58
|
-
min=self.cargo_limit, resources=
|
|
59
|
+
min=self.cargo_limit, resources=CvCConfig.ELEMENTS, modifiers=self.cargo_modifiers
|
|
59
60
|
),
|
|
60
61
|
"influence": ResourceLimitsConfig(
|
|
61
62
|
min=self.influence_limit, resources=["influence"], modifiers=self.influence_modifiers
|
|
@@ -76,4 +77,10 @@ class CogConfig(Config):
|
|
|
76
77
|
]
|
|
77
78
|
)
|
|
78
79
|
},
|
|
80
|
+
rewards={
|
|
81
|
+
"aligned_junction_held": reward(
|
|
82
|
+
game_stat("collective.aligned.junction.held"),
|
|
83
|
+
weight=1.0 / max_steps,
|
|
84
|
+
),
|
|
85
|
+
},
|
|
79
86
|
)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""CogsGuard tutorial mission configuration."""
|
|
2
2
|
|
|
3
|
-
from cogames.cogs_vs_clips.mission import
|
|
4
|
-
from cogames.cogs_vs_clips.
|
|
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() ->
|
|
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",
|
|
@@ -32,7 +34,7 @@ def make_cogsguard_tutorial_site() -> Site:
|
|
|
32
34
|
hub=hub_config,
|
|
33
35
|
),
|
|
34
36
|
)
|
|
35
|
-
return
|
|
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 =
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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", *_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,9 +333,9 @@ 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`, `
|
|
338
|
-
- quadrant_buildings: `EmptyBaseVariant`, `QuadrantBuildingsVariant
|
|
339
|
-
- single_use_swarm: `EmptyBaseVariant`, `SingleUseSwarmVariant`, `
|
|
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:
|
|
@@ -349,6 +348,6 @@ uv run python packages/cogames/scripts/run_evaluation.py \
|
|
|
349
348
|
--repeats 2
|
|
350
349
|
```
|
|
351
350
|
|
|
352
|
-
Recommendation: When designing new scorable baselines, combine one
|
|
353
|
-
`
|
|
354
|
-
`
|
|
351
|
+
Recommendation: When designing new scorable baselines, combine one "shaping" variant (e.g., `HeartChorusVariant`,
|
|
352
|
+
`PackRatVariant`) with one "constraint" variant (e.g., `DarkSideVariant`, `ResourceBottleneckVariant`,
|
|
353
|
+
`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
|
|
183
|
-
uv run cogames play --mission hello_world.oxygen_bottleneck --cogs 2 --variant
|
|
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
|
|
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
|
|
@@ -230,7 +230,7 @@ mission_variant_curriculum.train(
|
|
|
230
230
|
mission_variant_curriculum.train(
|
|
231
231
|
base_missions=["oxygen_bottleneck", "energy_starved"],
|
|
232
232
|
num_cogs=4,
|
|
233
|
-
variants=["
|
|
233
|
+
variants=["pack_rat", "energized"]
|
|
234
234
|
)
|
|
235
235
|
```
|
|
236
236
|
|