cogames 0.3.49__py3-none-any.whl → 0.3.64__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 +60 -6
- cogames/cli/docsync/__init__.py +0 -0
- cogames/cli/docsync/_nb_md_directive_processing.py +180 -0
- cogames/cli/docsync/_nb_md_sync.py +103 -0
- cogames/cli/docsync/_nb_py_sync.py +122 -0
- cogames/cli/docsync/_three_way_sync.py +115 -0
- cogames/cli/docsync/_utils.py +76 -0
- cogames/cli/docsync/docsync.py +156 -0
- cogames/cli/leaderboard.py +112 -28
- cogames/cli/mission.py +64 -53
- cogames/cli/policy.py +46 -10
- cogames/cli/submit.py +268 -67
- cogames/cogs_vs_clips/cog.py +79 -0
- cogames/cogs_vs_clips/cogs_vs_clips_mapgen.md +19 -16
- cogames/cogs_vs_clips/cogsguard_reward_variants.py +153 -0
- cogames/cogs_vs_clips/cogsguard_tutorial.py +56 -0
- cogames/cogs_vs_clips/evals/README.md +10 -16
- cogames/cogs_vs_clips/evals/cogsguard_evals.py +81 -0
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +49 -444
- cogames/cogs_vs_clips/evals/difficulty_variants.py +13 -326
- cogames/cogs_vs_clips/evals/integrated_evals.py +5 -45
- cogames/cogs_vs_clips/evals/spanning_evals.py +9 -180
- cogames/cogs_vs_clips/mission.py +187 -146
- cogames/cogs_vs_clips/missions.py +46 -137
- cogames/cogs_vs_clips/procedural.py +8 -8
- cogames/cogs_vs_clips/sites.py +107 -3
- cogames/cogs_vs_clips/stations.py +198 -186
- cogames/cogs_vs_clips/tutorial_missions.py +1 -1
- cogames/cogs_vs_clips/variants.py +25 -476
- cogames/device.py +13 -1
- cogames/{policy/scripted_agent/README.md → docs/SCRIPTED_AGENT.md} +82 -58
- cogames/evaluate.py +18 -30
- cogames/main.py +1434 -243
- 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 +2 -2
- 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 +2 -2
- cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_near.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_search.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_memory.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +2 -2
- 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 +2 -2
- cogames/maps/diagnostic_evals/diagnostic_unclip.map +2 -2
- cogames/maps/evals/eval_balanced_spread.map +9 -5
- cogames/maps/evals/eval_clip_oxygen.map +9 -5
- cogames/maps/evals/eval_collect_resources.map +9 -5
- cogames/maps/evals/eval_collect_resources_hard.map +9 -5
- cogames/maps/evals/eval_collect_resources_medium.map +9 -5
- cogames/maps/evals/eval_divide_and_conquer.map +9 -5
- cogames/maps/evals/eval_energy_starved.map +9 -5
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +9 -5
- cogames/maps/evals/eval_oxygen_bottleneck.map +9 -5
- cogames/maps/evals/eval_single_use_world.map +9 -5
- cogames/maps/evals/extractor_hub_100x100.map +9 -5
- cogames/maps/evals/extractor_hub_30x30.map +9 -5
- cogames/maps/evals/extractor_hub_50x50.map +9 -5
- cogames/maps/evals/extractor_hub_70x70.map +9 -5
- cogames/maps/evals/extractor_hub_80x80.map +9 -5
- 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 +28 -0
- cogames/maps/planky_evals/aligner_full_cycle.map +28 -0
- cogames/maps/planky_evals/aligner_gear.map +24 -0
- cogames/maps/planky_evals/aligner_hearts.map +24 -0
- cogames/maps/planky_evals/aligner_junction.map +26 -0
- cogames/maps/planky_evals/exploration_distant.map +28 -0
- cogames/maps/planky_evals/maze.map +32 -0
- cogames/maps/planky_evals/miner_best_resource.map +26 -0
- cogames/maps/planky_evals/miner_deposit.map +24 -0
- cogames/maps/planky_evals/miner_extract.map +26 -0
- cogames/maps/planky_evals/miner_full_cycle.map +28 -0
- cogames/maps/planky_evals/miner_gear.map +24 -0
- cogames/maps/planky_evals/multi_role.map +28 -0
- cogames/maps/planky_evals/resource_chain.map +30 -0
- cogames/maps/planky_evals/scout_explore.map +32 -0
- cogames/maps/planky_evals/scout_gear.map +24 -0
- cogames/maps/planky_evals/scrambler_full_cycle.map +28 -0
- cogames/maps/planky_evals/scrambler_gear.map +24 -0
- cogames/maps/planky_evals/scrambler_target.map +26 -0
- cogames/maps/planky_evals/stuck_corridor.map +32 -0
- cogames/maps/planky_evals/survive_retreat.map +26 -0
- 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 +183 -0
- cogames/play.py +166 -33
- cogames/policy/chaos_monkey.py +54 -0
- cogames/policy/nim_agents/__init__.py +27 -10
- cogames/policy/nim_agents/agents.py +121 -60
- cogames/policy/nim_agents/thinky_eval.py +35 -222
- cogames/policy/pufferlib_policy.py +67 -32
- cogames/policy/starter_agent.py +184 -0
- cogames/policy/trainable_policy_template.py +4 -1
- cogames/train.py +51 -13
- cogames/verbose.py +2 -2
- cogames-0.3.64.dist-info/METADATA +1842 -0
- cogames-0.3.64.dist-info/RECORD +159 -0
- cogames-0.3.64.dist-info/licenses/LICENSE +21 -0
- cogames-0.3.64.dist-info/top_level.txt +2 -0
- metta_alo/__init__.py +0 -0
- metta_alo/job_specs.py +17 -0
- metta_alo/policy.py +16 -0
- metta_alo/pure_single_episode_runner.py +75 -0
- metta_alo/py.typed +0 -0
- metta_alo/rollout.py +322 -0
- metta_alo/scoring.py +168 -0
- cogames/maps/diagnostic_evals/diagnostic_assembler_near.map +0 -49
- cogames/maps/diagnostic_evals/diagnostic_assembler_search.map +0 -49
- cogames/maps/diagnostic_evals/diagnostic_assembler_search_hard.map +0 -89
- cogames/policy/nim_agents/common.nim +0 -887
- cogames/policy/nim_agents/install.sh +0 -1
- cogames/policy/nim_agents/ladybug_agent.nim +0 -984
- cogames/policy/nim_agents/nim_agents.nim +0 -55
- cogames/policy/nim_agents/nim_agents.nims +0 -14
- cogames/policy/nim_agents/nimby.lock +0 -3
- cogames/policy/nim_agents/racecar_agents.nim +0 -884
- cogames/policy/nim_agents/random_agents.nim +0 -68
- cogames/policy/nim_agents/test_agents.py +0 -53
- cogames/policy/nim_agents/thinky_agents.nim +0 -717
- cogames/policy/scripted_agent/baseline_agent.py +0 -1049
- cogames/policy/scripted_agent/demo_policy.py +0 -244
- cogames/policy/scripted_agent/pathfinding.py +0 -126
- cogames/policy/scripted_agent/starter_agent.py +0 -136
- cogames/policy/scripted_agent/types.py +0 -235
- cogames/policy/scripted_agent/unclipping_agent.py +0 -476
- cogames/policy/scripted_agent/utils.py +0 -385
- cogames-0.3.49.dist-info/METADATA +0 -406
- cogames-0.3.49.dist-info/RECORD +0 -136
- cogames-0.3.49.dist-info/top_level.txt +0 -1
- {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/WHEEL +0 -0
- {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/entry_points.txt +0 -0
cogames/cli/submit.py
CHANGED
|
@@ -19,48 +19,43 @@ from rich.console import Console
|
|
|
19
19
|
from cogames.cli.base import console
|
|
20
20
|
from cogames.cli.login import DEFAULT_COGAMES_SERVER
|
|
21
21
|
from cogames.cli.policy import PolicySpec, get_policy_spec
|
|
22
|
+
from metta_alo.rollout import run_single_episode
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
25
|
from cogames.cli.client import TournamentServerClient
|
|
25
26
|
|
|
26
|
-
from mettagrid.
|
|
27
|
-
from mettagrid.policy.policy_env_interface import PolicyEnvInterface
|
|
27
|
+
from mettagrid.config.mettagrid_config import MettaGridConfig
|
|
28
28
|
from mettagrid.policy.submission import POLICY_SPEC_FILENAME, SubmissionPolicySpec
|
|
29
29
|
|
|
30
30
|
DEFAULT_SUBMIT_SERVER = "https://api.observatory.softmax-research.net"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def results_url_for_season(_server: str, _season: str) -> str:
|
|
34
|
+
return "https://www.softmax.com/alignmentleague"
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
@dataclass
|
|
34
38
|
class UploadResult:
|
|
35
39
|
policy_version_id: uuid.UUID
|
|
36
40
|
name: str
|
|
37
41
|
version: int
|
|
42
|
+
pools: list[str] | None = None
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
def validate_paths(paths: list[str], console: Console) -> list[Path]:
|
|
41
|
-
"""Validate
|
|
42
|
-
|
|
43
|
-
Ensures paths don't escape the current working directory using '../'.
|
|
44
|
-
Returns list of resolved Path objects.
|
|
45
|
-
"""
|
|
46
|
-
cwd = Path.cwd()
|
|
46
|
+
"""Validate paths are within CWD and return them as relative paths."""
|
|
47
|
+
cwd = Path.cwd().resolve()
|
|
47
48
|
validated_paths = []
|
|
48
49
|
|
|
49
50
|
for path_str in paths:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Check if path is absolute
|
|
53
|
-
if path.is_absolute():
|
|
54
|
-
console.print(f"[red]Error:[/red] Path must be relative: {path_str}")
|
|
55
|
-
raise ValueError(f"Absolute paths not allowed: {path_str}")
|
|
51
|
+
raw_path = Path(path_str).expanduser()
|
|
56
52
|
|
|
57
53
|
# Resolve the path and check it's within CWD
|
|
58
54
|
try:
|
|
59
|
-
resolved = (cwd /
|
|
60
|
-
|
|
61
|
-
resolved.relative_to(cwd)
|
|
55
|
+
resolved = raw_path.resolve() if raw_path.is_absolute() else (cwd / raw_path).resolve()
|
|
56
|
+
relative = resolved.relative_to(cwd)
|
|
62
57
|
except ValueError:
|
|
63
|
-
console.print(f"[red]Error:[/red] Path
|
|
58
|
+
console.print(f"[red]Error:[/red] Path must be within the current directory: {path_str}")
|
|
64
59
|
raise ValueError(f"Path escapes CWD: {path_str}") from None
|
|
65
60
|
|
|
66
61
|
# Check if path exists
|
|
@@ -68,32 +63,130 @@ def validate_paths(paths: list[str], console: Console) -> list[Path]:
|
|
|
68
63
|
console.print(f"[red]Error:[/red] Path does not exist: {path_str}")
|
|
69
64
|
raise FileNotFoundError(f"Path not found: {path_str}")
|
|
70
65
|
|
|
71
|
-
validated_paths.append(
|
|
66
|
+
validated_paths.append(relative)
|
|
72
67
|
|
|
73
68
|
return validated_paths
|
|
74
69
|
|
|
75
70
|
|
|
76
|
-
def
|
|
77
|
-
"""
|
|
78
|
-
|
|
71
|
+
def _maybe_resolve_checkpoint_bundle_uri(policy: str) -> tuple[Path, bool] | None:
|
|
72
|
+
"""Return (local_zip_path, cleanup) if policy points to a checkpoint bundle URI."""
|
|
73
|
+
from mettagrid.policy.prepare_policy_spec import download_policy_spec_from_s3_as_zip
|
|
74
|
+
from mettagrid.util.uri_resolvers.schemes import parse_uri, resolve_uri
|
|
75
|
+
|
|
76
|
+
first = policy.split(",", 1)[0].strip()
|
|
77
|
+
parsed = parse_uri(first, allow_none=True, default_scheme=None)
|
|
78
|
+
if parsed is None or parsed.scheme not in {"file", "s3"}:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
resolved = resolve_uri(first)
|
|
82
|
+
if resolved.scheme == "s3":
|
|
83
|
+
if not resolved.canonical.endswith(".zip"):
|
|
84
|
+
raise ValueError("S3 policy specs must be .zip bundles.")
|
|
85
|
+
return download_policy_spec_from_s3_as_zip(resolved.canonical), False
|
|
86
|
+
|
|
87
|
+
local_path = resolved.local_path
|
|
88
|
+
if local_path is None:
|
|
89
|
+
raise ValueError(f"Cannot resolve local path for URI: {policy}")
|
|
90
|
+
if not local_path.exists():
|
|
91
|
+
raise FileNotFoundError(f"Bundle path not found: {local_path}")
|
|
92
|
+
if local_path.is_dir():
|
|
93
|
+
return _zip_directory_bundle(local_path), True
|
|
94
|
+
if local_path.suffix == ".zip":
|
|
95
|
+
return local_path, False
|
|
96
|
+
raise ValueError("Checkpoint bundle must be a directory or .zip file.")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _zip_directory_bundle(bundle_dir: Path) -> Path:
|
|
100
|
+
zip_fd, zip_path = tempfile.mkstemp(suffix=".zip", prefix="cogames_bundle_")
|
|
101
|
+
os.close(zip_fd)
|
|
102
|
+
|
|
103
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
104
|
+
for file_path in bundle_dir.rglob("*"):
|
|
105
|
+
if file_path.is_file():
|
|
106
|
+
zipf.write(file_path, arcname=file_path.relative_to(bundle_dir))
|
|
107
|
+
|
|
108
|
+
return Path(zip_path)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_bundle_in_isolation(policy_zip: Path, console: Console, *, season: str, server: str) -> bool:
|
|
112
|
+
console.print("[dim]Testing policy bundle can run 10 steps...[/dim]")
|
|
113
|
+
|
|
114
|
+
temp_dir = None
|
|
115
|
+
try:
|
|
116
|
+
temp_dir = create_temp_validation_env()
|
|
117
|
+
bundle_name = policy_zip.name
|
|
118
|
+
shutil.copy2(policy_zip, temp_dir / bundle_name)
|
|
119
|
+
|
|
120
|
+
env = os.environ.copy()
|
|
121
|
+
env["UV_NO_CACHE"] = "1"
|
|
122
|
+
|
|
123
|
+
res = subprocess.run(
|
|
124
|
+
[
|
|
125
|
+
"uv",
|
|
126
|
+
"run",
|
|
127
|
+
"cogames",
|
|
128
|
+
"validate-policy",
|
|
129
|
+
"--season",
|
|
130
|
+
season,
|
|
131
|
+
"--server",
|
|
132
|
+
server,
|
|
133
|
+
"--policy",
|
|
134
|
+
f"file://./{bundle_name}",
|
|
135
|
+
],
|
|
136
|
+
cwd=temp_dir,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
timeout=300,
|
|
140
|
+
env=env,
|
|
141
|
+
)
|
|
142
|
+
if res.returncode != 0:
|
|
143
|
+
console.print("[red]Validation failed[/red]")
|
|
144
|
+
console.print(f"\n[red]Error:[/red]\n{res.stderr}")
|
|
145
|
+
if res.stdout:
|
|
146
|
+
console.print(f"\n[dim]Output:[/dim]\n{res.stdout}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
console.print("[green]Validation passed[/green]")
|
|
150
|
+
return True
|
|
151
|
+
except subprocess.TimeoutExpired:
|
|
152
|
+
console.print("[red]Validation timed out after 5 minutes[/red]")
|
|
153
|
+
return False
|
|
154
|
+
finally:
|
|
155
|
+
if temp_dir and temp_dir.exists():
|
|
156
|
+
shutil.rmtree(temp_dir)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_latest_pypi_version(package: str) -> str:
|
|
160
|
+
"""Get the latest published version of a package from PyPI."""
|
|
161
|
+
response = httpx.get(f"https://pypi.org/pypi/{package}/json")
|
|
79
162
|
response.raise_for_status()
|
|
80
163
|
return response.json()["info"]["version"]
|
|
81
164
|
|
|
82
165
|
|
|
166
|
+
def get_pypi_requires_python(package: str) -> str:
|
|
167
|
+
"""Get the requires_python constraint from PyPI."""
|
|
168
|
+
response = httpx.get(f"https://pypi.org/pypi/{package}/json")
|
|
169
|
+
response.raise_for_status()
|
|
170
|
+
return response.json()["info"]["requires_python"]
|
|
171
|
+
|
|
172
|
+
|
|
83
173
|
def create_temp_validation_env() -> Path:
|
|
84
174
|
"""Create a temporary directory with a minimal pyproject.toml.
|
|
85
175
|
|
|
86
|
-
The pyproject.toml depends on the latest published cogames
|
|
176
|
+
The pyproject.toml depends on the latest published cogames and cogames-agents packages.
|
|
177
|
+
Python version is constrained to match mettagrid's published wheels.
|
|
87
178
|
"""
|
|
88
179
|
temp_dir = Path(tempfile.mkdtemp(prefix="cogames_submit_"))
|
|
89
180
|
|
|
90
|
-
latest_cogames_version =
|
|
181
|
+
latest_cogames_version = get_latest_pypi_version("cogames")
|
|
182
|
+
latest_agents_version = get_latest_pypi_version("cogames-agents")
|
|
183
|
+
mettagrid_requires_python = get_pypi_requires_python("mettagrid")
|
|
91
184
|
|
|
92
185
|
pyproject_content = f"""[project]
|
|
93
186
|
name = "cogames-submission-validator"
|
|
94
187
|
version = "0.1.0"
|
|
95
|
-
requires-python = "
|
|
96
|
-
dependencies = ["cogames=={latest_cogames_version}"]
|
|
188
|
+
requires-python = "{mettagrid_requires_python}"
|
|
189
|
+
dependencies = ["cogames=={latest_cogames_version}", "cogames-agents=={latest_agents_version}"]
|
|
97
190
|
|
|
98
191
|
[build-system]
|
|
99
192
|
requires = ["setuptools>=42"]
|
|
@@ -121,26 +214,27 @@ def copy_files_maintaining_structure(files: list[Path], dest_dir: Path) -> None:
|
|
|
121
214
|
shutil.copy2(file_path, dest_path)
|
|
122
215
|
|
|
123
216
|
|
|
124
|
-
def validate_policy_spec(policy_spec: PolicySpec) -> None:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Loads the policy and runs a single episode (up to 10 steps) using the same
|
|
128
|
-
multi_episode_rollout flow as `cogames eval`.
|
|
129
|
-
"""
|
|
130
|
-
from cogames.cli.mission import get_mission
|
|
131
|
-
from mettagrid.simulator.multi_episode.rollout import multi_episode_rollout
|
|
132
|
-
|
|
133
|
-
_, env_cfg, _ = get_mission("machina_1")
|
|
134
|
-
policy_env_info = PolicyEnvInterface.from_mg_cfg(env_cfg)
|
|
135
|
-
policy = initialize_or_load_policy(policy_env_info, policy_spec)
|
|
136
|
-
|
|
137
|
-
# Run 1 episode for up to 10 steps to validate the policy works
|
|
217
|
+
def validate_policy_spec(policy_spec: PolicySpec, env_cfg: MettaGridConfig) -> None:
|
|
218
|
+
env_cfg = env_cfg.model_copy()
|
|
138
219
|
env_cfg.game.max_steps = 10
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
220
|
+
n = env_cfg.game.num_agents
|
|
221
|
+
n_submitted = min(2, n)
|
|
222
|
+
noop_spec = PolicySpec(class_path="mettagrid.policy.noop.NoopPolicy")
|
|
223
|
+
if n_submitted < n:
|
|
224
|
+
policy_specs = [policy_spec, noop_spec]
|
|
225
|
+
assignments = [0] * n_submitted + [1] * (n - n_submitted)
|
|
226
|
+
else:
|
|
227
|
+
policy_specs = [policy_spec]
|
|
228
|
+
assignments = [0] * n
|
|
229
|
+
run_single_episode(
|
|
230
|
+
policy_specs=policy_specs,
|
|
231
|
+
assignments=assignments,
|
|
232
|
+
env=env_cfg,
|
|
233
|
+
results_uri=None,
|
|
234
|
+
replay_uri=None,
|
|
143
235
|
seed=42,
|
|
236
|
+
max_action_time_ms=10000,
|
|
237
|
+
device="cpu",
|
|
144
238
|
)
|
|
145
239
|
|
|
146
240
|
|
|
@@ -149,15 +243,10 @@ def validate_policy_in_isolation(
|
|
|
149
243
|
include_files: list[Path],
|
|
150
244
|
console: Console,
|
|
151
245
|
setup_script: str | None = None,
|
|
246
|
+
*,
|
|
247
|
+
season: str,
|
|
248
|
+
server: str,
|
|
152
249
|
) -> bool:
|
|
153
|
-
"""Validate policy works in isolated environment.
|
|
154
|
-
|
|
155
|
-
Creates a temp environment with published cogames package,
|
|
156
|
-
copies include-files, and runs cogames eval for 1 episode with max 10 steps.
|
|
157
|
-
|
|
158
|
-
Returns True if validation succeeds, False otherwise.
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
250
|
def _format_policy_arg(spec: PolicySpec) -> str:
|
|
162
251
|
parts = [f"class={spec.class_path}"]
|
|
163
252
|
if spec.data_path:
|
|
@@ -166,7 +255,7 @@ def validate_policy_in_isolation(
|
|
|
166
255
|
parts.append(f"kw.{key}={value}")
|
|
167
256
|
return ",".join(parts)
|
|
168
257
|
|
|
169
|
-
console.print("[dim]Testing policy can run 10 steps
|
|
258
|
+
console.print("[dim]Testing policy can run 10 steps...[/dim]")
|
|
170
259
|
|
|
171
260
|
temp_dir = None
|
|
172
261
|
try:
|
|
@@ -175,7 +264,7 @@ def validate_policy_in_isolation(
|
|
|
175
264
|
|
|
176
265
|
policy_arg = _format_policy_arg(policy_spec)
|
|
177
266
|
|
|
178
|
-
def _run_from_tmp_dir(cmd: list[str]) -> subprocess.CompletedProcess:
|
|
267
|
+
def _run_from_tmp_dir(cmd: list[str]) -> subprocess.CompletedProcess[str]:
|
|
179
268
|
env = os.environ.copy()
|
|
180
269
|
env["UV_NO_CACHE"] = "1"
|
|
181
270
|
res = subprocess.run(
|
|
@@ -183,7 +272,7 @@ def validate_policy_in_isolation(
|
|
|
183
272
|
cwd=temp_dir,
|
|
184
273
|
capture_output=True,
|
|
185
274
|
text=True,
|
|
186
|
-
timeout=300,
|
|
275
|
+
timeout=300,
|
|
187
276
|
env=env,
|
|
188
277
|
)
|
|
189
278
|
if not res.returncode == 0:
|
|
@@ -202,9 +291,14 @@ def validate_policy_in_isolation(
|
|
|
202
291
|
"cogames",
|
|
203
292
|
"validate-policy",
|
|
204
293
|
policy_arg,
|
|
294
|
+
"--season",
|
|
295
|
+
season,
|
|
296
|
+
"--server",
|
|
297
|
+
server,
|
|
205
298
|
]
|
|
206
299
|
if setup_script:
|
|
207
300
|
validate_cmd.extend(["--setup-script", setup_script])
|
|
301
|
+
validate_cmd.extend(["--policy", policy_arg])
|
|
208
302
|
|
|
209
303
|
_run_from_tmp_dir(validate_cmd)
|
|
210
304
|
|
|
@@ -261,6 +355,7 @@ def upload_submission(
|
|
|
261
355
|
zip_path: Path,
|
|
262
356
|
submission_name: str,
|
|
263
357
|
console: Console,
|
|
358
|
+
season: str | None = None,
|
|
264
359
|
) -> UploadResult | None:
|
|
265
360
|
"""Upload submission to CoGames backend using a presigned S3 URL."""
|
|
266
361
|
console.print("[bold]Uploading[/bold]")
|
|
@@ -306,19 +401,24 @@ def upload_submission(
|
|
|
306
401
|
console.print(f"[red]Upload error: {e}[/red]")
|
|
307
402
|
return None
|
|
308
403
|
|
|
309
|
-
|
|
404
|
+
if not season:
|
|
405
|
+
console.print("[dim]Uploading policy...[/dim]")
|
|
406
|
+
else:
|
|
407
|
+
console.print(f"[dim]Uploading policy and submitting to season {season}...[/dim]")
|
|
310
408
|
|
|
311
409
|
try:
|
|
312
|
-
result = client.complete_policy_upload(upload_id, submission_name)
|
|
410
|
+
result = client.complete_policy_upload(upload_id, submission_name, season=season)
|
|
313
411
|
submission_id = result.get("id")
|
|
314
412
|
name = result.get("name")
|
|
315
413
|
version = result.get("version")
|
|
414
|
+
pools = result.get("pools")
|
|
316
415
|
if submission_id is not None and name is not None and version is not None:
|
|
317
416
|
try:
|
|
318
417
|
return UploadResult(
|
|
319
418
|
policy_version_id=uuid.UUID(str(submission_id)),
|
|
320
419
|
name=name,
|
|
321
420
|
version=version,
|
|
421
|
+
pools=pools,
|
|
322
422
|
)
|
|
323
423
|
except ValueError:
|
|
324
424
|
console.print(f"[red]Invalid submission ID returned: {submission_id}[/red]")
|
|
@@ -338,6 +438,55 @@ def upload_submission(
|
|
|
338
438
|
return None
|
|
339
439
|
|
|
340
440
|
|
|
441
|
+
def _upload_policy_bundle(
|
|
442
|
+
bundle_result: tuple[Path, bool],
|
|
443
|
+
*,
|
|
444
|
+
client: TournamentServerClient,
|
|
445
|
+
name: str,
|
|
446
|
+
console: Console,
|
|
447
|
+
init_kwargs: dict[str, str] | None,
|
|
448
|
+
include_files: list[str] | None,
|
|
449
|
+
setup_script: str | None,
|
|
450
|
+
skip_validation: bool,
|
|
451
|
+
dry_run: bool,
|
|
452
|
+
validation_season: str,
|
|
453
|
+
server: str,
|
|
454
|
+
season: str | None = None,
|
|
455
|
+
) -> UploadResult | None:
|
|
456
|
+
bundle_zip, cleanup_bundle_zip = bundle_result
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
if init_kwargs or include_files or setup_script:
|
|
460
|
+
console.print("[red]Error:[/red] Extra files/kwargs are not supported when uploading a bundle URI.")
|
|
461
|
+
console.print(
|
|
462
|
+
"Upload the bundle as-is, or use a short policy name or URI with local files to "
|
|
463
|
+
"build a new submission zip."
|
|
464
|
+
)
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
if not skip_validation:
|
|
468
|
+
if not validate_bundle_in_isolation(bundle_zip, console, season=validation_season, server=server):
|
|
469
|
+
console.print("\n[red]Upload aborted due to validation failure.[/red]")
|
|
470
|
+
return None
|
|
471
|
+
else:
|
|
472
|
+
console.print("[dim]Skipping validation[/dim]")
|
|
473
|
+
|
|
474
|
+
if dry_run:
|
|
475
|
+
console.print("[green]Dry run complete[/green]")
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
with client:
|
|
479
|
+
result = upload_submission(client, bundle_zip, name, console, season=season)
|
|
480
|
+
if not result:
|
|
481
|
+
console.print("\n[red]Upload failed.[/red]")
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
return result
|
|
485
|
+
finally:
|
|
486
|
+
if cleanup_bundle_zip and bundle_zip.exists():
|
|
487
|
+
bundle_zip.unlink()
|
|
488
|
+
|
|
489
|
+
|
|
341
490
|
def upload_policy(
|
|
342
491
|
ctx: typer.Context,
|
|
343
492
|
policy: str,
|
|
@@ -349,12 +498,9 @@ def upload_policy(
|
|
|
349
498
|
dry_run: bool = False,
|
|
350
499
|
skip_validation: bool = False,
|
|
351
500
|
setup_script: str | None = None,
|
|
501
|
+
validation_season: str = "",
|
|
502
|
+
season: str | None = None,
|
|
352
503
|
) -> UploadResult | None:
|
|
353
|
-
"""Upload a policy to CoGames (without submitting to a tournament).
|
|
354
|
-
|
|
355
|
-
Returns UploadResult with policy_version_id, name, and version on success.
|
|
356
|
-
Returns None on failure.
|
|
357
|
-
"""
|
|
358
504
|
from cogames.cli.client import TournamentServerClient
|
|
359
505
|
|
|
360
506
|
if dry_run:
|
|
@@ -364,6 +510,28 @@ def upload_policy(
|
|
|
364
510
|
if not client:
|
|
365
511
|
return None
|
|
366
512
|
|
|
513
|
+
try:
|
|
514
|
+
bundle_result = _maybe_resolve_checkpoint_bundle_uri(policy)
|
|
515
|
+
except Exception as e:
|
|
516
|
+
console.print(f"[red]Error resolving checkpoint bundle:[/red] {e}")
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
if bundle_result is not None:
|
|
520
|
+
return _upload_policy_bundle(
|
|
521
|
+
bundle_result,
|
|
522
|
+
client=client,
|
|
523
|
+
name=name,
|
|
524
|
+
console=console,
|
|
525
|
+
init_kwargs=init_kwargs,
|
|
526
|
+
include_files=include_files,
|
|
527
|
+
setup_script=setup_script,
|
|
528
|
+
skip_validation=skip_validation,
|
|
529
|
+
dry_run=dry_run,
|
|
530
|
+
validation_season=validation_season,
|
|
531
|
+
server=server,
|
|
532
|
+
season=season,
|
|
533
|
+
)
|
|
534
|
+
|
|
367
535
|
try:
|
|
368
536
|
policy_spec = get_policy_spec(ctx, policy)
|
|
369
537
|
except Exception as e:
|
|
@@ -378,11 +546,36 @@ def upload_policy(
|
|
|
378
546
|
init_kwargs=merged_kwargs,
|
|
379
547
|
)
|
|
380
548
|
|
|
549
|
+
cwd = Path.cwd().resolve()
|
|
550
|
+
if policy_spec.data_path:
|
|
551
|
+
try:
|
|
552
|
+
resolved = Path(policy_spec.data_path).expanduser().resolve()
|
|
553
|
+
data_rel = str(resolved.relative_to(cwd))
|
|
554
|
+
except ValueError:
|
|
555
|
+
console.print("[red]Error:[/red] Policy weights path must be within the current directory.")
|
|
556
|
+
console.print(f"[dim]{policy_spec.data_path}[/dim]")
|
|
557
|
+
return None
|
|
558
|
+
policy_spec = PolicySpec(
|
|
559
|
+
class_path=policy_spec.class_path,
|
|
560
|
+
data_path=data_rel,
|
|
561
|
+
init_kwargs=policy_spec.init_kwargs,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
setup_script_rel: str | None = None
|
|
565
|
+
if setup_script:
|
|
566
|
+
try:
|
|
567
|
+
resolved = Path(setup_script).expanduser().resolve()
|
|
568
|
+
setup_script_rel = str(resolved.relative_to(cwd))
|
|
569
|
+
except ValueError:
|
|
570
|
+
console.print("[red]Error:[/red] Setup script path must be within the current directory.")
|
|
571
|
+
console.print(f"[dim]{setup_script}[/dim]")
|
|
572
|
+
return None
|
|
573
|
+
|
|
381
574
|
files_to_include = []
|
|
382
575
|
if policy_spec.data_path:
|
|
383
576
|
files_to_include.append(policy_spec.data_path)
|
|
384
|
-
if
|
|
385
|
-
files_to_include.append(
|
|
577
|
+
if setup_script_rel:
|
|
578
|
+
files_to_include.append(setup_script_rel)
|
|
386
579
|
if include_files:
|
|
387
580
|
files_to_include.extend(include_files)
|
|
388
581
|
|
|
@@ -394,14 +587,21 @@ def upload_policy(
|
|
|
394
587
|
return None
|
|
395
588
|
|
|
396
589
|
if not skip_validation:
|
|
397
|
-
if not validate_policy_in_isolation(
|
|
590
|
+
if not validate_policy_in_isolation(
|
|
591
|
+
policy_spec,
|
|
592
|
+
validated_paths,
|
|
593
|
+
console,
|
|
594
|
+
setup_script=setup_script_rel,
|
|
595
|
+
season=validation_season,
|
|
596
|
+
server=server,
|
|
597
|
+
):
|
|
398
598
|
console.print("\n[red]Upload aborted due to validation failure.[/red]")
|
|
399
599
|
return None
|
|
400
600
|
else:
|
|
401
601
|
console.print("[dim]Skipping validation[/dim]")
|
|
402
602
|
|
|
403
603
|
try:
|
|
404
|
-
zip_path = create_submission_zip(validated_paths, policy_spec, setup_script=
|
|
604
|
+
zip_path = create_submission_zip(validated_paths, policy_spec, setup_script=setup_script_rel)
|
|
405
605
|
except Exception as e:
|
|
406
606
|
console.print(f"[red]Error creating zip:[/red] {e}")
|
|
407
607
|
return None
|
|
@@ -414,10 +614,11 @@ def upload_policy(
|
|
|
414
614
|
|
|
415
615
|
try:
|
|
416
616
|
with client:
|
|
417
|
-
result = upload_submission(client, zip_path, name, console)
|
|
617
|
+
result = upload_submission(client, zip_path, name, console, season=season)
|
|
418
618
|
if not result:
|
|
419
619
|
console.print("\n[red]Upload failed.[/red]")
|
|
420
620
|
return None
|
|
621
|
+
|
|
421
622
|
return result
|
|
422
623
|
finally:
|
|
423
624
|
if zip_path.exists():
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from mettagrid.base_config import Config
|
|
6
|
+
from mettagrid.config.handler_config import Handler
|
|
7
|
+
from mettagrid.config.mettagrid_config import (
|
|
8
|
+
AgentConfig,
|
|
9
|
+
InventoryConfig,
|
|
10
|
+
ResourceLimitsConfig,
|
|
11
|
+
)
|
|
12
|
+
from mettagrid.config.mutation.resource_mutation import updateActor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CogConfig(Config):
|
|
16
|
+
"""Configuration for cog agents in CogsGuard game mode."""
|
|
17
|
+
|
|
18
|
+
# Inventory limits
|
|
19
|
+
gear_limit: int = Field(default=1)
|
|
20
|
+
hp_limit: int = Field(default=100)
|
|
21
|
+
heart_limit: int = Field(default=10)
|
|
22
|
+
energy_limit: int = Field(default=20)
|
|
23
|
+
cargo_limit: int = Field(default=4)
|
|
24
|
+
influence_limit: int = Field(default=0)
|
|
25
|
+
|
|
26
|
+
# Inventory modifiers by gear type
|
|
27
|
+
hp_modifiers: dict[str, int] = Field(default_factory=lambda: {"scout": 400, "scrambler": 200})
|
|
28
|
+
energy_modifiers: dict[str, int] = Field(default_factory=lambda: {"scout": 100})
|
|
29
|
+
cargo_modifiers: dict[str, int] = Field(default_factory=lambda: {"miner": 40})
|
|
30
|
+
influence_modifiers: dict[str, int] = Field(default_factory=lambda: {"aligner": 20})
|
|
31
|
+
|
|
32
|
+
# Initial inventory
|
|
33
|
+
initial_energy: int = Field(default=100)
|
|
34
|
+
initial_hp: int = Field(default=50)
|
|
35
|
+
|
|
36
|
+
# Regen amounts
|
|
37
|
+
energy_regen: int = Field(default=1)
|
|
38
|
+
hp_regen: int = Field(default=-1)
|
|
39
|
+
influence_regen: int = Field(default=-1)
|
|
40
|
+
|
|
41
|
+
# Movement cost
|
|
42
|
+
move_energy_cost: int = Field(default=3)
|
|
43
|
+
|
|
44
|
+
def agent_config(self, gear: list[str], elements: list[str]) -> AgentConfig:
|
|
45
|
+
"""Create an AgentConfig for this cog configuration."""
|
|
46
|
+
return AgentConfig(
|
|
47
|
+
collective="cogs",
|
|
48
|
+
inventory=InventoryConfig(
|
|
49
|
+
limits={
|
|
50
|
+
"hp": ResourceLimitsConfig(min=self.hp_limit, resources=["hp"], modifiers=self.hp_modifiers),
|
|
51
|
+
# when hp == 0, the cog can't hold gear or hearts
|
|
52
|
+
"gear": ResourceLimitsConfig(max=self.gear_limit, resources=gear, modifiers={"hp": 100}),
|
|
53
|
+
"heart": ResourceLimitsConfig(max=self.heart_limit, resources=["heart"], modifiers={"hp": 100}),
|
|
54
|
+
"energy": ResourceLimitsConfig(
|
|
55
|
+
min=self.energy_limit, resources=["energy"], modifiers=self.energy_modifiers
|
|
56
|
+
),
|
|
57
|
+
"cargo": ResourceLimitsConfig(
|
|
58
|
+
min=self.cargo_limit, resources=elements, modifiers=self.cargo_modifiers
|
|
59
|
+
),
|
|
60
|
+
"influence": ResourceLimitsConfig(
|
|
61
|
+
min=self.influence_limit, resources=["influence"], modifiers=self.influence_modifiers
|
|
62
|
+
),
|
|
63
|
+
},
|
|
64
|
+
initial={"energy": self.initial_energy, "hp": self.initial_hp},
|
|
65
|
+
),
|
|
66
|
+
on_tick={
|
|
67
|
+
"regen": Handler(
|
|
68
|
+
mutations=[
|
|
69
|
+
updateActor(
|
|
70
|
+
{
|
|
71
|
+
"energy": self.energy_regen,
|
|
72
|
+
"hp": self.hp_regen,
|
|
73
|
+
"influence": self.influence_regen,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
)
|
|
@@ -23,7 +23,7 @@ This guide explains how the procedural map system is wired together and how to e
|
|
|
23
23
|
- CLI glue (`cogames play`, `cogames missions`, `cogames train`) and variant composition
|
|
24
24
|
|
|
25
25
|
Everything ultimately produces a `MapBuilderConfig` that feeds into a `MettaGridConfig`. Missions and variants
|
|
26
|
-
coordinate map building, agent setup, and post-processing such as
|
|
26
|
+
coordinate map building, agent setup, and post-processing such as hub rewrites.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -75,7 +75,7 @@ MACHINA_PROCEDURAL_200 = Site(
|
|
|
75
75
|
hub_cross_distance=7,
|
|
76
76
|
building_names=[
|
|
77
77
|
"chest",
|
|
78
|
-
"
|
|
78
|
+
"junction",
|
|
79
79
|
"carbon_extractor",
|
|
80
80
|
"oxygen_extractor",
|
|
81
81
|
"germanium_extractor",
|
|
@@ -83,7 +83,7 @@ MACHINA_PROCEDURAL_200 = Site(
|
|
|
83
83
|
],
|
|
84
84
|
building_weights={
|
|
85
85
|
"chest": 0.2,
|
|
86
|
-
"
|
|
86
|
+
"junction": 0.6,
|
|
87
87
|
"carbon_extractor": 0.3,
|
|
88
88
|
"oxygen_extractor": 0.3,
|
|
89
89
|
"germanium_extractor": 0.3,
|
|
@@ -93,7 +93,7 @@ MACHINA_PROCEDURAL_200 = Site(
|
|
|
93
93
|
distribution={"type": "bimodal", "cluster_std": 0.15},
|
|
94
94
|
building_distributions={
|
|
95
95
|
"chest": {"type": "exponential", "decay_rate": 5.0, "origin_x": 0.0, "origin_y": 0.0},
|
|
96
|
-
"
|
|
96
|
+
"junction": {"type": "poisson"},
|
|
97
97
|
},
|
|
98
98
|
),
|
|
99
99
|
),
|
|
@@ -208,7 +208,7 @@ Common patterns:
|
|
|
208
208
|
class MyVariant(MissionVariant):
|
|
209
209
|
name: str = "my"
|
|
210
210
|
def modify_env(self, mission: Mission, env: MettaGridConfig) -> None:
|
|
211
|
-
env.game.
|
|
211
|
+
env.game.junction.efficiency -= 50
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
CLI variants are composed in order, so `cogames play -m machina_procedural.open_world -v city -v both_base` applies
|
|
@@ -218,13 +218,16 @@ CLI variants are composed in order, so `cogames play -m machina_procedural.open_
|
|
|
218
218
|
|
|
219
219
|
### Seeds and Reproducibility
|
|
220
220
|
|
|
221
|
-
- `MapGen.Config.seed` (`env.game.map_builder.seed`) controls **map layout
|
|
222
|
-
|
|
223
|
-
-
|
|
224
|
-
|
|
225
|
-
-
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
- `MapGen.Config.seed` (`env.game.map_builder.seed`) controls **map layout**.
|
|
222
|
+
- If the mission sets a MapGen seed, all commands use it unless you pass `--map-seed`.
|
|
223
|
+
- `--map-seed` overrides the MapGen seed for procedural maps.
|
|
224
|
+
- `--seed` sets the simulator/policy RNG for the run (sim dynamics, policy sampling, assignment shuffles).
|
|
225
|
+
- When a MapGen seed is set (`--map-seed` or mission seed), the map layout follows a deterministic per-episode seed
|
|
226
|
+
sequence. If `maps_cache_size` is set, the sequence repeats every `maps_cache_size` episodes; otherwise it increases
|
|
227
|
+
monotonically.
|
|
228
|
+
- When no MapGen seed is set, the map layout is random. With caching enabled, you see up to `maps_cache_size` distinct
|
|
229
|
+
layouts; with caching disabled (`maps_cache_size=None`), you get a fresh layout each episode.
|
|
230
|
+
- For fully reproducible play/eval runs, set **both** `--seed` and `--map-seed`.
|
|
228
231
|
|
|
229
232
|
Example programmatic override using the shared `MapSeedVariant` helper:
|
|
230
233
|
|
|
@@ -292,11 +295,11 @@ mission = Mission(
|
|
|
292
295
|
--seed 12345
|
|
293
296
|
```
|
|
294
297
|
|
|
295
|
-
- Reproduce a procedural
|
|
298
|
+
- Reproduce a procedural layout:
|
|
296
299
|
```bash
|
|
297
|
-
cogames play -m machina_procedural.open_world --variant city --seed 24601
|
|
300
|
+
cogames play -m machina_procedural.open_world --variant city --map-seed 24601 --seed 24601
|
|
298
301
|
```
|
|
299
|
-
(
|
|
302
|
+
(Use `--map-seed` for layout determinism; include `--seed` to reproduce simulator/policy RNG.)
|
|
300
303
|
|
|
301
304
|
---
|
|
302
305
|
|
|
@@ -340,7 +343,7 @@ Usage example:
|
|
|
340
343
|
|
|
341
344
|
```bash
|
|
342
345
|
uv run python packages/cogames/scripts/run_evaluation.py \
|
|
343
|
-
--
|
|
346
|
+
--policy thinky \
|
|
344
347
|
--mission-set integrated_evals \
|
|
345
348
|
--cogs 4 \
|
|
346
349
|
--repeats 2
|