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.
Files changed (128) hide show
  1. cogames_agents/__init__.py +0 -0
  2. cogames_agents/evals/__init__.py +5 -0
  3. cogames_agents/evals/planky_evals.py +415 -0
  4. cogames_agents/policy/__init__.py +0 -0
  5. cogames_agents/policy/evolution/__init__.py +0 -0
  6. cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
  7. cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
  8. cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
  9. cogames_agents/policy/nim_agents/__init__.py +20 -0
  10. cogames_agents/policy/nim_agents/agents.py +98 -0
  11. cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
  12. cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
  13. cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
  14. cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
  15. cogames_agents/policy/nim_agents/common.nim +1054 -0
  16. cogames_agents/policy/nim_agents/install.sh +1 -0
  17. cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
  18. cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
  19. cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
  20. cogames_agents/policy/nim_agents/nimby.lock +3 -0
  21. cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
  22. cogames_agents/policy/nim_agents/random_agents.nim +68 -0
  23. cogames_agents/policy/nim_agents/test_agents.py +53 -0
  24. cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
  25. cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
  26. cogames_agents/policy/scripted_agent/README.md +360 -0
  27. cogames_agents/policy/scripted_agent/__init__.py +0 -0
  28. cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
  29. cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
  30. cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
  31. cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
  32. cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
  33. cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
  34. cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
  35. cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
  36. cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
  37. cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
  38. cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
  39. cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
  40. cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
  41. cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
  42. cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
  43. cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
  44. cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
  45. cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
  46. cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
  47. cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
  48. cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
  49. cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
  50. cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
  51. cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
  52. cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
  53. cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
  54. cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
  55. cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
  56. cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
  57. cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
  58. cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
  59. cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
  60. cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
  61. cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
  62. cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
  63. cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
  64. cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
  65. cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
  66. cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
  67. cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
  68. cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
  69. cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
  70. cogames_agents/policy/scripted_agent/common/roles.py +34 -0
  71. cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
  72. cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
  73. cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
  74. cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
  75. cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
  76. cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
  77. cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
  78. cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
  79. cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
  80. cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
  81. cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
  82. cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
  83. cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
  84. cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
  85. cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
  86. cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
  87. cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
  88. cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
  89. cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
  90. cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
  91. cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
  92. cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
  93. cogames_agents/policy/scripted_agent/planky/README.md +214 -0
  94. cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
  95. cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
  96. cogames_agents/policy/scripted_agent/planky/context.py +68 -0
  97. cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
  98. cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
  99. cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
  100. cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
  101. cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
  102. cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
  103. cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
  104. cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
  105. cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
  106. cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
  107. cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
  108. cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
  109. cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
  110. cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
  111. cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
  112. cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
  113. cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
  114. cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
  115. cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
  116. cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
  117. cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
  118. cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
  119. cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
  120. cogames_agents/policy/scripted_agent/types.py +239 -0
  121. cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
  122. cogames_agents/policy/scripted_agent/utils.py +381 -0
  123. cogames_agents/policy/scripted_registry.py +80 -0
  124. cogames_agents/py.typed +0 -0
  125. cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
  126. cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
  127. cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
  128. cogames_agents-0.0.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,954 @@
1
+ import
2
+ std/[algorithm, deques, options, random, sequtils, sets, strutils, tables],
3
+ common
4
+
5
+ type
6
+ Phase = enum gatherPhase, assemblePhase, deliverPhase, rechargePhase
7
+ ObservedObject = object
8
+ name: string
9
+ clipped: bool
10
+ cooldownRemaining, remainingUses: int
11
+ protocolInputs, protocolOutputs: Table[string, int]
12
+ agentGroup, agentFrozen: int
13
+ ExtractorInfo = object
14
+ position: Location
15
+ resource: string
16
+ lastSeen, cooldownRemaining, remainingUses: int
17
+ clipped: bool
18
+ LadybugState = object
19
+ row, col, mapHeight, mapWidth, obsHalfWidth, obsHalfHeight: int
20
+ phase: Phase
21
+ stepCount, waitSteps, lastAction: int
22
+ explorationDirectionSetStep, explorationEscapeUntilStep, stuckEscapeStep: int
23
+ positionHistory: seq[Location]
24
+ occupancy: seq[seq[int]]
25
+ agentOccupancy: HashSet[Location]
26
+ energy, carbon, oxygen, germanium, silicon, hearts: int
27
+ decoder, modulator, resonator, scrambler: int
28
+ heartRecipe: Table[string, int]
29
+ heartRecipeKnown: bool
30
+ stations: Table[string, Option[Location]]
31
+ extractors: Table[string, seq[ExtractorInfo]]
32
+ currentGlyph, targetResource, pendingUseResource, explorationDirection: string
33
+ pendingUseAmount: int
34
+ waitingAtExtractor: Option[Location]
35
+ usingObjectThisStep, stuckLoopDetected: bool
36
+ cachedPath: seq[(int, int)]
37
+ cachedPathTarget: Option[Location]
38
+ cachedPathReachAdjacent: bool
39
+ LadybugAgent* = ref object
40
+ agentId*: int
41
+ cfg*: Config
42
+ random*: Rand
43
+ state*: LadybugState
44
+ LadybugPolicy* = ref object
45
+ agents*: seq[LadybugAgent]
46
+
47
+ proc initHeartRecipeFromConfig(agent: LadybugAgent) {.raises: [].}
48
+
49
+ const
50
+ defaultMapSize = 200
51
+ rechargeThresholdLow = 35
52
+ rechargeThresholdHigh = 85
53
+ positionHistorySize = 40
54
+ explorationAreaCheckWindow = 35
55
+ explorationAreaSizeThreshold = 9
56
+ explorationEscapeDuration = 8
57
+ explorationDirectionPersistence = 18
58
+ explorationHubDistanceThreshold = 12
59
+ cellFree = 1
60
+ cellObstacle = 2
61
+ parentSentinel = (-9999, -9999)
62
+ ResourceTypes = ["carbon", "oxygen", "germanium", "silicon"]
63
+ StationKeys = ["hub", "chest", "junction"]
64
+ DirectionNames = ["north", "south", "east", "west"]
65
+ CardinalNeighbors = [(-1, 0), (1, 0), (0, -1), (0, 1)]
66
+ DefaultHeartRecipe = [
67
+ ("carbon", 2),
68
+ ("oxygen", 2),
69
+ ("germanium", 1),
70
+ ("silicon", 3)
71
+ ]
72
+
73
+ proc initState(agent: LadybugAgent) =
74
+ agent.state = LadybugState()
75
+ agent.state.mapHeight = defaultMapSize
76
+ agent.state.mapWidth = defaultMapSize
77
+ agent.state.obsHalfWidth = agent.cfg.config.obsWidth div 2
78
+ agent.state.obsHalfHeight = agent.cfg.config.obsHeight div 2
79
+ agent.state.row = defaultMapSize div 2
80
+ agent.state.col = defaultMapSize div 2
81
+ agent.state.phase = gatherPhase
82
+ agent.state.currentGlyph = "default"
83
+ agent.state.targetResource = ""
84
+ agent.state.pendingUseResource = ""
85
+ agent.state.pendingUseAmount = 0
86
+ agent.state.heartRecipe = initTable[string, int]()
87
+ agent.state.heartRecipeKnown = false
88
+ agent.initHeartRecipeFromConfig()
89
+ if not agent.state.heartRecipeKnown:
90
+ for (resource, amount) in DefaultHeartRecipe:
91
+ agent.state.heartRecipe[resource] = amount
92
+ agent.state.heartRecipeKnown = true
93
+ agent.state.stations = initTable[string, Option[Location]]()
94
+ for key in StationKeys:
95
+ agent.state.stations[key] = none(Location)
96
+ agent.state.extractors = initTable[string, seq[ExtractorInfo]]()
97
+ for resource in ResourceTypes:
98
+ agent.state.extractors[resource] = @[]
99
+ agent.state.agentOccupancy = initHashSet[Location]()
100
+ agent.state.occupancy = newSeqWith(
101
+ agent.state.mapHeight,
102
+ newSeqWith(agent.state.mapWidth, cellFree.int)
103
+ )
104
+ agent.state.lastAction = agent.cfg.actions.noop
105
+ agent.state.explorationDirection = ""
106
+ agent.state.explorationDirectionSetStep = 0
107
+ agent.state.explorationEscapeUntilStep = 0
108
+ agent.state.positionHistory = @[]
109
+ agent.state.waitSteps = 0
110
+ agent.state.waitingAtExtractor = none(Location)
111
+ agent.state.usingObjectThisStep = false
112
+ agent.state.stuckLoopDetected = false
113
+ agent.state.stuckEscapeStep = 0
114
+ agent.state.cachedPath = @[]
115
+ agent.state.cachedPathTarget = none(Location)
116
+ agent.state.cachedPathReachAdjacent = false
117
+
118
+ proc detectLoops(agent: LadybugAgent)
119
+ proc tryRandomDirection(agent: LadybugAgent): int
120
+
121
+ proc resourceVibe(resource: string): string =
122
+ ## Map canonical resource names to the actual vibe glyphs used by the engine.
123
+ case resource.toLowerAscii()
124
+ of "carbon":
125
+ "carbon_a"
126
+ of "oxygen":
127
+ "oxygen_a"
128
+ of "germanium":
129
+ "germanium_a"
130
+ of "silicon":
131
+ "silicon_a"
132
+ else:
133
+ "carbon_a"
134
+
135
+ proc tagName(cfg: Config, tagId: int): string =
136
+ if tagId >= 0 and tagId < cfg.config.tags.len:
137
+ return cfg.config.tags[tagId]
138
+ ""
139
+
140
+ proc clearCachedPath(agent: LadybugAgent) =
141
+ agent.state.cachedPath = @[]
142
+ agent.state.cachedPathTarget = none(Location)
143
+ agent.state.cachedPathReachAdjacent = false
144
+
145
+ proc stationFromName(lowerName: string): string =
146
+ if lowerName.contains("hub"):
147
+ "hub"
148
+ elif lowerName.contains("chest"):
149
+ "chest"
150
+ elif lowerName.contains("junction"):
151
+ "junction"
152
+ else:
153
+ ""
154
+
155
+ proc initHeartRecipeFromConfig(agent: LadybugAgent) =
156
+ for protocol in agent.cfg.hubProtocols:
157
+ if protocol.outputResources.getOrDefault("heart", 0) > 0:
158
+ agent.state.heartRecipe = initTable[string, int]()
159
+ for resource, amount in protocol.inputResources.pairs:
160
+ if resource != "energy":
161
+ agent.state.heartRecipe[resource] = amount
162
+ agent.state.heartRecipeKnown = true
163
+ return
164
+
165
+ proc newLadybugAgent*(agentId: int, environmentConfig: string): LadybugAgent {.raises: [].} =
166
+ var config = parseConfig(environmentConfig)
167
+ result = LadybugAgent(agentId: agentId, cfg: config)
168
+ result.random = initRand(agentId)
169
+ initState(result)
170
+
171
+ proc reset*(agent: LadybugAgent) =
172
+ initState(agent)
173
+
174
+ proc decodeObservation(
175
+ agent: LadybugAgent,
176
+ numTokens: int,
177
+ sizeToken: int,
178
+ rawObservation: pointer
179
+ ): Table[Location, seq[FeatureValue]] =
180
+ let observations = cast[ptr UncheckedArray[uint8]](rawObservation)
181
+ for token in 0 ..< numTokens:
182
+ let baseIdx = token * sizeToken
183
+ let locationPacked = observations[baseIdx]
184
+ let featureId = observations[baseIdx + 1]
185
+ let value = observations[baseIdx + 2]
186
+ if locationPacked == 255 and featureId == 255 and value == 255:
187
+ break
188
+ var location: Location
189
+ if locationPacked != 0xFF:
190
+ location.y = (locationPacked shr 4).int - agent.state.obsHalfHeight
191
+ location.x = (locationPacked and 0x0F).int - agent.state.obsHalfWidth
192
+ if location notin result:
193
+ result[location] = @[]
194
+ result[location].add(FeatureValue(featureId: featureId.int, value: value.int))
195
+
196
+ proc worldLocation(agent: LadybugAgent, local: Location): Option[Location] =
197
+ let worldRow = agent.state.row + local.y
198
+ let worldCol = agent.state.col + local.x
199
+ if worldRow < 0 or worldRow >= agent.state.mapHeight:
200
+ return none(Location)
201
+ if worldCol < 0 or worldCol >= agent.state.mapWidth:
202
+ return none(Location)
203
+ some(Location(x: worldCol, y: worldRow))
204
+
205
+ proc updateInventory(agent: LadybugAgent, visible: Table[Location, seq[FeatureValue]]) =
206
+ agent.state.energy = agent.cfg.getInventory(visible, agent.cfg.features.invEnergy)
207
+ agent.state.carbon = agent.cfg.getInventory(visible, agent.cfg.features.invCarbon)
208
+ agent.state.oxygen = agent.cfg.getInventory(visible, agent.cfg.features.invOxygen)
209
+ agent.state.germanium = agent.cfg.getInventory(visible, agent.cfg.features.invGermanium)
210
+ agent.state.silicon = agent.cfg.getInventory(visible, agent.cfg.features.invSilicon)
211
+ agent.state.hearts = agent.cfg.getInventory(visible, agent.cfg.features.invHeart)
212
+ agent.state.decoder = agent.cfg.getInventory(visible, agent.cfg.features.invDecoder)
213
+ agent.state.modulator = agent.cfg.getInventory(visible, agent.cfg.features.invModulator)
214
+ agent.state.resonator = agent.cfg.getInventory(visible, agent.cfg.features.invResonator)
215
+ agent.state.scrambler = agent.cfg.getInventory(visible, agent.cfg.features.invScrambler)
216
+
217
+ proc buildObservedObject(agent: LadybugAgent, features: seq[FeatureValue]): ObservedObject =
218
+ var tagIds: seq[int] = @[]
219
+ result.protocolInputs = initTable[string, int]()
220
+ result.protocolOutputs = initTable[string, int]()
221
+ for fv in features:
222
+ if fv.featureId == agent.cfg.features.tag:
223
+ tagIds.add(fv.value)
224
+ continue
225
+ elif fv.featureId == agent.cfg.features.cooldownRemaining:
226
+ result.cooldownRemaining = fv.value
227
+ elif fv.featureId == agent.cfg.features.clipped:
228
+ result.clipped = fv.value > 0
229
+ elif fv.featureId == agent.cfg.features.remainingUses:
230
+ result.remainingUses = fv.value
231
+ elif fv.featureId == agent.cfg.features.group:
232
+ result.agentGroup = fv.value
233
+ elif fv.featureId == agent.cfg.features.frozen:
234
+ result.agentFrozen = fv.value
235
+ else:
236
+ assignProtocolTables(agent.cfg, fv, result.protocolInputs, result.protocolOutputs)
237
+
238
+ if tagIds.len > 0:
239
+ result.name = tagName(agent.cfg, tagIds[0])
240
+ elif result.agentGroup != 0 or result.agentFrozen != 0:
241
+ result.name = "agent"
242
+ else:
243
+ result.name = ""
244
+
245
+ proc discoverStation(agent: LadybugAgent, key: string, loc: Location) =
246
+ if key notin agent.state.stations:
247
+ agent.state.stations[key] = some(loc)
248
+ return
249
+ if agent.state.stations[key].isNone:
250
+ agent.state.stations[key] = some(loc)
251
+
252
+ proc discoverExtractor(
253
+ agent: LadybugAgent,
254
+ resource: string,
255
+ loc: Location,
256
+ obj: ObservedObject
257
+ ) =
258
+ var items = agent.state.extractors.getOrDefault(resource, @[])
259
+ var found = false
260
+ for i in 0 ..< items.len:
261
+ if items[i].position == loc:
262
+ items[i].lastSeen = agent.state.stepCount
263
+ items[i].cooldownRemaining = obj.cooldownRemaining
264
+ items[i].clipped = obj.clipped
265
+ if obj.remainingUses != 0:
266
+ items[i].remainingUses = obj.remainingUses
267
+ found = true
268
+ break
269
+ if not found:
270
+ var info = ExtractorInfo(
271
+ position: loc,
272
+ resource: resource,
273
+ lastSeen: agent.state.stepCount,
274
+ cooldownRemaining: obj.cooldownRemaining,
275
+ clipped: obj.clipped,
276
+ remainingUses: (if obj.remainingUses == 0: 999 else: obj.remainingUses)
277
+ )
278
+ items.add(info)
279
+ agent.state.extractors[resource] = items
280
+
281
+ proc discoverObjects(agent: LadybugAgent, visible: Table[Location, seq[FeatureValue]]) =
282
+ agent.state.agentOccupancy.clear()
283
+ for dy in -agent.state.obsHalfHeight .. agent.state.obsHalfHeight:
284
+ for dx in -agent.state.obsHalfWidth .. agent.state.obsHalfWidth:
285
+ let local = Location(x: dx, y: dy)
286
+ let world = agent.worldLocation(local)
287
+ if world.isSome:
288
+ agent.state.occupancy[world.get().y][world.get().x] = cellFree
289
+
290
+ for local, features in visible.pairs:
291
+ if local.x == 0 and local.y == 0:
292
+ continue
293
+ let worldOpt = agent.worldLocation(local)
294
+ if worldOpt.isNone:
295
+ continue
296
+ let world = worldOpt.get()
297
+ var obj = agent.buildObservedObject(features)
298
+ if obj.name.len == 0:
299
+ continue
300
+ let lowerName = obj.name.toLowerAscii()
301
+
302
+ if lowerName == "agent":
303
+ agent.state.agentOccupancy.incl(world)
304
+ continue
305
+
306
+ if lowerName.contains("wall"):
307
+ agent.state.occupancy[world.y][world.x] = cellObstacle
308
+ continue
309
+
310
+ let stationKey = stationFromName(lowerName)
311
+ if stationKey.len > 0:
312
+ agent.state.occupancy[world.y][world.x] = cellObstacle
313
+ discoverStation(agent, stationKey, world)
314
+ if stationKey == "hub" and (not agent.state.heartRecipeKnown) and
315
+ obj.protocolOutputs.getOrDefault("heart", 0) > 0:
316
+ agent.state.heartRecipe = initTable[string, int]()
317
+ for resource, amount in obj.protocolInputs.pairs:
318
+ if resource != "energy":
319
+ agent.state.heartRecipe[resource] = amount
320
+ agent.state.heartRecipeKnown = true
321
+ continue
322
+
323
+ if lowerName.contains("extractor"):
324
+ agent.state.occupancy[world.y][world.x] = cellObstacle
325
+ var resource = lowerName
326
+ if resource.startsWith("clipped_"):
327
+ resource = resource[8 .. ^1]
328
+ if resource.endsWith("_extractor"):
329
+ resource = resource[0 ..< resource.len - "_extractor".len]
330
+ discoverExtractor(agent, resource, world, obj)
331
+
332
+ proc updatePosition(agent: LadybugAgent) =
333
+ if agent.state.usingObjectThisStep:
334
+ agent.state.usingObjectThisStep = false
335
+ return
336
+ if agent.state.lastAction == agent.cfg.actions.moveNorth:
337
+ dec agent.state.row
338
+ elif agent.state.lastAction == agent.cfg.actions.moveSouth:
339
+ inc agent.state.row
340
+ elif agent.state.lastAction == agent.cfg.actions.moveEast:
341
+ inc agent.state.col
342
+ elif agent.state.lastAction == agent.cfg.actions.moveWest:
343
+ dec agent.state.col
344
+ agent.state.positionHistory.add(Location(x: agent.state.col, y: agent.state.row))
345
+ if agent.state.positionHistory.len > positionHistorySize:
346
+ agent.state.positionHistory.delete(0)
347
+ detectLoops(agent)
348
+
349
+ proc detectLoops(agent: LadybugAgent) =
350
+ let hist = agent.state.positionHistory
351
+ let lenHist = hist.len
352
+ if lenHist >= 6:
353
+ let a1 = hist[lenHist - 1]
354
+ let a2 = hist[lenHist - 2]
355
+ let a3 = hist[lenHist - 3]
356
+ let a4 = hist[lenHist - 4]
357
+ let a5 = hist[lenHist - 5]
358
+ let a6 = hist[lenHist - 6]
359
+ if a1 == a3 and a3 == a5 and a2 == a4 and a4 == a6 and a1 != a2:
360
+ agent.state.stuckLoopDetected = true
361
+ agent.state.stuckEscapeStep = agent.state.stepCount
362
+ return
363
+ if lenHist >= 9:
364
+ let b1 = hist[lenHist - 1]
365
+ let b2 = hist[lenHist - 2]
366
+ let b3 = hist[lenHist - 3]
367
+ let b4 = hist[lenHist - 4]
368
+ let b5 = hist[lenHist - 5]
369
+ let b6 = hist[lenHist - 6]
370
+ let b7 = hist[lenHist - 7]
371
+ let b8 = hist[lenHist - 8]
372
+ let b9 = hist[lenHist - 9]
373
+ if b1 == b4 and b4 == b7 and b2 == b5 and b5 == b8 and b3 == b6 and b6 == b9:
374
+ agent.state.stuckLoopDetected = true
375
+ agent.state.stuckEscapeStep = agent.state.stepCount
376
+
377
+ proc clearWaiting(agent: LadybugAgent) =
378
+ agent.state.waitingAtExtractor = none(Location)
379
+ agent.state.pendingUseResource = ""
380
+ agent.state.pendingUseAmount = 0
381
+ agent.state.waitSteps = 0
382
+
383
+ proc getInventoryValue(agent: LadybugAgent, resource: string): int =
384
+ case resource
385
+ of "carbon": result = agent.state.carbon
386
+ of "oxygen": result = agent.state.oxygen
387
+ of "germanium": result = agent.state.germanium
388
+ of "silicon": result = agent.state.silicon
389
+ of "heart": result = agent.state.hearts
390
+ else: result = 0
391
+
392
+ proc findExtractor(
393
+ agent: LadybugAgent,
394
+ resource: string,
395
+ loc: Location
396
+ ): Option[ExtractorInfo] =
397
+ if resource notin agent.state.extractors:
398
+ return none(ExtractorInfo)
399
+ for info in agent.state.extractors[resource]:
400
+ if info.position == loc:
401
+ return some(info)
402
+ none(ExtractorInfo)
403
+
404
+ proc handleWaiting(agent: LadybugAgent): Option[int] =
405
+ if agent.state.pendingUseResource.len == 0 or agent.state.waitingAtExtractor.isNone:
406
+ return none(int)
407
+
408
+ let resource = agent.state.pendingUseResource
409
+ let waitingLoc = agent.state.waitingAtExtractor.get()
410
+ if agent.getInventoryValue(resource) > agent.state.pendingUseAmount:
411
+ clearWaiting(agent)
412
+ return none(int)
413
+
414
+ var maxWait = 20
415
+ let infoOpt = agent.findExtractor(resource, waitingLoc)
416
+ if infoOpt.isSome():
417
+ maxWait = infoOpt.get().cooldownRemaining + 5
418
+
419
+ inc agent.state.waitSteps
420
+ if agent.state.waitSteps > maxWait:
421
+ clearWaiting(agent)
422
+ return none(int)
423
+
424
+ some(agent.cfg.actions.noop)
425
+
426
+ proc calculateDeficits(agent: LadybugAgent): Table[string, int] =
427
+ if not agent.state.heartRecipeKnown:
428
+ raise newException(ValueError,
429
+ "Heart recipe not discovered! Agent must observe hub with correct vibe to learn recipe. Ensure protocol_details_obs=True in game config.")
430
+ result = initTable[string, int]()
431
+ for resource in ResourceTypes:
432
+ let required = agent.state.heartRecipe.getOrDefault(resource, 0)
433
+ let deficit = required - agent.getInventoryValue(resource)
434
+ result[resource] = (if deficit > 0: deficit else: 0)
435
+
436
+ proc findNearestExtractor(agent: LadybugAgent, resource: string): Option[ExtractorInfo] =
437
+ if resource notin agent.state.extractors:
438
+ return none(ExtractorInfo)
439
+ var bestDist = high(int)
440
+ var best: ExtractorInfo
441
+ for info in agent.state.extractors[resource]:
442
+ if info.clipped or info.remainingUses == 0:
443
+ continue
444
+ let dist = abs(info.position.x - agent.state.col) + abs(info.position.y - agent.state.row)
445
+ if dist < bestDist:
446
+ bestDist = dist
447
+ best = info
448
+ if bestDist == high(int):
449
+ return none(ExtractorInfo)
450
+ some(best)
451
+
452
+ proc findAnyNeededExtractor(agent: LadybugAgent): Option[(ExtractorInfo, string)] =
453
+ let deficits = agent.calculateDeficits()
454
+ var needed = toSeq(deficits.pairs)
455
+ needed.sort(proc(a, b: (string, int)): int = cmp(b[1], a[1]))
456
+ for (resource, deficit) in needed:
457
+ if deficit > 0:
458
+ let extractorOpt = agent.findNearestExtractor(resource)
459
+ if extractorOpt.isSome():
460
+ return some((extractorOpt.get(), resource))
461
+ none((ExtractorInfo, string))
462
+
463
+ proc isWithinBounds(agent: LadybugAgent, row: int, col: int): bool =
464
+ row >= 0 and row < agent.state.mapHeight and col >= 0 and col < agent.state.mapWidth
465
+
466
+ proc isAgentBlocking(agent: LadybugAgent, row: int, col: int): bool =
467
+ let loc = Location(x: col, y: row)
468
+ loc in agent.state.agentOccupancy
469
+
470
+ proc isTraversable(agent: LadybugAgent, row: int, col: int): bool =
471
+ if not agent.isWithinBounds(row, col):
472
+ return false
473
+ if agent.isAgentBlocking(row, col):
474
+ return false
475
+ agent.state.occupancy[row][col] == cellFree
476
+
477
+ proc isAdjacent(row1: int, col1: int, row2: int, col2: int): bool =
478
+ let dr = abs(row1 - row2)
479
+ let dc = abs(col1 - col2)
480
+ (dr == 1 and dc == 0) or (dr == 0 and dc == 1)
481
+
482
+ proc computeGoalCells(
483
+ agent: LadybugAgent,
484
+ targetRow: int,
485
+ targetCol: int,
486
+ reachAdjacent: bool
487
+ ): seq[(int, int)] =
488
+ if not reachAdjacent:
489
+ return @[(targetRow, targetCol)]
490
+ for delta in CardinalNeighbors:
491
+ let nr = targetRow + delta[0]
492
+ let nc = targetCol + delta[1]
493
+ if agent.isTraversable(nr, nc):
494
+ result.add((nr, nc))
495
+ if result.len == 0:
496
+ for delta in CardinalNeighbors:
497
+ let nr = targetRow + delta[0]
498
+ let nc = targetCol + delta[1]
499
+ if agent.isWithinBounds(nr, nc):
500
+ result.add((nr, nc))
501
+
502
+ proc reconstructPath(
503
+ cameFrom: Table[(int, int), (int, int)],
504
+ current: (int, int)
505
+ ): seq[(int, int)] =
506
+ var cur = current
507
+ while cameFrom.hasKey(cur):
508
+ let prev = cameFrom[cur]
509
+ if prev == parentSentinel:
510
+ break
511
+ result.add(cur)
512
+ cur = prev
513
+ result.reverse()
514
+
515
+ proc moveTowards(
516
+ agent: LadybugAgent,
517
+ targetRow: int,
518
+ targetCol: int,
519
+ reachAdjacent: bool = false,
520
+ allowGoalBlock: bool = false
521
+ ): int =
522
+ if not reachAdjacent and agent.state.row == targetRow and agent.state.col == targetCol:
523
+ return agent.cfg.actions.noop
524
+ let goals = agent.computeGoalCells(targetRow, targetCol, reachAdjacent)
525
+ if goals.len == 0:
526
+ return agent.cfg.actions.noop
527
+ var goalSet = initHashSet[(int, int)]()
528
+ for g in goals:
529
+ goalSet.incl(g)
530
+ let targetLoc = Location(x: targetCol, y: targetRow)
531
+ var pathValid = false
532
+ if agent.state.cachedPath.len > 0 and agent.state.cachedPathTarget.isSome and
533
+ agent.state.cachedPathTarget.get().x == targetCol and
534
+ agent.state.cachedPathTarget.get().y == targetRow and
535
+ agent.state.cachedPathReachAdjacent == reachAdjacent:
536
+ let nextStep = agent.state.cachedPath[0]
537
+ if agent.isTraversable(nextStep[0], nextStep[1]) or (allowGoalBlock and nextStep in goalSet):
538
+ pathValid = true
539
+ else:
540
+ clearCachedPath(agent)
541
+ if not pathValid:
542
+ var queue = initDeque[(int, int)]()
543
+ var cameFrom = initTable[(int, int), (int, int)]()
544
+ let start = (agent.state.row, agent.state.col)
545
+ queue.addLast(start)
546
+ cameFrom[start] = parentSentinel
547
+ var found = false
548
+ var goalReached: (int, int)
549
+ while queue.len > 0 and not found:
550
+ let current = queue.popFirst()
551
+ if current in goalSet:
552
+ found = true
553
+ goalReached = current
554
+ break
555
+ for delta in CardinalNeighbors:
556
+ let nr = current[0] + delta[0]
557
+ let nc = current[1] + delta[1]
558
+ if cameFrom.hasKey((nr, nc)):
559
+ continue
560
+ var canTraverse = agent.isTraversable(nr, nc)
561
+ if not canTraverse and allowGoalBlock and (nr, nc) in goalSet:
562
+ canTraverse = true
563
+ if not canTraverse:
564
+ continue
565
+ cameFrom[(nr, nc)] = current
566
+ queue.addLast((nr, nc))
567
+ if not found:
568
+ clearCachedPath(agent)
569
+ let randomAction = agent.tryRandomDirection()
570
+ return randomAction
571
+ let newPath = reconstructPath(cameFrom, goalReached)
572
+ if newPath.len == 0:
573
+ return agent.cfg.actions.noop
574
+ agent.state.cachedPath = newPath
575
+ agent.state.cachedPathTarget = some(targetLoc)
576
+ agent.state.cachedPathReachAdjacent = reachAdjacent
577
+ let nextStep = agent.state.cachedPath[0]
578
+ agent.state.cachedPath.delete(0)
579
+ if agent.state.cachedPath.len == 0:
580
+ clearCachedPath(agent)
581
+ if allowGoalBlock and agent.isAgentBlocking(nextStep[0], nextStep[1]):
582
+ clearCachedPath(agent)
583
+ let reroute = agent.tryRandomDirection()
584
+ return reroute
585
+ let dr = nextStep[0] - agent.state.row
586
+ let dc = nextStep[1] - agent.state.col
587
+ if dr == -1 and dc == 0:
588
+ return agent.cfg.actions.moveNorth
589
+ if dr == 1 and dc == 0:
590
+ return agent.cfg.actions.moveSouth
591
+ if dr == 0 and dc == 1:
592
+ return agent.cfg.actions.moveEast
593
+ if dr == 0 and dc == -1:
594
+ return agent.cfg.actions.moveWest
595
+ agent.cfg.actions.noop
596
+
597
+ proc directionDelta(direction: string): (int, int) =
598
+ case direction
599
+ of "north": (-1, 0)
600
+ of "south": (1, 0)
601
+ of "east": (0, 1)
602
+ of "west": (0, -1)
603
+ else: (0, 0)
604
+
605
+ proc stepInDirection(agent: LadybugAgent, direction: string): Option[int] =
606
+ let delta = directionDelta(direction)
607
+ if delta == (0, 0):
608
+ return none(int)
609
+ let nr = agent.state.row + delta[0]
610
+ let nc = agent.state.col + delta[1]
611
+ if not agent.isTraversable(nr, nc):
612
+ return none(int)
613
+ let action = agent.moveTowards(nr, nc)
614
+ if action == agent.cfg.actions.noop:
615
+ return none(int)
616
+ some(action)
617
+
618
+ proc tryRandomDirection(agent: LadybugAgent): int =
619
+ var dirs = @DirectionNames
620
+ agent.random.shuffle(dirs)
621
+ for dir in dirs:
622
+ let action = agent.stepInDirection(dir)
623
+ if action.isSome():
624
+ clearCachedPath(agent)
625
+ return action.get()
626
+ return agent.cfg.actions.noop
627
+
628
+ proc explore(agent: LadybugAgent): int =
629
+ clearCachedPath(agent)
630
+ if agent.state.explorationDirection.len > 0:
631
+ let steps = agent.state.stepCount - agent.state.explorationDirectionSetStep
632
+ if steps >= explorationDirectionPersistence:
633
+ agent.state.explorationDirection = ""
634
+
635
+ # Escape mode: navigate toward hub if stuck recently
636
+ if agent.state.explorationEscapeUntilStep > agent.state.stepCount:
637
+ agent.state.explorationDirection = ""
638
+ if agent.state.stations["hub"].isSome():
639
+ let hubLoc = agent.state.stations["hub"].get()
640
+ if isAdjacent(agent.state.row, agent.state.col, hubLoc.y, hubLoc.x):
641
+ agent.state.explorationEscapeUntilStep = 0
642
+ else:
643
+ return agent.moveTowards(hubLoc.y, hubLoc.x, reachAdjacent = true)
644
+ else:
645
+ agent.state.explorationEscapeUntilStep = 0
646
+ else:
647
+ # Check if we've stayed within a small area recently; if so, trigger escape
648
+ let historyLen = agent.state.positionHistory.len
649
+ if historyLen >= explorationAreaCheckWindow and agent.state.stations["hub"].isSome():
650
+ var minRow = high(int)
651
+ var maxRow = low(int)
652
+ var minCol = high(int)
653
+ var maxCol = low(int)
654
+ for i in (historyLen - explorationAreaCheckWindow) ..< historyLen:
655
+ let pos = agent.state.positionHistory[i]
656
+ if pos.y < minRow:
657
+ minRow = pos.y
658
+ if pos.y > maxRow:
659
+ maxRow = pos.y
660
+ if pos.x < minCol:
661
+ minCol = pos.x
662
+ if pos.x > maxCol:
663
+ maxCol = pos.x
664
+ let areaHeight = maxRow - minRow + 1
665
+ let areaWidth = maxCol - minCol + 1
666
+ if areaHeight <= explorationAreaSizeThreshold and areaWidth <= explorationAreaSizeThreshold:
667
+ let hubLoc = agent.state.stations["hub"].get()
668
+ let dist = abs(agent.state.row - hubLoc.y) + abs(agent.state.col - hubLoc.x)
669
+ if dist > explorationHubDistanceThreshold:
670
+ agent.state.explorationEscapeUntilStep = agent.state.stepCount + explorationEscapeDuration
671
+ agent.state.explorationDirection = ""
672
+ return agent.moveTowards(hubLoc.y, hubLoc.x, reachAdjacent = true)
673
+
674
+ if agent.state.explorationDirection.len > 0:
675
+ let action = agent.stepInDirection(agent.state.explorationDirection)
676
+ if action.isSome():
677
+ return action.get()
678
+ agent.state.explorationDirection = ""
679
+
680
+ var dirs = @DirectionNames
681
+ agent.random.shuffle(dirs)
682
+ for dir in dirs:
683
+ let action = agent.stepInDirection(dir)
684
+ if action.isSome():
685
+ agent.state.explorationDirection = dir
686
+ agent.state.explorationDirectionSetStep = agent.state.stepCount
687
+ return action.get()
688
+ return agent.tryRandomDirection()
689
+
690
+ proc exploreUntil(agent: LadybugAgent, condition: proc (): bool, reason: string): Option[int] =
691
+ if condition():
692
+ return none(int)
693
+ agent.state.explorationEscapeUntilStep = 0
694
+ some(agent.explore())
695
+
696
+ proc navigateToAdjacent(
697
+ agent: LadybugAgent,
698
+ targetRow: int,
699
+ targetCol: int
700
+ ): Option[int] =
701
+ if isAdjacent(agent.state.row, agent.state.col, targetRow, targetCol):
702
+ return none(int)
703
+ let action = agent.moveTowards(targetRow, targetCol, reachAdjacent = true)
704
+ if action == agent.cfg.actions.noop:
705
+ return some(agent.explore())
706
+ some(action)
707
+
708
+ proc moveIntoCell(agent: LadybugAgent, targetRow: int, targetCol: int): int =
709
+ if agent.state.row == targetRow and agent.state.col == targetCol:
710
+ return agent.cfg.actions.noop
711
+ if agent.isAgentBlocking(targetRow, targetCol):
712
+ clearCachedPath(agent)
713
+ return agent.tryRandomDirection()
714
+ agent.state.usingObjectThisStep = true
715
+ return agent.moveTowards(targetRow, targetCol, allowGoalBlock = true)
716
+
717
+ proc useExtractor(agent: LadybugAgent, extractor: ExtractorInfo): int =
718
+ if extractor.cooldownRemaining > 0:
719
+ agent.state.waitingAtExtractor = some(extractor.position)
720
+ inc agent.state.waitSteps
721
+ return agent.cfg.actions.noop
722
+ if extractor.remainingUses == 0 or extractor.clipped:
723
+ clearWaiting(agent)
724
+ return agent.cfg.actions.noop
725
+ agent.state.pendingUseResource = extractor.resource
726
+ agent.state.pendingUseAmount = agent.getInventoryValue(extractor.resource)
727
+ agent.state.waitingAtExtractor = some(extractor.position)
728
+ agent.state.waitSteps = 0
729
+ return agent.moveIntoCell(extractor.position.y, extractor.position.x)
730
+
731
+ proc handleStuck(agent: LadybugAgent): Option[int] =
732
+ if not agent.state.stuckLoopDetected:
733
+ return none(int)
734
+ agent.state.stuckLoopDetected = false
735
+ some(agent.tryRandomDirection())
736
+
737
+ proc updatePhase(agent: LadybugAgent) =
738
+ let previousPhase = agent.state.phase
739
+ if agent.state.energy < rechargeThresholdLow:
740
+ agent.state.phase = rechargePhase
741
+ clearWaiting(agent)
742
+ if previousPhase != agent.state.phase:
743
+ clearCachedPath(agent)
744
+ return
745
+ if agent.state.phase == rechargePhase and agent.state.energy < rechargeThresholdHigh:
746
+ return
747
+ if agent.state.hearts > 0:
748
+ agent.state.phase = deliverPhase
749
+ clearWaiting(agent)
750
+ if previousPhase != agent.state.phase:
751
+ clearCachedPath(agent)
752
+ return
753
+ if agent.state.heartRecipeKnown:
754
+ var ready = true
755
+ for resource, amount in agent.state.heartRecipe.pairs:
756
+ if agent.getInventoryValue(resource) < amount:
757
+ ready = false
758
+ break
759
+ if ready:
760
+ agent.state.phase = assemblePhase
761
+ clearWaiting(agent)
762
+ if previousPhase != agent.state.phase:
763
+ clearCachedPath(agent)
764
+ return
765
+ agent.state.phase = gatherPhase
766
+ if previousPhase != agent.state.phase:
767
+ clearCachedPath(agent)
768
+
769
+ proc desiredVibe(agent: LadybugAgent): string =
770
+ case agent.state.phase
771
+ of gatherPhase:
772
+ if agent.state.targetResource.len > 0:
773
+ return resourceVibe(agent.state.targetResource)
774
+ return "carbon_a"
775
+ of assemblePhase:
776
+ return "heart_a"
777
+ of deliverPhase:
778
+ return "default"
779
+ of rechargePhase:
780
+ return "junction"
781
+
782
+ proc vibeAction(agent: LadybugAgent, vibe: string): int =
783
+ case vibe
784
+ of "carbon_a": agent.cfg.actions.vibeCarbonA
785
+ of "carbon_b": agent.cfg.actions.vibeCarbonB
786
+ of "oxygen_a": agent.cfg.actions.vibeOxygenA
787
+ of "oxygen_b": agent.cfg.actions.vibeOxygenB
788
+ of "germanium_a": agent.cfg.actions.vibeGermaniumA
789
+ of "germanium_b": agent.cfg.actions.vibeGermaniumB
790
+ of "silicon_a": agent.cfg.actions.vibeSiliconA
791
+ of "silicon_b": agent.cfg.actions.vibeSiliconB
792
+ of "heart_a": agent.cfg.actions.vibeHeartA
793
+ of "heart_b": agent.cfg.actions.vibeHeartB
794
+ of "junction": agent.cfg.actions.vibeCharger
795
+ of "gear": agent.cfg.actions.vibeGear
796
+ of "hub": agent.cfg.actions.vibeHub
797
+ of "chest": agent.cfg.actions.vibeChest
798
+ of "wall": agent.cfg.actions.vibeWall
799
+ of "default": agent.cfg.actions.vibeDefault
800
+ else: agent.cfg.actions.vibeDefault
801
+
802
+ proc doGather(agent: LadybugAgent): int =
803
+ let waitAction = agent.handleWaiting()
804
+ if waitAction.isSome():
805
+ return waitAction.get()
806
+ if not agent.state.heartRecipeKnown:
807
+ agent.state.targetResource = ""
808
+ clearWaiting(agent)
809
+ return agent.explore()
810
+ let deficits = agent.calculateDeficits()
811
+ var needsResources = false
812
+ for deficit in deficits.values:
813
+ if deficit > 0:
814
+ needsResources = true
815
+ break
816
+ if not needsResources:
817
+ clearWaiting(agent)
818
+ return agent.cfg.actions.noop
819
+ let exploreAction = agent.exploreUntil(
820
+ proc (): bool = agent.findAnyNeededExtractor().isSome(),
821
+ "need extractor"
822
+ )
823
+ if exploreAction.isSome():
824
+ return exploreAction.get()
825
+ let extractorChoice = agent.findAnyNeededExtractor()
826
+ if extractorChoice.isNone():
827
+ return agent.explore()
828
+ let (extractor, resource) = extractorChoice.get()
829
+ agent.state.targetResource = resource
830
+ let nav = agent.navigateToAdjacent(extractor.position.y, extractor.position.x)
831
+ if nav.isSome():
832
+ clearWaiting(agent)
833
+ return nav.get()
834
+ return agent.useExtractor(extractor)
835
+
836
+ proc doAssemble(agent: LadybugAgent): int =
837
+ agent.state.targetResource = ""
838
+ let exploreAction = agent.exploreUntil(
839
+ proc (): bool = agent.state.stations["hub"].isSome(),
840
+ "need hub"
841
+ )
842
+ if exploreAction.isSome():
843
+ return exploreAction.get()
844
+ let loc = agent.state.stations["hub"].get()
845
+ let nav = agent.navigateToAdjacent(loc.y, loc.x)
846
+ if nav.isSome():
847
+ return nav.get()
848
+ return agent.moveIntoCell(loc.y, loc.x)
849
+
850
+ proc doDeliver(agent: LadybugAgent): int =
851
+ agent.state.targetResource = ""
852
+ let exploreAction = agent.exploreUntil(
853
+ proc (): bool = agent.state.stations["chest"].isSome(),
854
+ "need chest"
855
+ )
856
+ if exploreAction.isSome():
857
+ return exploreAction.get()
858
+ let loc = agent.state.stations["chest"].get()
859
+ let nav = agent.navigateToAdjacent(loc.y, loc.x)
860
+ if nav.isSome():
861
+ return nav.get()
862
+ return agent.moveIntoCell(loc.y, loc.x)
863
+
864
+ proc doRecharge(agent: LadybugAgent): int =
865
+ agent.state.targetResource = ""
866
+ let exploreAction = agent.exploreUntil(
867
+ proc (): bool = agent.state.stations["junction"].isSome(),
868
+ "need junction"
869
+ )
870
+ if exploreAction.isSome():
871
+ return exploreAction.get()
872
+ let loc = agent.state.stations["junction"].get()
873
+ let nav = agent.navigateToAdjacent(loc.y, loc.x)
874
+ if nav.isSome():
875
+ return nav.get()
876
+ return agent.moveIntoCell(loc.y, loc.x)
877
+
878
+ proc executePhase(agent: LadybugAgent): int =
879
+ case agent.state.phase
880
+ of gatherPhase:
881
+ doGather(agent)
882
+ of assemblePhase:
883
+ doAssemble(agent)
884
+ of deliverPhase:
885
+ doDeliver(agent)
886
+ of rechargePhase:
887
+ doRecharge(agent)
888
+
889
+ proc step*(
890
+ agent: LadybugAgent,
891
+ numAgents: int,
892
+ numTokens: int,
893
+ sizeToken: int,
894
+ rawObservations: pointer,
895
+ numActions: int,
896
+ rawActions: pointer
897
+ ) {.raises: [].} =
898
+ discard numAgents
899
+ discard numActions
900
+ let observations = cast[ptr UncheckedArray[uint8]](rawObservations)
901
+ let actions = cast[ptr UncheckedArray[int32]](rawActions)
902
+ let offset = agent.agentId * numTokens * sizeToken
903
+ let agentObservation = cast[pointer](observations[offset].addr)
904
+ try:
905
+ inc agent.state.stepCount
906
+ updatePosition(agent)
907
+ let visible = agent.decodeObservation(numTokens, sizeToken, agentObservation)
908
+ agent.updateInventory(visible)
909
+ discoverObjects(agent, visible)
910
+ updatePhase(agent)
911
+ let stuckAction = handleStuck(agent)
912
+ if stuckAction.isSome():
913
+ let actionId = stuckAction.get()
914
+ actions[agent.agentId] = actionId.int32
915
+ agent.state.lastAction = actionId
916
+ return
917
+ let desired = agent.desiredVibe()
918
+ if agent.state.currentGlyph != desired:
919
+ agent.state.currentGlyph = desired
920
+ let actionId = agent.vibeAction(desired)
921
+ actions[agent.agentId] = actionId.int32
922
+ agent.state.lastAction = actionId
923
+ return
924
+ let actionId = agent.executePhase()
925
+ actions[agent.agentId] = actionId.int32
926
+ agent.state.lastAction = actionId
927
+ except:
928
+ echo getCurrentException().getStackTrace()
929
+ echo getCurrentExceptionMsg()
930
+ actions[agent.agentId] = agent.cfg.actions.noop.int32
931
+ agent.state.lastAction = agent.cfg.actions.noop
932
+
933
+ proc newLadybugPolicy*(environmentConfig: string): LadybugPolicy =
934
+ let cfg = parseConfig(environmentConfig)
935
+ var agents: seq[LadybugAgent] = @[]
936
+ for id in 0 ..< cfg.config.numAgents:
937
+ agents.add(newLadybugAgent(id, environmentConfig))
938
+ LadybugPolicy(agents: agents)
939
+
940
+ proc stepBatch*(
941
+ policy: LadybugPolicy,
942
+ agentIds: pointer,
943
+ numAgentIds: int,
944
+ numAgents: int,
945
+ numTokens: int,
946
+ sizeToken: int,
947
+ rawObservations: pointer,
948
+ numActions: int,
949
+ rawActions: pointer
950
+ ) =
951
+ let ids = cast[ptr UncheckedArray[int32]](agentIds)
952
+ for i in 0 ..< numAgentIds:
953
+ let idx = int(ids[i])
954
+ step(policy.agents[idx], numAgents, numTokens, sizeToken, rawObservations, numActions, rawActions)