irregular6612 Claude Opus 4.8 (1M context) commited on
Commit
5eb529f
·
1 Parent(s): 6c48e8f

fix(resource_race): collected resource leaves the field (live sprite removal + memory pickup frame)

Browse files
proteus/game/runtime/multiagent_director.py CHANGED
@@ -191,7 +191,8 @@ def author_resource_race(
191
  for t in range(1, max_turns + 1):
192
  chosen = next(a for a in agents if a.is_chosen)
193
  if _covers(chosen, resource):
194
- turns.append(_frame(agents, None, "right", [resource],
 
195
  ["a0 got resource"], "stay", t))
196
  break
197
  action = _step_toward(chosen, resource)
 
191
  for t in range(1, max_turns + 1):
192
  chosen = next(a for a in agents if a.is_chosen)
193
  if _covers(chosen, resource):
194
+ # Collected -> the resource leaves the field on this final frame.
195
+ turns.append(_frame(agents, None, "right", [],
196
  ["a0 got resource"], "stay", t))
197
  break
198
  action = _step_toward(chosen, resource)
proteus/game/scenarios/resource_race.py CHANGED
@@ -91,12 +91,23 @@ class ResourceRace(Scenario):
91
  return 0
92
 
93
  def advance_threat(self, game: MotiveGridGame) -> None:
94
- """No predator; collect any resource the focal now overlaps."""
 
 
 
 
 
 
95
  focal = game.focal_sprite
96
  if focal is None:
97
  return
98
  covered = {(focal.x + i, focal.y + j)
99
  for i in range(focal.width) for j in range(focal.height)}
 
 
 
 
 
100
  self._resources = [c for c in self._resources if c not in covered]
101
 
102
  def check_elimination(self, game: MotiveGridGame) -> bool:
 
91
  return 0
92
 
93
  def advance_threat(self, game: MotiveGridGame) -> None:
94
+ """No predator; collect any resource the focal now overlaps.
95
+
96
+ Collection removes BOTH the bookkeeping cell (used by the persona
97
+ metrics / ``food_cells``) AND the corresponding food ``Sprite`` from the
98
+ engine level, so the collected resource actually disappears from the
99
+ rendered grid (not just the metric list).
100
+ """
101
  focal = game.focal_sprite
102
  if focal is None:
103
  return
104
  covered = {(focal.x + i, focal.y + j)
105
  for i in range(focal.width) for j in range(focal.height)}
106
+ if not covered:
107
+ return
108
+ for sprite in game.current_level.get_sprites_by_name("food"):
109
+ if (sprite.x, sprite.y) in covered:
110
+ game.current_level.remove_sprite(sprite)
111
  self._resources = [c for c in self._resources if c not in covered]
112
 
113
  def check_elimination(self, game: MotiveGridGame) -> bool:
tests/runtime/test_director_resource_race.py CHANGED
@@ -31,3 +31,14 @@ def test_resource_present_until_collected():
31
  # Every turn except the last carries the resource; the last records pickup.
32
  for t in ck.memory_turns[:-1]:
33
  assert t.resources == [(54, 12)]
 
 
 
 
 
 
 
 
 
 
 
 
31
  # Every turn except the last carries the resource; the last records pickup.
32
  for t in ck.memory_turns[:-1]:
33
  assert t.resources == [(54, 12)]
34
+
35
+
36
+ def test_pickup_frame_drops_resource():
37
+ ck = _run()
38
+ last = ck.memory_turns[-1]
39
+ assert any("got resource" in e for e in last.events)
40
+ # Collected -> the resource is gone from the field on the final frame.
41
+ assert last.resources == []
42
+ # Every earlier frame still shows it (unchanged contract).
43
+ for t in ck.memory_turns[:-1]:
44
+ assert t.resources == [(54, 12)]
tests/scenarios/test_resource_race_collection.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tests/scenarios/test_resource_race_collection.py
2
+ """Collecting a resource removes its food sprite from the live engine grid."""
3
+ import random
4
+
5
+ from proteus.game.engine.difficulty import Difficulty
6
+ from proteus.game.engine.grid import MotiveGridGame
7
+ from proteus.game.scenarios.base import get_scenario
8
+ import proteus.game.scenarios # noqa: F401
9
+
10
+
11
+ def _game(seed=1):
12
+ scen = get_scenario("resource_race")()
13
+ return scen, MotiveGridGame(scen, random.Random(seed), Difficulty.EASY, max_steps=40)
14
+
15
+
16
+ def _food_sprites_at(game, cell):
17
+ return [s for s in game.current_level.get_sprites_by_name("food")
18
+ if (s.x, s.y) == cell]
19
+
20
+
21
+ def test_collection_removes_food_sprite_from_engine():
22
+ scen, game = _game()
23
+ target = scen.food_cells()[0]
24
+ assert _food_sprites_at(game, target), "expected a food sprite at the target before collection"
25
+ # Teleport the 2x2 focal onto the resource, then run the collection step.
26
+ game.focal_sprite.set_position(target[0], target[1])
27
+ scen.advance_threat(game)
28
+ # Bookkeeping list AND the engine sprite are both gone.
29
+ assert target not in scen.food_cells()
30
+ assert not _food_sprites_at(game, target), "food sprite should be removed from the level"
31
+
32
+
33
+ def test_uncovered_resources_survive():
34
+ scen, game = _game()
35
+ cells = scen.food_cells()
36
+ target = cells[0]
37
+ # A resource far from the focal footprint must remain after a collection step.
38
+ survivor = next((c for c in cells
39
+ if abs(c[0] - target[0]) > 3 or abs(c[1] - target[1]) > 3), None)
40
+ assert survivor is not None, "test fixture needs a distant second resource"
41
+ game.focal_sprite.set_position(target[0], target[1])
42
+ scen.advance_threat(game)
43
+ assert survivor in scen.food_cells()
44
+ assert _food_sprites_at(game, survivor), "distant food sprite must survive"