Erinaldordso commited on
Commit
802836b
·
verified ·
1 Parent(s): 3548b62

Recover enclosed Creative builders onto verified open surface sites

Browse files
Files changed (3) hide show
  1. README.md +6 -0
  2. tests/test_voyager_navigation.js +27 -0
  3. voyager_place.js +84 -1
README.md CHANGED
@@ -116,6 +116,12 @@ Os logs `[llm_metric]` registram bot, papel, latência e tokens reais por chamad
116
  de Curriculum, Action, Critic e Skill Manager. No Gemini CLI, a temperatura zero
117
  é aplicada por `modelConfigs.customOverrides`, além do cliente Voyager.
118
 
 
 
 
 
 
 
119
  - CLI fixado em `@google/gemini-cli@0.61.0`, modelo efetivo `gemini-3.5-flash-lite`.
120
  - Segredos privados do Space: `GEMINI_BOT_KEY_1` a `GEMINI_BOT_KEY_6`.
121
  - Nenhuma chave deve ser salva no Git, no checkpoint ou nas saídas de status.
 
116
  de Curriculum, Action, Critic e Skill Manager. No Gemini CLI, a temperatura zero
117
  é aplicada por `modelConfigs.customOverrides`, além do cliente Voyager.
118
 
119
+ Se as construções cercarem o bot, o helper procura uma área de superfície já
120
+ carregada e livre, a 8–12 blocos por eixo. O voo Creative de recuperação verifica
121
+ o volume do jogador em toda a rota, usa segmentos de até 0,4 bloco, sobe no máximo
122
+ 4 blocos para ultrapassar a construção e pousa em solo confirmado. Não destrói
123
+ blocos, não teleporta e não altera a exploração horizontal do Curriculum.
124
+
125
  - CLI fixado em `@google/gemini-cli@0.61.0`, modelo efetivo `gemini-3.5-flash-lite`.
126
  - Segredos privados do Space: `GEMINI_BOT_KEY_1` a `GEMINI_BOT_KEY_6`.
127
  - Nenhuma chave deve ser salva no Git, no checkpoint ou nas saídas de status.
tests/test_voyager_navigation.js CHANGED
@@ -61,6 +61,33 @@ vm.runInContext(fs.readFileSync(path.join(__dirname,'../voyager_place.js'),'utf8
61
  assert.equal(s.bot.blockAt(new Vec3(1,64,0)).name, 'snow_block');
62
  console.log('PASS pinned GoalPlaceBlock raycast, blocked-sector recovery and 4 real simulated world updates');
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  // The old 1.3.1 liquid constants are undefined on MC 1.19. Compare actual
65
  // physics integration, not a string/version assertion alone.
66
  const liquidWorld = {getBlock(p) {
 
61
  assert.equal(s.bot.blockAt(new Vec3(1,64,0)).name, 'snow_block');
62
  console.log('PASS pinned GoalPlaceBlock raycast, blocked-sector recovery and 4 real simulated world updates');
63
 
64
+ const enclosed = () => {
65
+ const f=fixture();
66
+ for(let y=64;y<=65;y++) for(let x=-2;x<=2;x++) for(let z=-2;z<=2;z++) {
67
+ if(Math.max(Math.abs(x),Math.abs(z))===2) f.blocks.set(new Vec3(x,y,z).toString(),'stone');
68
+ }
69
+ f.bot.physics={gravity:.08}; f.bot.clearControlStates=()=>{};
70
+ f.bot.creative={startFlying:()=>{f.bot.physics.gravity=0;},stopFlying:()=>{f.bot.physics.gravity=.08;},
71
+ flyTo:async p=>{assert(f.bot.entity.position.distanceTo(p)<=.401);f.bot.entity.position=p.clone();}};
72
+ return f;
73
+ };
74
+ const boxed=enclosed(), original=new Map(boxed.blocks);
75
+ await context.placeBlocks(boxed.bot,'snow_block',3);
76
+ assert.equal(boxed.bot.entity.position.y,64);
77
+ assert(boxed.bot.entity.position.x>=8);
78
+ assert.equal(boxed.bot.physics.gravity,.08);
79
+ for(const [key,value] of original) assert.equal(boxed.blocks.get(key),value);
80
+ assert.equal(boxed.bot._voyagerVerifiedActions.length,3);
81
+ const interrupted=enclosed();
82
+ interrupted.bot.creative.flyTo=async()=>{throw Error('disconnected');};
83
+ await assert.rejects(context.moveToOpenPlacementSite(interrupted.bot),/disconnected/);
84
+ assert.equal(interrupted.bot.physics.gravity,.08);
85
+ const unloaded=enclosed(), getBlock=unloaded.bot.blockAt;
86
+ unloaded.bot.blockAt=p=>Math.abs(p.x)>5 || Math.abs(p.z)>5 ? null : getBlock(p);
87
+ await assert.rejects(context.moveToOpenPlacementSite(unloaded.bot),/No known clear route/);
88
+ assert.equal(unloaded.bot.entity.position.y,64);
89
+ console.log('PASS enclosed Creative recovery, preserved builds, surface landing, unknown-chunk refusal and gravity cleanup');
90
+
91
  // The old 1.3.1 liquid constants are undefined on MC 1.19. Compare actual
92
  // physics integration, not a string/version assertion alone.
93
  const liquidWorld = {getBlock(p) {
voyager_place.js CHANGED
@@ -39,7 +39,82 @@ async function placeItem(bot, name, position) {
39
  bot.save(`${name}_placed`);
40
  }
41
 
42
- async function placeBlocks(bot, name, count = 1) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  if (!Number.isInteger(count) || count < 1 || count > 4) throw new Error("Place 1..4 blocks per task");
44
  const origin = bot.entity.position.floored();
45
  let placed = 0, attempts = 0, lastError = "No supported empty cells nearby";
@@ -65,6 +140,10 @@ async function placeBlocks(bot, name, count = 1) {
65
  distance: bot.entity.position.distanceTo(position),
66
  }));
67
  supported.sort((a,b) => Number(b.visible) - Number(a.visible) || a.distance - b.distance);
 
 
 
 
68
  for (const {position} of supported) {
69
  try {
70
  await placeItem(bot,name,position);
@@ -79,5 +158,9 @@ async function placeBlocks(bot, name, count = 1) {
79
  }
80
  if (++attempts >= 24) break;
81
  }
 
 
 
 
82
  throw new Error(`Placed ${placed}/${count} ${name}: ${lastError}; supported=${supported.length}, visible=${supported.filter(c=>c.visible).length}, position=${bot.entity.position}`);
83
  }
 
39
  bot.save(`${name}_placed`);
40
  }
41
 
42
+ async function moveToOpenPlacementSite(bot) {
43
+ // Construction can enclose a Creative bot. Recover over known blocks,
44
+ // without digging the build, teleporting, or exploring underground.
45
+ const start = bot.entity.position.clone();
46
+ const clear = (p) => {
47
+ if (![p.x,p.y,p.z].every(Number.isFinite)) return false;
48
+ for (let x=Math.floor(p.x-.3);x<=Math.floor(p.x+.3);x++) {
49
+ for (let y=Math.floor(p.y+.001);y<=Math.floor(p.y+1.799);y++) {
50
+ for (let z=Math.floor(p.z-.3);z<=Math.floor(p.z+.3);z++) {
51
+ const b=bot.blockAt(new Vec3(x,y,z));
52
+ if (!b || b.boundingBox !== 'empty' || ['water','lava'].includes(b.name)) return false;
53
+ }
54
+ }
55
+ }
56
+ return true;
57
+ };
58
+ const segment = (from,to) => {
59
+ const n=Math.max(1,Math.ceil(from.distanceTo(to)/.4)), points=[];
60
+ for(let i=1;i<=n;i++) {
61
+ const p=from.plus(to.minus(from).scaled(i/n));
62
+ if(!clear(p)) return null;
63
+ points.push(p);
64
+ }
65
+ return points;
66
+ };
67
+ const openSite = (p) => {
68
+ for(let dx=-2;dx<=2;dx++) for(let dz=-2;dz<=2;dz++) {
69
+ const q=p.offset(dx,0,dz), support=bot.blockAt(q.offset(0,-.01,0));
70
+ if(!support || support.boundingBox !== 'block' || !clear(q)) return false;
71
+ }
72
+ return true;
73
+ };
74
+ let route, landing;
75
+ for(const radius of [8,12]) {
76
+ for(const [dx,dz] of [[1,0],[0,1],[-1,0],[0,-1],[1,1],[-1,1],[-1,-1],[1,-1]]) {
77
+ for(let y=Math.floor(start.y);y>=Math.max(64,Math.floor(start.y)-2);y--) {
78
+ const target=new Vec3(Math.floor(start.x)+dx*radius+.5,y,Math.floor(start.z)+dz*radius+.5);
79
+ if(!openSite(target)) continue;
80
+ for(const rise of [2,3,4]) {
81
+ const up=new Vec3(start.x,Math.floor(start.y)+rise,start.z);
82
+ const across=new Vec3(target.x,up.y,target.z);
83
+ const a=segment(start,up), b=a && segment(up,across), c=b && segment(across,target);
84
+ if(c) {route=[...a,...b,...c];landing=target;break;}
85
+ }
86
+ if(route) break;
87
+ }
88
+ if(route) break;
89
+ }
90
+ if(route) break;
91
+ }
92
+ if(!route) throw new Error('No known clear route to an open surface construction site');
93
+ bot.pathfinder.setGoal(null);
94
+ bot.clearControlStates();
95
+ const deadline=Date.now()+20000;
96
+ bot.creative.startFlying();
97
+ try {
98
+ for(const p of route) {
99
+ if(Date.now()>deadline || !clear(p) || bot.entity.position.distanceTo(p)>.6) {
100
+ throw new Error('Construction recovery route changed; stopping flight');
101
+ }
102
+ // The documented Creative API has no collision avoidance: every
103
+ // segment is prechecked and shorter than its 0.5-block inner loop.
104
+ await bot.creative.flyTo(p);
105
+ }
106
+ } finally {
107
+ bot.creative.stopFlying();
108
+ bot.clearControlStates();
109
+ }
110
+ await bot.waitForTicks(4);
111
+ if(bot.entity.position.distanceTo(landing)>1 || !openSite(landing)) {
112
+ throw new Error('Construction recovery did not reach the verified surface site');
113
+ }
114
+ console.info(`[placement recovery] surface ${start} -> ${bot.entity.position}`);
115
+ }
116
+
117
+ async function placeBlocks(bot, name, count = 1, relocated = false) {
118
  if (!Number.isInteger(count) || count < 1 || count > 4) throw new Error("Place 1..4 blocks per task");
119
  const origin = bot.entity.position.floored();
120
  let placed = 0, attempts = 0, lastError = "No supported empty cells nearby";
 
140
  distance: bot.entity.position.distanceTo(position),
141
  }));
142
  supported.sort((a,b) => Number(b.visible) - Number(a.visible) || a.distance - b.distance);
143
+ if (!relocated && !supported.some(c=>c.visible) && bot.game.gameMode === 'creative') {
144
+ await moveToOpenPlacementSite(bot);
145
+ return placeBlocks(bot,name,count,true);
146
+ }
147
  for (const {position} of supported) {
148
  try {
149
  await placeItem(bot,name,position);
 
158
  }
159
  if (++attempts >= 24) break;
160
  }
161
+ if(!relocated && bot.game.gameMode === 'creative') {
162
+ await moveToOpenPlacementSite(bot);
163
+ return placeBlocks(bot,name,count-placed,true);
164
+ }
165
  throw new Error(`Placed ${placed}/${count} ${name}: ${lastError}; supported=${supported.length}, visible=${supported.filter(c=>c.visible).length}, position=${bot.entity.position}`);
166
  }