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.
Files changed (169) hide show
  1. cogames/cli/client.py +60 -6
  2. cogames/cli/docsync/__init__.py +0 -0
  3. cogames/cli/docsync/_nb_md_directive_processing.py +180 -0
  4. cogames/cli/docsync/_nb_md_sync.py +103 -0
  5. cogames/cli/docsync/_nb_py_sync.py +122 -0
  6. cogames/cli/docsync/_three_way_sync.py +115 -0
  7. cogames/cli/docsync/_utils.py +76 -0
  8. cogames/cli/docsync/docsync.py +156 -0
  9. cogames/cli/leaderboard.py +112 -28
  10. cogames/cli/mission.py +64 -53
  11. cogames/cli/policy.py +46 -10
  12. cogames/cli/submit.py +268 -67
  13. cogames/cogs_vs_clips/cog.py +79 -0
  14. cogames/cogs_vs_clips/cogs_vs_clips_mapgen.md +19 -16
  15. cogames/cogs_vs_clips/cogsguard_reward_variants.py +153 -0
  16. cogames/cogs_vs_clips/cogsguard_tutorial.py +56 -0
  17. cogames/cogs_vs_clips/evals/README.md +10 -16
  18. cogames/cogs_vs_clips/evals/cogsguard_evals.py +81 -0
  19. cogames/cogs_vs_clips/evals/diagnostic_evals.py +49 -444
  20. cogames/cogs_vs_clips/evals/difficulty_variants.py +13 -326
  21. cogames/cogs_vs_clips/evals/integrated_evals.py +5 -45
  22. cogames/cogs_vs_clips/evals/spanning_evals.py +9 -180
  23. cogames/cogs_vs_clips/mission.py +187 -146
  24. cogames/cogs_vs_clips/missions.py +46 -137
  25. cogames/cogs_vs_clips/procedural.py +8 -8
  26. cogames/cogs_vs_clips/sites.py +107 -3
  27. cogames/cogs_vs_clips/stations.py +198 -186
  28. cogames/cogs_vs_clips/tutorial_missions.py +1 -1
  29. cogames/cogs_vs_clips/variants.py +25 -476
  30. cogames/device.py +13 -1
  31. cogames/{policy/scripted_agent/README.md → docs/SCRIPTED_AGENT.md} +82 -58
  32. cogames/evaluate.py +18 -30
  33. cogames/main.py +1434 -243
  34. cogames/maps/canidate1_1000.map +1 -1
  35. cogames/maps/canidate1_1000_stations.map +2 -2
  36. cogames/maps/canidate1_500.map +1 -1
  37. cogames/maps/canidate1_500_stations.map +2 -2
  38. cogames/maps/canidate2_1000.map +1 -1
  39. cogames/maps/canidate2_1000_stations.map +2 -2
  40. cogames/maps/canidate2_500.map +1 -1
  41. cogames/maps/canidate2_500_stations.map +2 -2
  42. cogames/maps/canidate3_1000.map +1 -1
  43. cogames/maps/canidate3_1000_stations.map +2 -2
  44. cogames/maps/canidate3_500.map +1 -1
  45. cogames/maps/canidate3_500_stations.map +2 -2
  46. cogames/maps/canidate4_500.map +1 -1
  47. cogames/maps/canidate4_500_stations.map +2 -2
  48. cogames/maps/cave_base_50.map +2 -2
  49. cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
  50. cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
  51. cogames/maps/diagnostic_evals/diagnostic_charge_up.map +2 -2
  52. cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +2 -2
  53. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +2 -2
  54. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +2 -2
  55. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +2 -2
  56. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +2 -2
  57. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +2 -2
  58. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +2 -2
  59. cogames/maps/diagnostic_evals/diagnostic_chest_near.map +2 -2
  60. cogames/maps/diagnostic_evals/diagnostic_chest_search.map +2 -2
  61. cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +2 -2
  62. cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +2 -2
  63. cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +2 -2
  64. cogames/maps/diagnostic_evals/diagnostic_memory.map +2 -2
  65. cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +2 -2
  66. cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
  67. cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
  68. cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +2 -2
  69. cogames/maps/diagnostic_evals/diagnostic_unclip.map +2 -2
  70. cogames/maps/evals/eval_balanced_spread.map +9 -5
  71. cogames/maps/evals/eval_clip_oxygen.map +9 -5
  72. cogames/maps/evals/eval_collect_resources.map +9 -5
  73. cogames/maps/evals/eval_collect_resources_hard.map +9 -5
  74. cogames/maps/evals/eval_collect_resources_medium.map +9 -5
  75. cogames/maps/evals/eval_divide_and_conquer.map +9 -5
  76. cogames/maps/evals/eval_energy_starved.map +9 -5
  77. cogames/maps/evals/eval_multi_coordinated_collect_hard.map +9 -5
  78. cogames/maps/evals/eval_oxygen_bottleneck.map +9 -5
  79. cogames/maps/evals/eval_single_use_world.map +9 -5
  80. cogames/maps/evals/extractor_hub_100x100.map +9 -5
  81. cogames/maps/evals/extractor_hub_30x30.map +9 -5
  82. cogames/maps/evals/extractor_hub_50x50.map +9 -5
  83. cogames/maps/evals/extractor_hub_70x70.map +9 -5
  84. cogames/maps/evals/extractor_hub_80x80.map +9 -5
  85. cogames/maps/machina_100_stations.map +2 -2
  86. cogames/maps/machina_200_stations.map +2 -2
  87. cogames/maps/machina_200_stations_small.map +2 -2
  88. cogames/maps/machina_eval_exp01.map +2 -2
  89. cogames/maps/machina_eval_template_large.map +2 -2
  90. cogames/maps/machinatrainer4agents.map +2 -2
  91. cogames/maps/machinatrainer4agentsbase.map +2 -2
  92. cogames/maps/machinatrainerbig.map +2 -2
  93. cogames/maps/machinatrainersmall.map +2 -2
  94. cogames/maps/planky_evals/aligner_avoid_aoe.map +28 -0
  95. cogames/maps/planky_evals/aligner_full_cycle.map +28 -0
  96. cogames/maps/planky_evals/aligner_gear.map +24 -0
  97. cogames/maps/planky_evals/aligner_hearts.map +24 -0
  98. cogames/maps/planky_evals/aligner_junction.map +26 -0
  99. cogames/maps/planky_evals/exploration_distant.map +28 -0
  100. cogames/maps/planky_evals/maze.map +32 -0
  101. cogames/maps/planky_evals/miner_best_resource.map +26 -0
  102. cogames/maps/planky_evals/miner_deposit.map +24 -0
  103. cogames/maps/planky_evals/miner_extract.map +26 -0
  104. cogames/maps/planky_evals/miner_full_cycle.map +28 -0
  105. cogames/maps/planky_evals/miner_gear.map +24 -0
  106. cogames/maps/planky_evals/multi_role.map +28 -0
  107. cogames/maps/planky_evals/resource_chain.map +30 -0
  108. cogames/maps/planky_evals/scout_explore.map +32 -0
  109. cogames/maps/planky_evals/scout_gear.map +24 -0
  110. cogames/maps/planky_evals/scrambler_full_cycle.map +28 -0
  111. cogames/maps/planky_evals/scrambler_gear.map +24 -0
  112. cogames/maps/planky_evals/scrambler_target.map +26 -0
  113. cogames/maps/planky_evals/stuck_corridor.map +32 -0
  114. cogames/maps/planky_evals/survive_retreat.map +26 -0
  115. cogames/maps/training_facility_clipped.map +2 -2
  116. cogames/maps/training_facility_open_1.map +2 -2
  117. cogames/maps/training_facility_open_2.map +2 -2
  118. cogames/maps/training_facility_open_3.map +2 -2
  119. cogames/maps/training_facility_tight_4.map +2 -2
  120. cogames/maps/training_facility_tight_5.map +2 -2
  121. cogames/maps/vanilla_large.map +2 -2
  122. cogames/maps/vanilla_small.map +2 -2
  123. cogames/pickup.py +183 -0
  124. cogames/play.py +166 -33
  125. cogames/policy/chaos_monkey.py +54 -0
  126. cogames/policy/nim_agents/__init__.py +27 -10
  127. cogames/policy/nim_agents/agents.py +121 -60
  128. cogames/policy/nim_agents/thinky_eval.py +35 -222
  129. cogames/policy/pufferlib_policy.py +67 -32
  130. cogames/policy/starter_agent.py +184 -0
  131. cogames/policy/trainable_policy_template.py +4 -1
  132. cogames/train.py +51 -13
  133. cogames/verbose.py +2 -2
  134. cogames-0.3.64.dist-info/METADATA +1842 -0
  135. cogames-0.3.64.dist-info/RECORD +159 -0
  136. cogames-0.3.64.dist-info/licenses/LICENSE +21 -0
  137. cogames-0.3.64.dist-info/top_level.txt +2 -0
  138. metta_alo/__init__.py +0 -0
  139. metta_alo/job_specs.py +17 -0
  140. metta_alo/policy.py +16 -0
  141. metta_alo/pure_single_episode_runner.py +75 -0
  142. metta_alo/py.typed +0 -0
  143. metta_alo/rollout.py +322 -0
  144. metta_alo/scoring.py +168 -0
  145. cogames/maps/diagnostic_evals/diagnostic_assembler_near.map +0 -49
  146. cogames/maps/diagnostic_evals/diagnostic_assembler_search.map +0 -49
  147. cogames/maps/diagnostic_evals/diagnostic_assembler_search_hard.map +0 -89
  148. cogames/policy/nim_agents/common.nim +0 -887
  149. cogames/policy/nim_agents/install.sh +0 -1
  150. cogames/policy/nim_agents/ladybug_agent.nim +0 -984
  151. cogames/policy/nim_agents/nim_agents.nim +0 -55
  152. cogames/policy/nim_agents/nim_agents.nims +0 -14
  153. cogames/policy/nim_agents/nimby.lock +0 -3
  154. cogames/policy/nim_agents/racecar_agents.nim +0 -884
  155. cogames/policy/nim_agents/random_agents.nim +0 -68
  156. cogames/policy/nim_agents/test_agents.py +0 -53
  157. cogames/policy/nim_agents/thinky_agents.nim +0 -717
  158. cogames/policy/scripted_agent/baseline_agent.py +0 -1049
  159. cogames/policy/scripted_agent/demo_policy.py +0 -244
  160. cogames/policy/scripted_agent/pathfinding.py +0 -126
  161. cogames/policy/scripted_agent/starter_agent.py +0 -136
  162. cogames/policy/scripted_agent/types.py +0 -235
  163. cogames/policy/scripted_agent/unclipping_agent.py +0 -476
  164. cogames/policy/scripted_agent/utils.py +0 -385
  165. cogames-0.3.49.dist-info/METADATA +0 -406
  166. cogames-0.3.49.dist-info/RECORD +0 -136
  167. cogames-0.3.49.dist-info/top_level.txt +0 -1
  168. {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/WHEEL +0 -0
  169. {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.policy.loader import initialize_or_load_policy
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 that all paths are relative and within CWD.
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
- path = Path(path_str)
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 / path).resolve()
60
- # Check if resolved path is under CWD
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 escapes current directory: {path_str}")
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(path)
66
+ validated_paths.append(relative)
72
67
 
73
68
  return validated_paths
74
69
 
75
70
 
76
- def get_latest_cogames_version() -> str:
77
- """Get the latest published cogames version."""
78
- response = httpx.get("https://pypi.org/pypi/cogames/json")
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 package.
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 = get_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 = ">=3.12"
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
- """Validate policy works.
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
- multi_episode_rollout(
140
- env_cfg=env_cfg,
141
- policies=[policy],
142
- episodes=1,
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 on machina_1...[/dim]")
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, # 5 minute timeout
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
- console.print("[dim]Registering policy...[/dim]")
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 setup_script:
385
- files_to_include.append(setup_script)
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(policy_spec, validated_paths, console, setup_script=setup_script):
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=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 assembler rewrites.
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
- "charger",
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
- "charger": 0.6,
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
- "charger": {"type": "poisson"},
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.charger.efficiency -= 50
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**: set it for deterministic terrain/building
222
- placement for a mission/site.
223
- - `cogames evaluate` and `cogames play` use `--seed` for the simulator/policy RNG and `--map-seed` (or `--seed` if
224
- omitted) for `MapGenConfig.seed`, so runs can be made fully reproducible from the CLI.
225
- - `cogames train` treats `--map-seed` as an **opt-in** override: when set, all procedural training missions use that
226
- fixed `MapGenConfig.seed`; when left `None`, the vectorized env factory derives per-env map seeds from the runner’s
227
- RNG so a fixed `--seed` yields a reproducible but diverse map sequence.
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 seed:
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
- (Assuming the mission/variant combination sets or respects `procedural_overrides["seed"]`.)
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
- --agent cogames.policy.nim_agents.agents.ThinkyAgentsMultiPolicy \
346
+ --policy thinky \
344
347
  --mission-set integrated_evals \
345
348
  --cogs 4 \
346
349
  --repeats 2