cogames-agents 0.0.0.7__cp312-cp312-macosx_11_0_arm64.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_agents/__init__.py +0 -0
- cogames_agents/evals/__init__.py +5 -0
- cogames_agents/evals/planky_evals.py +415 -0
- cogames_agents/policy/__init__.py +0 -0
- cogames_agents/policy/evolution/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
- cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
- cogames_agents/policy/nim_agents/__init__.py +20 -0
- cogames_agents/policy/nim_agents/agents.py +98 -0
- cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
- cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
- cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
- cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
- cogames_agents/policy/nim_agents/common.nim +1054 -0
- cogames_agents/policy/nim_agents/install.sh +1 -0
- cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
- cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
- cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
- cogames_agents/policy/nim_agents/nimby.lock +3 -0
- cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
- cogames_agents/policy/nim_agents/random_agents.nim +68 -0
- cogames_agents/policy/nim_agents/test_agents.py +53 -0
- cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
- cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
- cogames_agents/policy/scripted_agent/README.md +360 -0
- cogames_agents/policy/scripted_agent/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
- cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
- cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
- cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
- cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
- cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
- cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
- cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
- cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
- cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
- cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
- cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
- cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
- cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
- cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
- cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
- cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
- cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
- cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
- cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
- cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
- cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
- cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
- cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
- cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
- cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
- cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
- cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
- cogames_agents/policy/scripted_agent/common/roles.py +34 -0
- cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
- cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
- cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
- cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
- cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
- cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
- cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
- cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
- cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
- cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
- cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
- cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
- cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
- cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
- cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
- cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
- cogames_agents/policy/scripted_agent/planky/README.md +214 -0
- cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
- cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/planky/context.py +68 -0
- cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
- cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
- cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
- cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
- cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
- cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
- cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
- cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
- cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
- cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
- cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
- cogames_agents/policy/scripted_agent/types.py +239 -0
- cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
- cogames_agents/policy/scripted_agent/utils.py +381 -0
- cogames_agents/policy/scripted_registry.py +80 -0
- cogames_agents/py.typed +0 -0
- cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
- cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
- cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
- cogames_agents-0.0.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Capability tests for the Aligner role."""
|
|
2
|
+
|
|
3
|
+
from cogames_agents.policy.scripted_agent.planky.tests.helpers import EpisodeResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_aligner_acquires_gear(aligner_episode: EpisodeResult):
|
|
7
|
+
gained = aligner_episode.gear_gained("aligner")
|
|
8
|
+
assert gained > 0, (
|
|
9
|
+
f"Aligner did not acquire gear (aligner.gained={gained})\nTrace:\n{aligner_episode.trace.summary()}"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_aligner_acquires_hearts(aligner_episode: EpisodeResult):
|
|
14
|
+
hearts = aligner_episode.hearts_gained()
|
|
15
|
+
assert hearts > 0, (
|
|
16
|
+
f"Aligner did not acquire hearts (heart.gained={hearts})\nTrace:\n{aligner_episode.trace.summary()}"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_aligner_aligns_junctions(aligner_episode: EpisodeResult):
|
|
21
|
+
aligned = aligner_episode.junctions_aligned()
|
|
22
|
+
assert aligned > 0, (
|
|
23
|
+
f"Aligner did not align any junctions (junction.gained={aligned})\nTrace:\n{aligner_episode.trace.summary()}"
|
|
24
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Capability tests for the Miner role."""
|
|
2
|
+
|
|
3
|
+
from cogames_agents.policy.scripted_agent.planky.tests.helpers import EpisodeResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_miner_acquires_gear(miner_episode: EpisodeResult):
|
|
7
|
+
gained = miner_episode.gear_gained("miner")
|
|
8
|
+
assert gained > 0, f"Miner did not acquire gear (miner.gained={gained})\nTrace:\n{miner_episode.trace.summary()}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_miner_deposits_resources(miner_episode: EpisodeResult):
|
|
12
|
+
total = miner_episode.total_deposited()
|
|
13
|
+
assert total > 0, (
|
|
14
|
+
f"Miner did not deposit any resources (total_deposited={total})\nTrace:\n{miner_episode.trace.summary()}"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_miner_mines_multiple_elements(miner_episode: EpisodeResult):
|
|
19
|
+
elements = ["carbon", "oxygen", "germanium", "silicon"]
|
|
20
|
+
deposited = {e: miner_episode.resource_deposited(e) for e in elements}
|
|
21
|
+
non_zero = [e for e, v in deposited.items() if v > 0]
|
|
22
|
+
assert len(non_zero) >= 1, f"Miner did not mine any elements: {deposited}\nTrace:\n{miner_episode.trace.summary()}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_miner_stays_productive(miner_episode: EpisodeResult):
|
|
26
|
+
"""Miner should deposit a meaningful amount of resources in 500 steps."""
|
|
27
|
+
total = miner_episode.total_deposited()
|
|
28
|
+
assert total >= 10, (
|
|
29
|
+
f"Miner deposited too few resources ({total}), may be stuck or idle\nTrace:\n{miner_episode.trace.summary()}"
|
|
30
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Capability tests for the Scout role."""
|
|
2
|
+
|
|
3
|
+
from cogames_agents.policy.scripted_agent.planky.tests.helpers import EpisodeResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_scout_acquires_gear(scout_episode: EpisodeResult):
|
|
7
|
+
gained = scout_episode.gear_gained("scout")
|
|
8
|
+
assert gained > 0, f"Scout did not acquire gear (scout.gained={gained})\nTrace:\n{scout_episode.trace.summary()}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_scout_explores(scout_episode: EpisodeResult):
|
|
12
|
+
"""Scout should be actively exploring (Explore goal activated in trace)."""
|
|
13
|
+
assert scout_episode.trace.had_goal("Explore"), (
|
|
14
|
+
f"Scout never activated Explore goal\nTrace:\n{scout_episode.trace.summary()}"
|
|
15
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Capability tests for the Scrambler role."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from cogames_agents.policy.scripted_agent.planky.tests.helpers import EpisodeResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_scrambler_acquires_gear(scrambler_episode: EpisodeResult):
|
|
9
|
+
gained = scrambler_episode.gear_gained("scrambler")
|
|
10
|
+
assert gained > 0, (
|
|
11
|
+
f"Scrambler did not acquire gear (scrambler.gained={gained})\nTrace:\n{scrambler_episode.trace.summary()}"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_scrambler_acquires_hearts(scrambler_episode: EpisodeResult):
|
|
16
|
+
hearts = scrambler_episode.hearts_gained()
|
|
17
|
+
assert hearts > 0, (
|
|
18
|
+
f"Scrambler did not acquire hearts (heart.gained={hearts})\nTrace:\n{scrambler_episode.trace.summary()}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.xfail(reason="Scrambler cannot reach enemy junctions in 500 steps with limited economy")
|
|
23
|
+
def test_scrambler_scrambles_junctions(scrambler_episode: EpisodeResult):
|
|
24
|
+
# Check for scrambled junctions in clips stats (enemy junctions neutralized)
|
|
25
|
+
clips_aligned = int(scrambler_episode.clips_stats.get("junction.lost", 0))
|
|
26
|
+
assert clips_aligned > 0, (
|
|
27
|
+
f"Scrambler did not scramble any enemy junctions (clips junction.lost={clips_aligned})\n"
|
|
28
|
+
f"Trace:\n{scrambler_episode.trace.summary()}"
|
|
29
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Capability tests for the Stem (dynamic role selection) system."""
|
|
2
|
+
|
|
3
|
+
from cogames_agents.policy.scripted_agent.planky.tests.helpers import EpisodeResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_stem_agents_select_roles(stem_episode: EpisodeResult):
|
|
7
|
+
changes = stem_episode.trace.role_changes
|
|
8
|
+
assert len(changes) > 0, f"No role changes detected in trace\nTrace:\n{stem_episode.trace.summary()}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_stem_multiple_roles_represented(stem_episode: EpisodeResult):
|
|
12
|
+
roles_seen = set()
|
|
13
|
+
for line in stem_episode.trace.role_changes:
|
|
14
|
+
# Format: "[planky][t=5 a=1] role: stem→miner"
|
|
15
|
+
if "→" in line:
|
|
16
|
+
role = line.split("→")[-1].strip()
|
|
17
|
+
roles_seen.add(role)
|
|
18
|
+
assert len(roles_seen) >= 2, (
|
|
19
|
+
f"Only {len(roles_seen)} role(s) selected: {roles_seen}. "
|
|
20
|
+
f"Expected at least 2 distinct roles.\n"
|
|
21
|
+
f"Trace:\n{stem_episode.trace.summary()}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_stem_economy_produces_resources(stem_episode: EpisodeResult):
|
|
26
|
+
total = stem_episode.total_deposited()
|
|
27
|
+
assert total > 0, (
|
|
28
|
+
f"Stem team did not deposit any resources (total_deposited={total})\nTrace:\n{stem_episode.trace.summary()}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_stem_pipeline_aligns_junctions(stem_episode: EpisodeResult):
|
|
33
|
+
aligned = stem_episode.junctions_aligned()
|
|
34
|
+
assert aligned > 0, (
|
|
35
|
+
f"Stem team did not align any junctions (junction.gained={aligned})\nTrace:\n{stem_episode.trace.summary()}"
|
|
36
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Tracing system for Planky policy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TraceEntry:
|
|
11
|
+
"""One goal evaluation entry."""
|
|
12
|
+
|
|
13
|
+
goal_name: str
|
|
14
|
+
satisfied: bool
|
|
15
|
+
detail: str = ""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class TraceLog:
|
|
20
|
+
"""Collects trace information during a single tick."""
|
|
21
|
+
|
|
22
|
+
entries: list[TraceEntry] = field(default_factory=list)
|
|
23
|
+
active_goal_chain: str = ""
|
|
24
|
+
action_name: str = ""
|
|
25
|
+
blackboard_summary: str = ""
|
|
26
|
+
nav_target: Optional[tuple[int, int]] = None
|
|
27
|
+
steps_since_useful: int = 0 # Steps since last useful action (mine/deposit/align/scramble)
|
|
28
|
+
|
|
29
|
+
def skip(self, goal_name: str, reason: str = "ok") -> None:
|
|
30
|
+
"""Record a satisfied (skipped) goal."""
|
|
31
|
+
self.entries.append(TraceEntry(goal_name=goal_name, satisfied=True, detail=reason))
|
|
32
|
+
|
|
33
|
+
def activate(self, goal_name: str, detail: str = "") -> None:
|
|
34
|
+
"""Record an activated (unsatisfied) goal."""
|
|
35
|
+
self.entries.append(TraceEntry(goal_name=goal_name, satisfied=False, detail=detail))
|
|
36
|
+
|
|
37
|
+
def format_line(
|
|
38
|
+
self,
|
|
39
|
+
step: int,
|
|
40
|
+
agent_id: int,
|
|
41
|
+
role: str,
|
|
42
|
+
pos: tuple[int, int],
|
|
43
|
+
hp: int,
|
|
44
|
+
level: int,
|
|
45
|
+
) -> str:
|
|
46
|
+
"""Format the trace as a single line."""
|
|
47
|
+
# Include idle indicator if agent hasn't done anything useful recently
|
|
48
|
+
idle_str = f" IDLE={self.steps_since_useful}" if self.steps_since_useful >= 20 else ""
|
|
49
|
+
prefix = f"[t={step} a={agent_id} {role} ({pos[0]},{pos[1]}) hp={hp}{idle_str}]"
|
|
50
|
+
|
|
51
|
+
if level == 1:
|
|
52
|
+
return f"{prefix} {self.active_goal_chain} → {self.action_name}"
|
|
53
|
+
|
|
54
|
+
if level == 2:
|
|
55
|
+
skips = " ".join(f"skip:{e.goal_name}({e.detail})" for e in self.entries if e.satisfied)
|
|
56
|
+
target_str = ""
|
|
57
|
+
if self.nav_target:
|
|
58
|
+
dist = abs(self.nav_target[0] - pos[0]) + abs(self.nav_target[1] - pos[1])
|
|
59
|
+
target_str = f" dist={dist}"
|
|
60
|
+
bb = f" | bb={{{self.blackboard_summary}}}" if self.blackboard_summary else ""
|
|
61
|
+
idle_detail = f" idle={self.steps_since_useful}" if self.steps_since_useful > 0 else ""
|
|
62
|
+
return f"{prefix} {skips} → {self.active_goal_chain}{target_str} → {self.action_name}{bb}{idle_detail}"
|
|
63
|
+
|
|
64
|
+
# Level 3 — full detail
|
|
65
|
+
all_entries = " ".join(f"{'skip' if e.satisfied else 'ACTIVE'}:{e.goal_name}({e.detail})" for e in self.entries)
|
|
66
|
+
target_str = f" nav_target={self.nav_target}" if self.nav_target else ""
|
|
67
|
+
bb = f" bb={{{self.blackboard_summary}}}" if self.blackboard_summary else ""
|
|
68
|
+
idle_detail = f" idle={self.steps_since_useful}"
|
|
69
|
+
return f"{prefix} {all_entries}{target_str} → {self.action_name}{bb}{idle_detail}"
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data types and structures for scripted agents.
|
|
3
|
+
|
|
4
|
+
This module contains all the dataclasses, enums, and type definitions
|
|
5
|
+
used by the baseline and unclipping agents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from mettagrid.simulator import Action
|
|
15
|
+
from mettagrid.simulator.interface import AgentObservation
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class BaselineHyperparameters:
|
|
20
|
+
"""Hyperparameters controlling baseline agent behavior."""
|
|
21
|
+
|
|
22
|
+
# Energy management (recharge timing)
|
|
23
|
+
recharge_threshold_low: int = 35 # Enter RECHARGE phase when energy < this
|
|
24
|
+
recharge_threshold_high: int = 85 # Exit RECHARGE phase when energy >= this
|
|
25
|
+
|
|
26
|
+
# Stuck detection and escape
|
|
27
|
+
stuck_detection_enabled: bool = True # Enable loop detection
|
|
28
|
+
stuck_escape_distance: int = 12 # Minimum distance for escape target
|
|
29
|
+
|
|
30
|
+
# Exploration parameters
|
|
31
|
+
position_history_size: int = 30 # Size of position history buffer
|
|
32
|
+
exploration_area_check_window: int = 30 # Steps to check for stuck area
|
|
33
|
+
exploration_area_size_threshold: int = 7 # Max area size (height/width) to trigger escape
|
|
34
|
+
exploration_escape_duration: int = 10 # Steps to navigate to hub when stuck
|
|
35
|
+
exploration_direction_persistence: int = 10 # Steps to persist in one direction
|
|
36
|
+
exploration_hub_distance_threshold: int = 10 # Min distance from hub to trigger escape
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Hyperparameter Presets for Ensemble Creation
|
|
40
|
+
BASELINE_HYPERPARAMETER_PRESETS = {
|
|
41
|
+
"default": BaselineHyperparameters(
|
|
42
|
+
recharge_threshold_low=35, # Moderate energy management
|
|
43
|
+
recharge_threshold_high=85,
|
|
44
|
+
stuck_detection_enabled=True,
|
|
45
|
+
stuck_escape_distance=12,
|
|
46
|
+
position_history_size=40, # Thorough exploration: longer history
|
|
47
|
+
exploration_area_check_window=35, # Thorough exploration: longer check window
|
|
48
|
+
exploration_area_size_threshold=9, # Thorough exploration: larger area tolerance
|
|
49
|
+
exploration_escape_duration=8, # Thorough exploration: shorter escape duration
|
|
50
|
+
exploration_direction_persistence=18, # Thorough exploration: longer persistence
|
|
51
|
+
exploration_hub_distance_threshold=12, # Thorough exploration: larger distance threshold
|
|
52
|
+
),
|
|
53
|
+
"conservative": BaselineHyperparameters(
|
|
54
|
+
recharge_threshold_low=50, # Recharge early
|
|
55
|
+
recharge_threshold_high=95, # Stay charged
|
|
56
|
+
stuck_detection_enabled=True,
|
|
57
|
+
stuck_escape_distance=8, # Shorter escape distance
|
|
58
|
+
position_history_size=30,
|
|
59
|
+
exploration_area_check_window=30,
|
|
60
|
+
exploration_area_size_threshold=7,
|
|
61
|
+
exploration_escape_duration=10,
|
|
62
|
+
exploration_direction_persistence=10,
|
|
63
|
+
exploration_hub_distance_threshold=10,
|
|
64
|
+
),
|
|
65
|
+
"aggressive": BaselineHyperparameters(
|
|
66
|
+
recharge_threshold_low=20, # Low energy tolerance
|
|
67
|
+
recharge_threshold_high=80, # Don't wait for full charge
|
|
68
|
+
stuck_detection_enabled=True,
|
|
69
|
+
stuck_escape_distance=15, # Longer escape distance
|
|
70
|
+
position_history_size=30,
|
|
71
|
+
exploration_area_check_window=30,
|
|
72
|
+
exploration_area_size_threshold=7,
|
|
73
|
+
exploration_escape_duration=10,
|
|
74
|
+
exploration_direction_persistence=10,
|
|
75
|
+
exploration_hub_distance_threshold=10,
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CellType(Enum):
|
|
81
|
+
"""Occupancy map cell states."""
|
|
82
|
+
|
|
83
|
+
FREE = 1 # Passable (can walk through)
|
|
84
|
+
OBSTACLE = 2 # Impassable (walls, stations, extractors)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Phase(Enum):
|
|
88
|
+
"""Goal-driven phases for the baseline agent."""
|
|
89
|
+
|
|
90
|
+
GATHER = "gather" # Collect resources (explore if needed)
|
|
91
|
+
ASSEMBLE = "assemble" # Make hearts at hub
|
|
92
|
+
DELIVER = "deliver" # Deposit hearts to chest
|
|
93
|
+
RECHARGE = "recharge" # Recharge energy at junction
|
|
94
|
+
CRAFT_UNCLIP = "craft_unclip" # Craft unclip items at hub
|
|
95
|
+
UNCLIP = "unclip" # Unclip extractors
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class ExtractorInfo:
|
|
100
|
+
"""Tracks a discovered extractor with full state."""
|
|
101
|
+
|
|
102
|
+
position: tuple[int, int]
|
|
103
|
+
resource_type: str # "carbon", "oxygen", "germanium", "silicon"
|
|
104
|
+
last_seen_step: int
|
|
105
|
+
times_used: int = 0
|
|
106
|
+
|
|
107
|
+
# Extractor state from observations
|
|
108
|
+
cooldown_remaining: int = 0 # Steps until ready
|
|
109
|
+
clipped: bool = False # Is it depleted?
|
|
110
|
+
remaining_uses: int = 999 # How many uses left
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class ObjectState:
|
|
115
|
+
"""State of a single object at a position."""
|
|
116
|
+
|
|
117
|
+
name: str
|
|
118
|
+
tags: list[str] = field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
# Extractor/station features
|
|
121
|
+
cooldown_remaining: int = 0
|
|
122
|
+
clipped: int = 0
|
|
123
|
+
remaining_uses: int = 999
|
|
124
|
+
|
|
125
|
+
# Inventory (for chests/extractors - maps resource name to amount)
|
|
126
|
+
inventory: dict[str, int] = field(default_factory=dict)
|
|
127
|
+
|
|
128
|
+
# Protocol details (recipes for hubs/extractors)
|
|
129
|
+
protocol_inputs: dict[str, int] = field(default_factory=dict)
|
|
130
|
+
protocol_outputs: dict[str, int] = field(default_factory=dict)
|
|
131
|
+
|
|
132
|
+
# Agent features (when object is another agent)
|
|
133
|
+
agent_id: int = -1 # Which agent (-1 if not an agent) - NOT in observations, kept for API
|
|
134
|
+
agent_group: int = -1 # Team/group
|
|
135
|
+
agent_frozen: int = 0 # Is frozen?
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass
|
|
139
|
+
class ParsedObservation:
|
|
140
|
+
"""Parsed observation data in a clean format."""
|
|
141
|
+
|
|
142
|
+
# Agent state
|
|
143
|
+
row: int
|
|
144
|
+
col: int
|
|
145
|
+
energy: int
|
|
146
|
+
|
|
147
|
+
# Inventory
|
|
148
|
+
carbon: int
|
|
149
|
+
oxygen: int
|
|
150
|
+
germanium: int
|
|
151
|
+
silicon: int
|
|
152
|
+
hearts: int
|
|
153
|
+
decoder: int
|
|
154
|
+
modulator: int
|
|
155
|
+
resonator: int
|
|
156
|
+
scrambler: int
|
|
157
|
+
|
|
158
|
+
# Nearby objects with full state (position -> ObjectState)
|
|
159
|
+
nearby_objects: dict[tuple[int, int], ObjectState]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class SimpleAgentState:
|
|
164
|
+
"""State for a single agent."""
|
|
165
|
+
|
|
166
|
+
agent_id: int
|
|
167
|
+
|
|
168
|
+
phase: Phase = Phase.GATHER # Start gathering immediately
|
|
169
|
+
step_count: int = 0
|
|
170
|
+
|
|
171
|
+
# Current position (origin-relative, starting at (0, 0))
|
|
172
|
+
row: int = 0
|
|
173
|
+
col: int = 0
|
|
174
|
+
energy: int = 100
|
|
175
|
+
|
|
176
|
+
# Per-agent discovered extractors and stations (no shared state, each agent tracks independently)
|
|
177
|
+
extractors: dict[str, list[ExtractorInfo]] = field(
|
|
178
|
+
default_factory=lambda: {"carbon": [], "oxygen": [], "germanium": [], "silicon": []}
|
|
179
|
+
)
|
|
180
|
+
stations: dict[str, tuple[int, int] | None] = field(
|
|
181
|
+
default_factory=lambda: {"hub": None, "chest": None, "junction": None}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Inventory
|
|
185
|
+
carbon: int = 0
|
|
186
|
+
oxygen: int = 0
|
|
187
|
+
germanium: int = 0
|
|
188
|
+
silicon: int = 0
|
|
189
|
+
hearts: int = 0
|
|
190
|
+
decoder: int = 0
|
|
191
|
+
modulator: int = 0
|
|
192
|
+
resonator: int = 0
|
|
193
|
+
scrambler: int = 0
|
|
194
|
+
|
|
195
|
+
# Current target
|
|
196
|
+
target_position: Optional[tuple[int, int]] = None
|
|
197
|
+
target_resource: Optional[str] = None
|
|
198
|
+
|
|
199
|
+
# Map knowledge
|
|
200
|
+
map_height: int = 0
|
|
201
|
+
map_width: int = 0
|
|
202
|
+
occupancy: list[list[int]] = field(default_factory=list) # 1=free, 2=obstacle (initialized in reset)
|
|
203
|
+
|
|
204
|
+
# Track last action for position updates
|
|
205
|
+
last_action: Action = field(default_factory=lambda: Action(name="noop"))
|
|
206
|
+
|
|
207
|
+
# Current glyph (vibe) for interacting with hub
|
|
208
|
+
current_glyph: str = "default"
|
|
209
|
+
|
|
210
|
+
# Discovered hub recipe (dynamically discovered from observations)
|
|
211
|
+
heart_recipe: Optional[dict[str, int]] = None
|
|
212
|
+
|
|
213
|
+
# Extractor activation state
|
|
214
|
+
waiting_at_extractor: Optional[tuple[int, int]] = None
|
|
215
|
+
wait_steps: int = 0
|
|
216
|
+
pending_use_resource: Optional[str] = None
|
|
217
|
+
pending_use_amount: int = 0
|
|
218
|
+
|
|
219
|
+
# Directional exploration state
|
|
220
|
+
exploration_target: Optional[str] = None # Current direction ("north", "south", "east", "west")
|
|
221
|
+
exploration_target_step: int = 0 # When we set the direction
|
|
222
|
+
exploration_escape_until_step: int = 0 # If > 0, we're in escape mode until this step
|
|
223
|
+
|
|
224
|
+
# Agent positions (for collision detection)
|
|
225
|
+
agent_occupancy: set[tuple[int, int]] = field(default_factory=set)
|
|
226
|
+
|
|
227
|
+
# Stuck detection
|
|
228
|
+
position_history: list[tuple[int, int]] = field(default_factory=list) # Last 30 positions
|
|
229
|
+
stuck_loop_detected: bool = False
|
|
230
|
+
stuck_escape_step: int = 0
|
|
231
|
+
|
|
232
|
+
# Path caching for efficient navigation (per-agent)
|
|
233
|
+
cached_path: Optional[list[tuple[int, int]]] = None
|
|
234
|
+
cached_path_target: Optional[tuple[int, int]] = None
|
|
235
|
+
cached_path_reach_adjacent: bool = False
|
|
236
|
+
using_object_this_step: bool = False # Flag to prevent position update when using objects
|
|
237
|
+
|
|
238
|
+
# Current observation (for collision detection and state updates)
|
|
239
|
+
current_obs: Optional[AgentObservation] = None
|