cogames 0.3.65__py3-none-any.whl → 0.3.68__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cogames/cli/client.py +0 -3
- cogames/cli/docsync/docsync.py +7 -1
- cogames/cli/mission.py +44 -19
- cogames/cli/policy.py +26 -10
- cogames/cli/submit.py +127 -141
- cogames/cli/utils.py +5 -0
- cogames/cogs_vs_clips/clip_difficulty.py +57 -0
- cogames/cogs_vs_clips/clips.py +23 -6
- cogames/cogs_vs_clips/cog.py +16 -5
- cogames/cogs_vs_clips/cogsguard_curriculum.py +122 -0
- cogames/cogs_vs_clips/cogsguard_tutorial.py +5 -5
- cogames/cogs_vs_clips/config.py +1 -1
- cogames/cogs_vs_clips/docs/cogs_vs_clips_mapgen.md +2 -3
- cogames/cogs_vs_clips/evals/README.md +8 -32
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +0 -1
- cogames/cogs_vs_clips/evals/difficulty_variants.py +7 -10
- cogames/cogs_vs_clips/mission.py +38 -10
- cogames/cogs_vs_clips/missions.py +1 -1
- cogames/cogs_vs_clips/reward_variants.py +173 -0
- cogames/cogs_vs_clips/sites.py +6 -5
- cogames/cogs_vs_clips/stations.py +13 -9
- cogames/cogs_vs_clips/team.py +3 -1
- cogames/cogs_vs_clips/terrain.py +2 -2
- cogames/cogs_vs_clips/variants.py +175 -4
- cogames/cogs_vs_clips/weather.py +52 -0
- cogames/docs/SCRIPTED_AGENT.md +3 -3
- cogames/evaluate.py +4 -2
- cogames/main.py +357 -51
- cogames/maps/canidate1_1000.map +1 -1
- cogames/maps/canidate1_1000_stations.map +2 -2
- cogames/maps/canidate1_500.map +1 -1
- cogames/maps/canidate1_500_stations.map +2 -2
- cogames/maps/canidate2_1000.map +1 -1
- cogames/maps/canidate2_1000_stations.map +2 -2
- cogames/maps/canidate2_500.map +1 -1
- cogames/maps/canidate2_500_stations.map +1 -1
- cogames/maps/canidate3_1000.map +1 -1
- cogames/maps/canidate3_1000_stations.map +2 -2
- cogames/maps/canidate3_500.map +1 -1
- cogames/maps/canidate3_500_stations.map +2 -2
- cogames/maps/canidate4_500.map +1 -1
- cogames/maps/canidate4_500_stations.map +2 -2
- cogames/maps/cave_base_50.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_charge_up.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_near.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_unclip.map +6 -6
- cogames/maps/evals/eval_balanced_spread.map +6 -6
- cogames/maps/evals/eval_clip_oxygen.map +6 -6
- cogames/maps/evals/eval_collect_resources.map +6 -6
- cogames/maps/evals/eval_collect_resources_hard.map +6 -6
- cogames/maps/evals/eval_collect_resources_medium.map +6 -6
- cogames/maps/evals/eval_divide_and_conquer.map +6 -6
- cogames/maps/evals/eval_energy_starved.map +6 -6
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +6 -6
- cogames/maps/evals/eval_oxygen_bottleneck.map +6 -6
- cogames/maps/evals/eval_single_use_world.map +6 -6
- cogames/maps/evals/extractor_hub_100x100.map +6 -6
- cogames/maps/evals/extractor_hub_30x30.map +6 -6
- cogames/maps/evals/extractor_hub_50x50.map +6 -6
- cogames/maps/evals/extractor_hub_70x70.map +6 -6
- cogames/maps/evals/extractor_hub_80x80.map +6 -6
- cogames/maps/machina_100_stations.map +2 -2
- cogames/maps/machina_200_stations.map +2 -2
- cogames/maps/machina_200_stations_small.map +2 -2
- cogames/maps/machina_eval_exp01.map +2 -2
- cogames/maps/machina_eval_template_large.map +2 -2
- cogames/maps/machinatrainer4agents.map +2 -2
- cogames/maps/machinatrainer4agentsbase.map +2 -2
- cogames/maps/machinatrainerbig.map +2 -2
- cogames/maps/machinatrainersmall.map +2 -2
- cogames/maps/planky_evals/aligner_avoid_aoe.map +6 -6
- cogames/maps/planky_evals/aligner_full_cycle.map +6 -6
- cogames/maps/planky_evals/aligner_gear.map +6 -6
- cogames/maps/planky_evals/aligner_hearts.map +6 -6
- cogames/maps/planky_evals/aligner_junction.map +6 -6
- cogames/maps/planky_evals/exploration_distant.map +6 -6
- cogames/maps/planky_evals/maze.map +6 -6
- cogames/maps/planky_evals/miner_best_resource.map +6 -6
- cogames/maps/planky_evals/miner_deposit.map +6 -6
- cogames/maps/planky_evals/miner_extract.map +6 -6
- cogames/maps/planky_evals/miner_full_cycle.map +6 -6
- cogames/maps/planky_evals/miner_gear.map +6 -6
- cogames/maps/planky_evals/multi_role.map +6 -6
- cogames/maps/planky_evals/resource_chain.map +6 -6
- cogames/maps/planky_evals/scout_explore.map +6 -6
- cogames/maps/planky_evals/scout_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_full_cycle.map +6 -6
- cogames/maps/planky_evals/scrambler_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_target.map +6 -6
- cogames/maps/planky_evals/stuck_corridor.map +6 -6
- cogames/maps/planky_evals/survive_retreat.map +6 -6
- cogames/maps/training_facility_clipped.map +2 -2
- cogames/maps/training_facility_open_1.map +2 -2
- cogames/maps/training_facility_open_2.map +2 -2
- cogames/maps/training_facility_open_3.map +2 -2
- cogames/maps/training_facility_tight_4.map +2 -2
- cogames/maps/training_facility_tight_5.map +2 -2
- cogames/maps/vanilla_large.map +2 -2
- cogames/maps/vanilla_small.map +2 -2
- cogames/pickup.py +6 -5
- cogames/play.py +14 -16
- cogames/policy/nim_agents/__init__.py +0 -2
- cogames/policy/nim_agents/agents.py +0 -11
- cogames/policy/starter_agent.py +4 -1
- {cogames-0.3.65.dist-info → cogames-0.3.68.dist-info}/METADATA +45 -29
- cogames-0.3.68.dist-info/RECORD +160 -0
- metta_alo/scoring.py +7 -7
- cogames-0.3.65.dist-info/RECORD +0 -160
- metta_alo/job_specs.py +0 -17
- metta_alo/policy.py +0 -16
- metta_alo/pure_single_episode_runner.py +0 -75
- metta_alo/rollout.py +0 -322
- {cogames-0.3.65.dist-info → cogames-0.3.68.dist-info}/WHEEL +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.68.dist-info}/entry_points.txt +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.68.dist-info}/licenses/LICENSE +0 -0
- {cogames-0.3.65.dist-info → cogames-0.3.68.dist-info}/top_level.txt +0 -0
cogames/cli/submit.py
CHANGED
|
@@ -10,22 +10,21 @@ import uuid
|
|
|
10
10
|
import zipfile
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import TYPE_CHECKING
|
|
14
13
|
|
|
15
14
|
import httpx
|
|
16
15
|
import typer
|
|
17
16
|
from rich.console import Console
|
|
18
17
|
|
|
19
18
|
from cogames.cli.base import console
|
|
19
|
+
from cogames.cli.client import TournamentServerClient
|
|
20
20
|
from cogames.cli.login import DEFAULT_COGAMES_SERVER
|
|
21
|
-
from cogames.cli.
|
|
22
|
-
from
|
|
23
|
-
|
|
24
|
-
if TYPE_CHECKING:
|
|
25
|
-
from cogames.cli.client import TournamentServerClient
|
|
26
|
-
|
|
21
|
+
from cogames.cli.mission import get_mission
|
|
22
|
+
from cogames.cli.policy import PolicySpec, get_policy_spec, parse_policy_spec
|
|
27
23
|
from mettagrid.config.mettagrid_config import MettaGridConfig
|
|
24
|
+
from mettagrid.policy.prepare_policy_spec import download_policy_spec_from_s3_as_zip
|
|
28
25
|
from mettagrid.policy.submission import POLICY_SPEC_FILENAME, SubmissionPolicySpec
|
|
26
|
+
from mettagrid.runner.rollout import run_episode_local
|
|
27
|
+
from mettagrid.util.uri_resolvers.schemes import parse_uri, resolve_uri
|
|
29
28
|
|
|
30
29
|
DEFAULT_SUBMIT_SERVER = "https://api.observatory.softmax-research.net"
|
|
31
30
|
|
|
@@ -51,12 +50,11 @@ def validate_paths(paths: list[str], console: Console) -> list[Path]:
|
|
|
51
50
|
raw_path = Path(path_str).expanduser()
|
|
52
51
|
|
|
53
52
|
# Resolve the path and check it's within CWD
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
relative = resolved.relative_to(cwd)
|
|
57
|
-
except ValueError:
|
|
53
|
+
resolved = raw_path.resolve() if raw_path.is_absolute() else (cwd / raw_path).resolve()
|
|
54
|
+
if not resolved.is_relative_to(cwd):
|
|
58
55
|
console.print(f"[red]Error:[/red] Path must be within the current directory: {path_str}")
|
|
59
|
-
raise ValueError(f"Path escapes CWD: {path_str}")
|
|
56
|
+
raise ValueError(f"Path escapes CWD: {path_str}")
|
|
57
|
+
relative = resolved.relative_to(cwd)
|
|
60
58
|
|
|
61
59
|
# Check if path exists
|
|
62
60
|
if not resolved.exists():
|
|
@@ -70,9 +68,6 @@ def validate_paths(paths: list[str], console: Console) -> list[Path]:
|
|
|
70
68
|
|
|
71
69
|
def _maybe_resolve_checkpoint_bundle_uri(policy: str) -> tuple[Path, bool] | None:
|
|
72
70
|
"""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
71
|
first = policy.split(",", 1)[0].strip()
|
|
77
72
|
parsed = parse_uri(first, allow_none=True, default_scheme=None)
|
|
78
73
|
if parsed is None or parsed.scheme not in {"file", "s3"}:
|
|
@@ -111,9 +106,8 @@ def _zip_directory_bundle(bundle_dir: Path) -> Path:
|
|
|
111
106
|
def validate_bundle_in_isolation(policy_zip: Path, console: Console, *, season: str, server: str) -> bool:
|
|
112
107
|
console.print("[dim]Testing policy bundle can run 10 steps...[/dim]")
|
|
113
108
|
|
|
114
|
-
temp_dir =
|
|
109
|
+
temp_dir = create_temp_validation_env()
|
|
115
110
|
try:
|
|
116
|
-
temp_dir = create_temp_validation_env()
|
|
117
111
|
bundle_name = policy_zip.name
|
|
118
112
|
shutil.copy2(policy_zip, temp_dir / bundle_name)
|
|
119
113
|
|
|
@@ -148,11 +142,8 @@ def validate_bundle_in_isolation(policy_zip: Path, console: Console, *, season:
|
|
|
148
142
|
|
|
149
143
|
console.print("[green]Validation passed[/green]")
|
|
150
144
|
return True
|
|
151
|
-
except subprocess.TimeoutExpired:
|
|
152
|
-
console.print("[red]Validation timed out after 5 minutes[/red]")
|
|
153
|
-
return False
|
|
154
145
|
finally:
|
|
155
|
-
if temp_dir
|
|
146
|
+
if temp_dir.exists():
|
|
156
147
|
shutil.rmtree(temp_dir)
|
|
157
148
|
|
|
158
149
|
|
|
@@ -214,8 +205,46 @@ def copy_files_maintaining_structure(files: list[Path], dest_dir: Path) -> None:
|
|
|
214
205
|
shutil.copy2(file_path, dest_path)
|
|
215
206
|
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
|
|
208
|
+
_SEASON_VALIDATION_MISSIONS: dict[str, str] = {
|
|
209
|
+
"beta": "training_facility.harvest",
|
|
210
|
+
"beta-cogsguard": "cogsguard_arena.basic",
|
|
211
|
+
}
|
|
212
|
+
_DEFAULT_VALIDATION_MISSION = "cogsguard_arena.basic"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_validation_mission_for_season(season: str | None = None) -> str:
|
|
216
|
+
"""Get the appropriate mission for validating policies in a given season."""
|
|
217
|
+
if season is None:
|
|
218
|
+
return _DEFAULT_VALIDATION_MISSION
|
|
219
|
+
return _SEASON_VALIDATION_MISSIONS.get(season, _DEFAULT_VALIDATION_MISSION)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def validate_policy_spec(
|
|
223
|
+
policy_spec: PolicySpec,
|
|
224
|
+
env_cfg: MettaGridConfig | None = None,
|
|
225
|
+
*,
|
|
226
|
+
device: str = "cpu",
|
|
227
|
+
season: str | None = None,
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Validate policy works.
|
|
230
|
+
|
|
231
|
+
Runs a single episode (up to 10 steps) using the same alo rollout flow as `cogames eval`.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
policy_spec: The policy to validate.
|
|
235
|
+
env_cfg: Optional environment config to validate against.
|
|
236
|
+
device: Target device for policy evaluation (cpu/cuda/auto).
|
|
237
|
+
season: Optional season name to determine which game to validate against.
|
|
238
|
+
"""
|
|
239
|
+
if env_cfg is None:
|
|
240
|
+
mission_name = get_validation_mission_for_season(season)
|
|
241
|
+
# Legacy seasons (e.g., "beta") use legacy missions that require include_legacy=True
|
|
242
|
+
include_legacy = season in _SEASON_VALIDATION_MISSIONS
|
|
243
|
+
_, env_cfg, _ = get_mission(mission_name, include_legacy=include_legacy)
|
|
244
|
+
else:
|
|
245
|
+
env_cfg = env_cfg.model_copy()
|
|
246
|
+
|
|
247
|
+
# Run 1 episode for up to 10 steps to validate the policy works
|
|
219
248
|
env_cfg.game.max_steps = 10
|
|
220
249
|
n = env_cfg.game.num_agents
|
|
221
250
|
n_submitted = min(2, n)
|
|
@@ -226,18 +255,21 @@ def validate_policy_spec(policy_spec: PolicySpec, env_cfg: MettaGridConfig) -> N
|
|
|
226
255
|
else:
|
|
227
256
|
policy_specs = [policy_spec]
|
|
228
257
|
assignments = [0] * n
|
|
229
|
-
|
|
258
|
+
run_episode_local(
|
|
230
259
|
policy_specs=policy_specs,
|
|
231
260
|
assignments=assignments,
|
|
232
261
|
env=env_cfg,
|
|
233
|
-
results_uri=None,
|
|
234
|
-
replay_uri=None,
|
|
235
262
|
seed=42,
|
|
236
263
|
max_action_time_ms=10000,
|
|
237
|
-
device=
|
|
264
|
+
device=device,
|
|
238
265
|
)
|
|
239
266
|
|
|
240
267
|
|
|
268
|
+
def validate_policy_uri(policy_uri: str, env_cfg: MettaGridConfig, *, device: str = "cpu") -> None:
|
|
269
|
+
policy_spec = parse_policy_spec(policy_uri, device=device).to_policy_spec()
|
|
270
|
+
validate_policy_spec(policy_spec, env_cfg, device=device)
|
|
271
|
+
|
|
272
|
+
|
|
241
273
|
def validate_policy_in_isolation(
|
|
242
274
|
policy_spec: PolicySpec,
|
|
243
275
|
include_files: list[Path],
|
|
@@ -257,9 +289,8 @@ def validate_policy_in_isolation(
|
|
|
257
289
|
|
|
258
290
|
console.print("[dim]Testing policy can run 10 steps...[/dim]")
|
|
259
291
|
|
|
260
|
-
temp_dir =
|
|
292
|
+
temp_dir = create_temp_validation_env()
|
|
261
293
|
try:
|
|
262
|
-
temp_dir = create_temp_validation_env()
|
|
263
294
|
copy_files_maintaining_structure(include_files, temp_dir)
|
|
264
295
|
|
|
265
296
|
policy_arg = _format_policy_arg(policy_spec)
|
|
@@ -304,17 +335,23 @@ def validate_policy_in_isolation(
|
|
|
304
335
|
|
|
305
336
|
console.print("[green]Validation passed[/green]")
|
|
306
337
|
return True
|
|
307
|
-
|
|
308
|
-
except subprocess.TimeoutExpired:
|
|
309
|
-
console.print("[red]Validation timed out after 5 minutes[/red]")
|
|
310
|
-
return False
|
|
311
|
-
except Exception:
|
|
312
|
-
return False
|
|
313
338
|
finally:
|
|
314
|
-
if temp_dir
|
|
339
|
+
if temp_dir.exists():
|
|
315
340
|
shutil.rmtree(temp_dir)
|
|
316
341
|
|
|
317
342
|
|
|
343
|
+
def _collect_ancestor_init_files(include_files: list[Path]) -> list[Path]:
|
|
344
|
+
found: set[Path] = set()
|
|
345
|
+
for path in include_files:
|
|
346
|
+
parent = path.parent
|
|
347
|
+
while parent != Path(".") and parent != parent.parent:
|
|
348
|
+
init = parent / "__init__.py"
|
|
349
|
+
if init.is_file():
|
|
350
|
+
found.add(init)
|
|
351
|
+
parent = parent.parent
|
|
352
|
+
return sorted(found)
|
|
353
|
+
|
|
354
|
+
|
|
318
355
|
def create_submission_zip(
|
|
319
356
|
include_files: list[Path],
|
|
320
357
|
policy_spec: PolicySpec,
|
|
@@ -335,17 +372,22 @@ def create_submission_zip(
|
|
|
335
372
|
setup_script=setup_script,
|
|
336
373
|
)
|
|
337
374
|
|
|
375
|
+
all_files: dict[str, Path] = {}
|
|
376
|
+
for init_path in _collect_ancestor_init_files(include_files):
|
|
377
|
+
all_files[str(init_path)] = init_path
|
|
378
|
+
for file_path in include_files:
|
|
379
|
+
if file_path.is_dir():
|
|
380
|
+
for root, _, files in os.walk(file_path):
|
|
381
|
+
for file in files:
|
|
382
|
+
full = Path(root) / file
|
|
383
|
+
all_files[str(full)] = full
|
|
384
|
+
else:
|
|
385
|
+
all_files[str(file_path)] = file_path
|
|
386
|
+
|
|
338
387
|
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
339
388
|
zipf.writestr(data=submission_spec.model_dump_json(), zinfo_or_arcname=POLICY_SPEC_FILENAME)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if file_path.is_dir():
|
|
343
|
-
for root, _, files in os.walk(file_path):
|
|
344
|
-
for file in files:
|
|
345
|
-
file_full_path = Path(root) / file
|
|
346
|
-
zipf.write(file_full_path, arcname=file_full_path)
|
|
347
|
-
else:
|
|
348
|
-
zipf.write(file_path, arcname=file_path)
|
|
389
|
+
for arcname, path in all_files.items():
|
|
390
|
+
zipf.write(path, arcname=arcname)
|
|
349
391
|
|
|
350
392
|
return Path(zip_path)
|
|
351
393
|
|
|
@@ -360,82 +402,45 @@ def upload_submission(
|
|
|
360
402
|
"""Upload submission to CoGames backend using a presigned S3 URL."""
|
|
361
403
|
console.print("[bold]Uploading[/bold]")
|
|
362
404
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
upload_id = presigned_data.get("upload_id")
|
|
405
|
+
presigned_data = client.get_presigned_upload_url()
|
|
406
|
+
upload_url = presigned_data.get("upload_url")
|
|
407
|
+
upload_id = presigned_data.get("upload_id")
|
|
367
408
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return None
|
|
371
|
-
except httpx.TimeoutException:
|
|
372
|
-
console.print("[red]Timed out while requesting upload URL[/red]")
|
|
373
|
-
return None
|
|
374
|
-
except httpx.HTTPStatusError as exc:
|
|
375
|
-
console.print(f"[red]Failed to get upload URL ({exc.response.status_code})[/red]")
|
|
376
|
-
console.print(f"[dim]{exc.response.text}[/dim]")
|
|
377
|
-
return None
|
|
378
|
-
except Exception as e:
|
|
379
|
-
console.print(f"[red]Error requesting upload URL: {e}[/red]")
|
|
380
|
-
return None
|
|
409
|
+
if not upload_url or not upload_id:
|
|
410
|
+
raise ValueError("Upload URL missing from response")
|
|
381
411
|
|
|
382
412
|
console.print("[dim]Uploading to storage...[/dim]")
|
|
383
413
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
upload_response.raise_for_status()
|
|
393
|
-
except httpx.TimeoutException:
|
|
394
|
-
console.print("[red]Upload timed out after 10 minutes[/red]")
|
|
395
|
-
return None
|
|
396
|
-
except httpx.HTTPStatusError as exc:
|
|
397
|
-
console.print(f"[red]Upload failed with status {exc.response.status_code}[/red]")
|
|
398
|
-
console.print(f"[dim]{exc.response.text}[/dim]")
|
|
399
|
-
return None
|
|
400
|
-
except Exception as e:
|
|
401
|
-
console.print(f"[red]Upload error: {e}[/red]")
|
|
402
|
-
return None
|
|
414
|
+
with open(zip_path, "rb") as f:
|
|
415
|
+
upload_response = httpx.put(
|
|
416
|
+
upload_url,
|
|
417
|
+
content=f,
|
|
418
|
+
headers={"Content-Type": "application/zip"},
|
|
419
|
+
timeout=600.0,
|
|
420
|
+
)
|
|
421
|
+
upload_response.raise_for_status()
|
|
403
422
|
|
|
404
423
|
if not season:
|
|
405
424
|
console.print("[dim]Uploading policy...[/dim]")
|
|
406
425
|
else:
|
|
407
426
|
console.print(f"[dim]Uploading policy and submitting to season {season}...[/dim]")
|
|
408
427
|
|
|
428
|
+
result = client.complete_policy_upload(upload_id, submission_name, season=season)
|
|
429
|
+
submission_id = result.get("id")
|
|
430
|
+
name = result.get("name")
|
|
431
|
+
version = result.get("version")
|
|
432
|
+
pools = result.get("pools")
|
|
433
|
+
if submission_id is None or name is None or version is None:
|
|
434
|
+
raise ValueError("Missing fields in response")
|
|
409
435
|
try:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
policy_version_id=uuid.UUID(str(submission_id)),
|
|
419
|
-
name=name,
|
|
420
|
-
version=version,
|
|
421
|
-
pools=pools,
|
|
422
|
-
)
|
|
423
|
-
except ValueError:
|
|
424
|
-
console.print(f"[red]Invalid submission ID returned: {submission_id}[/red]")
|
|
425
|
-
return None
|
|
426
|
-
|
|
427
|
-
console.print("[red]Missing fields in response[/red]")
|
|
428
|
-
return None
|
|
429
|
-
except httpx.TimeoutException:
|
|
430
|
-
console.print("[red]Registration timed out[/red]")
|
|
431
|
-
return None
|
|
432
|
-
except httpx.HTTPStatusError as exc:
|
|
433
|
-
console.print(f"[red]Registration failed with status {exc.response.status_code}[/red]")
|
|
434
|
-
console.print(f"[dim]{exc.response.text}[/dim]")
|
|
435
|
-
return None
|
|
436
|
-
except Exception as e:
|
|
437
|
-
console.print(f"[red]Registration error: {e}[/red]")
|
|
438
|
-
return None
|
|
436
|
+
return UploadResult(
|
|
437
|
+
policy_version_id=uuid.UUID(str(submission_id)),
|
|
438
|
+
name=name,
|
|
439
|
+
version=version,
|
|
440
|
+
pools=pools,
|
|
441
|
+
)
|
|
442
|
+
except ValueError as exc:
|
|
443
|
+
raise ValueError(f"Invalid submission ID returned: {submission_id}") from exc
|
|
439
444
|
|
|
440
445
|
|
|
441
446
|
def _upload_policy_bundle(
|
|
@@ -501,8 +506,6 @@ def upload_policy(
|
|
|
501
506
|
validation_season: str = "",
|
|
502
507
|
season: str | None = None,
|
|
503
508
|
) -> UploadResult | None:
|
|
504
|
-
from cogames.cli.client import TournamentServerClient
|
|
505
|
-
|
|
506
509
|
if dry_run:
|
|
507
510
|
console.print("[dim]Dry run mode - no upload[/dim]\n")
|
|
508
511
|
|
|
@@ -510,11 +513,7 @@ def upload_policy(
|
|
|
510
513
|
if not client:
|
|
511
514
|
return None
|
|
512
515
|
|
|
513
|
-
|
|
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
|
|
516
|
+
bundle_result = _maybe_resolve_checkpoint_bundle_uri(policy)
|
|
518
517
|
|
|
519
518
|
if bundle_result is not None:
|
|
520
519
|
return _upload_policy_bundle(
|
|
@@ -532,11 +531,7 @@ def upload_policy(
|
|
|
532
531
|
season=season,
|
|
533
532
|
)
|
|
534
533
|
|
|
535
|
-
|
|
536
|
-
policy_spec = get_policy_spec(ctx, policy)
|
|
537
|
-
except Exception as e:
|
|
538
|
-
console.print(f"[red]Error parsing policy:[/red] {e}")
|
|
539
|
-
return None
|
|
534
|
+
policy_spec = get_policy_spec(ctx, policy)
|
|
540
535
|
|
|
541
536
|
if init_kwargs:
|
|
542
537
|
merged_kwargs = {**policy_spec.init_kwargs, **init_kwargs}
|
|
@@ -548,13 +543,12 @@ def upload_policy(
|
|
|
548
543
|
|
|
549
544
|
cwd = Path.cwd().resolve()
|
|
550
545
|
if policy_spec.data_path:
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
data_rel = str(resolved.relative_to(cwd))
|
|
554
|
-
except ValueError:
|
|
546
|
+
resolved = Path(policy_spec.data_path).expanduser().resolve()
|
|
547
|
+
if not resolved.is_relative_to(cwd):
|
|
555
548
|
console.print("[red]Error:[/red] Policy weights path must be within the current directory.")
|
|
556
549
|
console.print(f"[dim]{policy_spec.data_path}[/dim]")
|
|
557
|
-
|
|
550
|
+
raise ValueError("Policy weights path must be within the current directory.")
|
|
551
|
+
data_rel = str(resolved.relative_to(cwd))
|
|
558
552
|
policy_spec = PolicySpec(
|
|
559
553
|
class_path=policy_spec.class_path,
|
|
560
554
|
data_path=data_rel,
|
|
@@ -563,13 +557,12 @@ def upload_policy(
|
|
|
563
557
|
|
|
564
558
|
setup_script_rel: str | None = None
|
|
565
559
|
if setup_script:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
setup_script_rel = str(resolved.relative_to(cwd))
|
|
569
|
-
except ValueError:
|
|
560
|
+
resolved = Path(setup_script).expanduser().resolve()
|
|
561
|
+
if not resolved.is_relative_to(cwd):
|
|
570
562
|
console.print("[red]Error:[/red] Setup script path must be within the current directory.")
|
|
571
563
|
console.print(f"[dim]{setup_script}[/dim]")
|
|
572
|
-
|
|
564
|
+
raise ValueError("Setup script path must be within the current directory.")
|
|
565
|
+
setup_script_rel = str(resolved.relative_to(cwd))
|
|
573
566
|
|
|
574
567
|
files_to_include = []
|
|
575
568
|
if policy_spec.data_path:
|
|
@@ -581,10 +574,7 @@ def upload_policy(
|
|
|
581
574
|
|
|
582
575
|
validated_paths: list[Path] = []
|
|
583
576
|
if files_to_include:
|
|
584
|
-
|
|
585
|
-
validated_paths = validate_paths(files_to_include, console)
|
|
586
|
-
except (ValueError, FileNotFoundError):
|
|
587
|
-
return None
|
|
577
|
+
validated_paths = validate_paths(files_to_include, console)
|
|
588
578
|
|
|
589
579
|
if not skip_validation:
|
|
590
580
|
if not validate_policy_in_isolation(
|
|
@@ -600,11 +590,7 @@ def upload_policy(
|
|
|
600
590
|
else:
|
|
601
591
|
console.print("[dim]Skipping validation[/dim]")
|
|
602
592
|
|
|
603
|
-
|
|
604
|
-
zip_path = create_submission_zip(validated_paths, policy_spec, setup_script=setup_script_rel)
|
|
605
|
-
except Exception as e:
|
|
606
|
-
console.print(f"[red]Error creating zip:[/red] {e}")
|
|
607
|
-
return None
|
|
593
|
+
zip_path = create_submission_zip(validated_paths, policy_spec, setup_script=setup_script_rel)
|
|
608
594
|
|
|
609
595
|
if dry_run:
|
|
610
596
|
console.print("[green]Dry run complete[/green]")
|
cogames/cli/utils.py
CHANGED
|
@@ -11,6 +11,11 @@ def suppress_noisy_logs() -> None:
|
|
|
11
11
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="pkg_resources")
|
|
12
12
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="pygame.pkgdata")
|
|
13
13
|
|
|
14
|
+
# Pyro docstrings use LaTeX math notation (\ge for ≥) which Python 3.12+ warns about:
|
|
15
|
+
# pyro/ops/stats.py:527: SyntaxWarning: invalid escape sequence '\g'
|
|
16
|
+
# Note: module= filter doesn't work for SyntaxWarnings (emitted at parse time before module loads)
|
|
17
|
+
warnings.filterwarnings("ignore", category=SyntaxWarning, message=r".*invalid escape sequence.*")
|
|
18
|
+
|
|
14
19
|
# Silence PyTorch distributed elastic warning about redirects on MacOS/Windows
|
|
15
20
|
logging.getLogger("torch.distributed.elastic.multiprocessing.redirects").setLevel(logging.ERROR)
|
|
16
21
|
warnings.filterwarnings(
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Final, Optional
|
|
4
|
+
|
|
5
|
+
from cogames.cogs_vs_clips.mission import CvCMission
|
|
6
|
+
from cogames.core import CoGameMissionVariant
|
|
7
|
+
|
|
8
|
+
MEDIUM_CLIPS_END: Final[int] = 300
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CogsGuardDifficulty(CoGameMissionVariant):
|
|
12
|
+
name: str
|
|
13
|
+
description: str = ""
|
|
14
|
+
disable_clips: bool = False
|
|
15
|
+
scramble_end: Optional[int] = None
|
|
16
|
+
align_end: Optional[int] = None
|
|
17
|
+
presence_end: Optional[int] = None
|
|
18
|
+
|
|
19
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
20
|
+
if self.disable_clips:
|
|
21
|
+
mission.clips.disabled = True
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
mission.clips.disabled = False
|
|
25
|
+
mission.clips.scramble_end = self.scramble_end
|
|
26
|
+
mission.clips.align_end = self.align_end
|
|
27
|
+
mission.clips.presence_end = self.presence_end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
EASY = CogsGuardDifficulty(
|
|
31
|
+
name="easy",
|
|
32
|
+
description="No clips events.",
|
|
33
|
+
disable_clips=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
MEDIUM = CogsGuardDifficulty(
|
|
37
|
+
name="medium",
|
|
38
|
+
description="A few early clips events, then none.",
|
|
39
|
+
scramble_end=MEDIUM_CLIPS_END,
|
|
40
|
+
align_end=MEDIUM_CLIPS_END,
|
|
41
|
+
presence_end=MEDIUM_CLIPS_END,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
HARD = CogsGuardDifficulty(
|
|
45
|
+
name="hard",
|
|
46
|
+
description="Standard clips event system.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
COGSGUARD_DIFFICULTIES: tuple[CogsGuardDifficulty, ...] = (EASY, MEDIUM, HARD)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_cogsguard_difficulty(name: str) -> CogsGuardDifficulty:
|
|
53
|
+
for difficulty in COGSGUARD_DIFFICULTIES:
|
|
54
|
+
if difficulty.name == name:
|
|
55
|
+
return difficulty
|
|
56
|
+
available = ", ".join(d.name for d in COGSGUARD_DIFFICULTIES)
|
|
57
|
+
raise ValueError(f"Unknown difficulty '{name}'. Available difficulties: {available}")
|
cogames/cogs_vs_clips/clips.py
CHANGED
|
@@ -4,6 +4,8 @@ Clips are a non-player faction that gradually takes over neutral junctions.
|
|
|
4
4
|
These events create the spreading/scrambling behavior that pressures players.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
7
9
|
from pydantic import Field
|
|
8
10
|
|
|
9
11
|
from mettagrid.base_config import Config
|
|
@@ -18,6 +20,8 @@ from mettagrid.config.tag import typeTag
|
|
|
18
20
|
class ClipsConfig(Config):
|
|
19
21
|
"""Configuration for clips behavior in CogsGuard game mode."""
|
|
20
22
|
|
|
23
|
+
disabled: bool = Field(default=False)
|
|
24
|
+
|
|
21
25
|
# Clips Behavior - scramble cogs junctions to neutral
|
|
22
26
|
initial_clips_start: int = Field(default=10)
|
|
23
27
|
initial_clips_spots: int = Field(default=1)
|
|
@@ -25,11 +29,16 @@ class ClipsConfig(Config):
|
|
|
25
29
|
scramble_start: int = Field(default=50)
|
|
26
30
|
scramble_interval: int = Field(default=100)
|
|
27
31
|
scramble_radius: int = Field(default=25)
|
|
32
|
+
scramble_end: Optional[int] = Field(default=None)
|
|
28
33
|
|
|
29
34
|
# Clips Behavior - align neutral junctions to clips
|
|
30
35
|
align_start: int = Field(default=100)
|
|
31
36
|
align_interval: int = Field(default=100)
|
|
32
37
|
align_radius: int = Field(default=25)
|
|
38
|
+
align_end: Optional[int] = Field(default=None)
|
|
39
|
+
|
|
40
|
+
# Clips Behavior - presence check for re-invasion
|
|
41
|
+
presence_end: Optional[int] = Field(default=None)
|
|
33
42
|
|
|
34
43
|
def events(self, max_steps: int) -> dict[str, EventConfig]:
|
|
35
44
|
"""Create all clips events for a mission.
|
|
@@ -37,6 +46,12 @@ class ClipsConfig(Config):
|
|
|
37
46
|
Returns:
|
|
38
47
|
Dictionary of event name to EventConfig.
|
|
39
48
|
"""
|
|
49
|
+
if self.disabled:
|
|
50
|
+
return {}
|
|
51
|
+
scramble_end = max_steps if self.scramble_end is None else min(self.scramble_end, max_steps)
|
|
52
|
+
align_end = max_steps if self.align_end is None else min(self.align_end, max_steps)
|
|
53
|
+
presence_end = max_steps if self.presence_end is None else min(self.presence_end, max_steps)
|
|
54
|
+
|
|
40
55
|
return {
|
|
41
56
|
"initial_clips": EventConfig(
|
|
42
57
|
name="initial_clips",
|
|
@@ -48,7 +63,7 @@ class ClipsConfig(Config):
|
|
|
48
63
|
"cogs_to_neutral": EventConfig(
|
|
49
64
|
name="cogs_to_neutral",
|
|
50
65
|
target_tag=typeTag("junction"),
|
|
51
|
-
timesteps=periodic(start=self.scramble_start, period=self.scramble_interval, end=
|
|
66
|
+
timesteps=periodic(start=self.scramble_start, period=self.scramble_interval, end=scramble_end),
|
|
52
67
|
# near a clips-aligned junction
|
|
53
68
|
filters=[
|
|
54
69
|
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.scramble_radius),
|
|
@@ -61,7 +76,7 @@ class ClipsConfig(Config):
|
|
|
61
76
|
"neutral_to_clips": EventConfig(
|
|
62
77
|
name="neutral_to_clips",
|
|
63
78
|
target_tag=typeTag("junction"),
|
|
64
|
-
timesteps=periodic(start=self.align_start, period=self.align_interval, end=
|
|
79
|
+
timesteps=periodic(start=self.align_start, period=self.align_interval, end=align_end),
|
|
65
80
|
# neutral junctions near a clips-aligned junction
|
|
66
81
|
filters=[
|
|
67
82
|
isNear(typeTag("junction"), [isAlignedTo("clips")], radius=self.align_radius),
|
|
@@ -74,13 +89,15 @@ class ClipsConfig(Config):
|
|
|
74
89
|
"presence_check": EventConfig(
|
|
75
90
|
name="presence_check",
|
|
76
91
|
target_tag=typeTag("junction"),
|
|
77
|
-
timesteps=periodic(start=self.initial_clips_start, period=self.scramble_interval * 2, end=
|
|
92
|
+
timesteps=periodic(start=self.initial_clips_start, period=self.scramble_interval * 2, end=presence_end),
|
|
78
93
|
filters=[isNear(typeTag("junction"), [isAlignedTo("clips")], radius=1000)],
|
|
79
94
|
max_targets=1,
|
|
80
95
|
fallback="initial_clips",
|
|
81
96
|
),
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
def
|
|
85
|
-
"""Create
|
|
86
|
-
|
|
99
|
+
def collectives(self) -> dict[str, CollectiveConfig]:
|
|
100
|
+
"""Create collectives for clips."""
|
|
101
|
+
if self.disabled:
|
|
102
|
+
return {}
|
|
103
|
+
return {"clips": CollectiveConfig(name="clips")}
|
cogames/cogs_vs_clips/cog.py
CHANGED
|
@@ -4,6 +4,7 @@ from pydantic import Field
|
|
|
4
4
|
|
|
5
5
|
from cogames.cogs_vs_clips.config import CvCConfig
|
|
6
6
|
from mettagrid.base_config import Config
|
|
7
|
+
from mettagrid.config.game_value import InventoryValue
|
|
7
8
|
from mettagrid.config.game_value import stat as game_stat
|
|
8
9
|
from mettagrid.config.handler_config import Handler
|
|
9
10
|
from mettagrid.config.mettagrid_config import (
|
|
@@ -11,6 +12,8 @@ from mettagrid.config.mettagrid_config import (
|
|
|
11
12
|
InventoryConfig,
|
|
12
13
|
ResourceLimitsConfig,
|
|
13
14
|
)
|
|
15
|
+
from mettagrid.config.mutation.game_value_mutation import SetGameValueMutation
|
|
16
|
+
from mettagrid.config.mutation.mutation import EntityTarget
|
|
14
17
|
from mettagrid.config.mutation.resource_mutation import updateActor
|
|
15
18
|
from mettagrid.config.reward_config import reward
|
|
16
19
|
|
|
@@ -35,12 +38,12 @@ class CogConfig(Config):
|
|
|
35
38
|
# Initial inventory
|
|
36
39
|
initial_energy: int = Field(default=100)
|
|
37
40
|
initial_hp: int = Field(default=50)
|
|
41
|
+
initial_solar: int = Field(default=1)
|
|
38
42
|
|
|
39
43
|
# Regen amounts
|
|
40
|
-
energy_regen: int = Field(default=1)
|
|
41
44
|
hp_regen: int = Field(default=-1)
|
|
42
45
|
influence_regen: int = Field(default=-1)
|
|
43
|
-
action_cost: dict[str, int] = Field(default_factory=lambda: {"energy":
|
|
46
|
+
action_cost: dict[str, int] = Field(default_factory=lambda: {"energy": 4})
|
|
44
47
|
|
|
45
48
|
def agent_config(self, team: str, max_steps: int) -> AgentConfig:
|
|
46
49
|
"""Create an AgentConfig for this cog configuration."""
|
|
@@ -62,20 +65,28 @@ class CogConfig(Config):
|
|
|
62
65
|
min=self.influence_limit, resources=["influence"], modifiers=self.influence_modifiers
|
|
63
66
|
),
|
|
64
67
|
},
|
|
65
|
-
initial={"energy": self.initial_energy, "hp": self.initial_hp},
|
|
68
|
+
initial={"energy": self.initial_energy, "hp": self.initial_hp, "solar": self.initial_solar},
|
|
66
69
|
),
|
|
67
70
|
on_tick={
|
|
68
71
|
"regen": Handler(
|
|
69
72
|
mutations=[
|
|
70
73
|
updateActor(
|
|
71
74
|
{
|
|
72
|
-
"energy": self.energy_regen,
|
|
73
75
|
"hp": self.hp_regen,
|
|
74
76
|
"influence": self.influence_regen,
|
|
75
77
|
}
|
|
76
78
|
)
|
|
77
79
|
]
|
|
78
|
-
)
|
|
80
|
+
),
|
|
81
|
+
"solar_to_energy": Handler(
|
|
82
|
+
mutations=[
|
|
83
|
+
SetGameValueMutation(
|
|
84
|
+
value=InventoryValue(item="energy"),
|
|
85
|
+
source=InventoryValue(item="solar"),
|
|
86
|
+
target=EntityTarget.ACTOR,
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
),
|
|
79
90
|
},
|
|
80
91
|
rewards={
|
|
81
92
|
"aligned_junction_held": reward(
|