naldo commited on
Commit
d17cab1
·
1 Parent(s): 0e7d3e7

Bound Creative exploration and favor verifiable flat-world tasks

Browse files
tests/test_voyager_patch.py CHANGED
@@ -204,10 +204,10 @@ def check_curriculum_behavior(source):
204
  agent.llm = Mock(side_effect=[SimpleNamespace(content="Task: Craft a stone pickaxe"),
205
  SimpleNamespace(content="Task: Obtain a wood log")])
206
  task, _ = agent.propose_next_ai_task(messages=messages, max_retries=2)
207
- assert task == "Explore 16 blocks north"
208
  agent.llm = Mock(side_effect=[SimpleNamespace(content="Explain only") for _ in range(3)])
209
  task, _ = agent.propose_next_ai_task(messages=[SimpleNamespace(content="Nearby blocks: dirt, stone")])
210
- assert task == "Mine 1 dirt" and agent.llm.call_count == 3
211
  assert task_spec(task) not in {task_spec(old) for old in agent.completed_tasks + agent.failed_tasks}
212
  agent.mode = "auto"
213
  agent.render_system_message = Mock(return_value=SimpleNamespace(content="Creative"))
@@ -297,7 +297,29 @@ async function run(direction, mode) {
297
  assert.equal(chats,0);
298
  return goal;
299
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  (async()=>{
 
301
  const g=await run({x:0.25,y:-0.4,z:-0.75},'creative');
302
  assert(g.x>0 && g.z<0 && !('y' in g));
303
  const v=await run({x:0,y:-1,z:0},'creative');
 
204
  agent.llm = Mock(side_effect=[SimpleNamespace(content="Task: Craft a stone pickaxe"),
205
  SimpleNamespace(content="Task: Obtain a wood log")])
206
  task, _ = agent.propose_next_ai_task(messages=messages, max_retries=2)
207
+ assert task == "Place 1 stone"
208
  agent.llm = Mock(side_effect=[SimpleNamespace(content="Explain only") for _ in range(3)])
209
  task, _ = agent.propose_next_ai_task(messages=[SimpleNamespace(content="Nearby blocks: dirt, stone")])
210
+ assert task == "Place 1 stone" and agent.llm.call_count == 3
211
  assert task_spec(task) not in {task_spec(old) for old in agent.completed_tasks + agent.failed_tasks}
212
  agent.mode = "auto"
213
  agent.render_system_message = Mock(return_value=SimpleNamespace(content="Creative"))
 
297
  assert.equal(chats,0);
298
  return goal;
299
  }
300
+ async function bounded() {
301
+ let interval, timer;
302
+ const goals=[];
303
+ const context={Math,Number,Promise,Error,
304
+ GoalNearXZ: class {constructor(x,z){Object.assign(this,{x,z});}},
305
+ setInterval:fn=>(interval=fn,1), setTimeout:fn=>(timer=fn,2),
306
+ clearInterval:()=>{}, clearTimeout:()=>{}};
307
+ vm.createContext(context); vm.runInContext(source,context);
308
+ const bot={game:{gameMode:'creative'},entity:{position:{x:0,y:64,z:0}},
309
+ pathfinder:{setGoal:g=>goals.push(g)}};
310
+ const promise=context.exploreUntil(bot,{x:1,y:0,z:1},60,()=>false);
311
+ for(let i=0;i<12;i++) {
312
+ interval();
313
+ const goal=goals[goals.length-1];
314
+ if(goal===null) break;
315
+ assert(Math.hypot(goal.x,goal.z)<=32.00001);
316
+ bot.entity.position.x=goal.x; bot.entity.position.z=goal.z;
317
+ }
318
+ assert.equal(await promise,null);
319
+ assert.equal(goals[goals.length-1],null);
320
+ }
321
  (async()=>{
322
+ await bounded();
323
  const g=await run({x:0.25,y:-0.4,z:-0.75},'creative');
324
  assert(g.x>0 && g.z<0 && !('y' in g));
325
  const v=await run({x:0,y:-1,z:0},'creative');
voyager_stack.py CHANGED
@@ -356,10 +356,11 @@ async function creativeGiveItem(bot, name, count = 1) {
356
  const candidates = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
357
  [dx, dz] = candidates[Math.floor(Math.random() * candidates.length)];
358
  }
359
- if (bot.game && bot.game.gameMode === "creative") dy = 0;
360
-
361
- maxTime = Math.min(maxTime, 120);
362
- return new Promise((resolve, reject) => {
 
363
  let explorationInterval;
364
  let maxTimeTimeout;
365
  const cleanUp = () => {
@@ -375,7 +376,19 @@ async function creativeGiveItem(bot, name, count = 1) {
375
  resolve(result);
376
  return;
377
  }
378
- const distance = Math.floor(Math.random() * 12 + 8);
 
 
 
 
 
 
 
 
 
 
 
 
379
  const x = bot.entity.position.x + distance * dx;
380
  const y = bot.entity.position.y + distance * dy;
381
  const z = bot.entity.position.z + distance * dz;
 
356
  const candidates = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
357
  [dx, dz] = candidates[Math.floor(Math.random() * candidates.length)];
358
  }
359
+ if (bot.game && bot.game.gameMode === "creative") dy = 0;
360
+
361
+ maxTime = Math.min(maxTime, 120);
362
+ const start = {x: bot.entity.position.x, z: bot.entity.position.z};
363
+ return new Promise((resolve, reject) => {
364
  let explorationInterval;
365
  let maxTimeTimeout;
366
  const cleanUp = () => {
 
376
  resolve(result);
377
  return;
378
  }
379
+ let distance = Math.floor(Math.random() * 12 + 8);
380
+ if (bot.game && bot.game.gameMode === "creative") {
381
+ const travelled = Math.hypot(
382
+ bot.entity.position.x - start.x, bot.entity.position.z - start.z
383
+ );
384
+ const remaining = 32 - travelled;
385
+ if (remaining <= 1) {
386
+ cleanUp();
387
+ resolve(null);
388
+ return;
389
+ }
390
+ distance = Math.min(distance, remaining / Math.hypot(dx, dz));
391
+ }
392
  const x = bot.entity.position.x + distance * dx;
393
  const y = bot.entity.position.y + distance * dy;
394
  const z = bot.entity.position.z + distance * dz;
voyager_verification.py CHANGED
@@ -134,26 +134,11 @@ def choose_curriculum_task(response, completed, failed):
134
 
135
 
136
  def fallback_curriculum_task(messages, completed, failed):
137
- """Use observed blocks first; otherwise rotate through safe Creative goals."""
138
- observed = "\n".join(str(getattr(message, "content", message)) for message in messages)
139
- nearby = re.findall(r"(?im)^Nearby blocks:\s*([^\n]+)", observed)
140
- if nearby:
141
- for name in nearby[-1].split(","):
142
- name = name.strip().lower().replace(" ", "_")
143
- if name != "none" and re.fullmatch(r"[a-z][a-z0-9_]*", name):
144
- task = canonical_task(f"Mine 1 {name}")
145
- if task:
146
- try:
147
- return validate_task(task, completed, failed)
148
- except ValueError:
149
- pass
150
- for distance in (16, 24, 32):
151
- for direction in ("north", "east", "south", "west"):
152
- task = f"Explore {distance} blocks {direction}"
153
- try:
154
- return validate_task(task, completed, failed)
155
- except ValueError:
156
- pass
157
  palette = (
158
  "stone", "dirt", "grass_block", "cobblestone", "oak_planks", "spruce_planks",
159
  "birch_planks", "sandstone", "bricks", "glass", "white_wool", "orange_wool",
@@ -170,6 +155,13 @@ def fallback_curriculum_task(messages, completed, failed):
170
  return validate_task(task, completed, failed)
171
  except ValueError:
172
  pass
 
 
 
 
 
 
 
173
  # The finite grammar can theoretically be exhausted. Keep Voyager alive;
174
  # in practice hundreds of distinct safe objectives remain in the palette.
175
  return "Explore 16 blocks north"
 
134
 
135
 
136
  def fallback_curriculum_task(messages, completed, failed):
137
+ """Choose a verifiable Creative placement before bounded exploration.
138
+
139
+ Nearby block names lack coordinates. In a flat world they mostly describe
140
+ the Y=63 floor, which mineBlock deliberately refuses to dig from Y=64.
141
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  palette = (
143
  "stone", "dirt", "grass_block", "cobblestone", "oak_planks", "spruce_planks",
144
  "birch_planks", "sandstone", "bricks", "glass", "white_wool", "orange_wool",
 
155
  return validate_task(task, completed, failed)
156
  except ValueError:
157
  pass
158
+ for distance in (16, 24, 32):
159
+ for direction in ("north", "east", "south", "west"):
160
+ task = f"Explore {distance} blocks {direction}"
161
+ try:
162
+ return validate_task(task, completed, failed)
163
+ except ValueError:
164
+ pass
165
  # The finite grammar can theoretically be exhausted. Keep Voyager alive;
166
  # in practice hundreds of distinct safe objectives remain in the palette.
167
  return "Explore 16 blocks north"