Spaces:
Sleeping
Sleeping
| /** | |
| * ============================================================ | |
| * bot.js β Zelin v36.0 "LIBRE" | |
| * ============================================================ | |
| * | |
| * 100% AI-DRIVEN β No predefined steps, no boot sequence. | |
| * The AI decides EVERYTHING. Reflexes are physical instincts only. | |
| * | |
| * Architecture: LLM-driven autonomous code-execution agent | |
| * LLM: GLM-4-Flash (free, unlimited, powerful, fast) | |
| * | |
| * v36.0 CHANGES: | |
| * - REMOVED: Boot sequence (AI decides everything from first spawn) | |
| * - FIXED: GLM-4 API with 120s timeout + 3 retries + exponential backoff | |
| * - FIXED: Emergency reflexes are ACTIVE (actually flee, not just set emotion) | |
| * - ADDED: Auto-tracking metrics from inventory changes | |
| * - ADDED: AI timeout counter for degraded mode | |
| * - ADDED: Post-resawn safety (flee hostiles immediately) | |
| * - PRESERVED: All memory systems (KG, Episodic, Remedy, Spatial, Causal, Skills) | |
| * - PRESERVED: Discord, Benchmark, Critic, Curriculum | |
| */ | |
| // Global error handler β prevents unhandled rejection crashes | |
| process.on('unhandledRejection', (reason, promise) => { | |
| console.error('[UNHANDLED] ' + (reason?.message || reason)); | |
| }); | |
| process.on('uncaughtException', (err) => { | |
| console.error('[UNCAUGHT] ' + err.message); | |
| }); | |
| import mineflayer from 'mineflayer'; | |
| import pkgPathfinder from 'mineflayer-pathfinder'; | |
| const { pathfinder, Movements, goals } = pkgPathfinder; | |
| import pvpModule from 'mineflayer-pvp'; | |
| const pvp = pvpModule.plugin || pvpModule; | |
| import autoEatModule from 'mineflayer-auto-eat'; | |
| const autoEat = autoEatModule.plugin || autoEatModule; | |
| import armorManager from 'mineflayer-armor-manager'; | |
| import collectBlockModule from 'mineflayer-collectblock'; | |
| const collectBlock = collectBlockModule.plugin || collectBlockModule; | |
| import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'; | |
| import { WebSocket } from 'ws'; | |
| import http from 'http'; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CONFIG | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const CONFIG = { | |
| host: process.env.MC_HOST || 'localhost', | |
| port: parseInt(process.env.MC_PORT || '25565'), | |
| username: process.env.MC_USERNAME || 'Zelin_', | |
| version: '1.20.1', | |
| auth: 'offline', | |
| llm: { | |
| key: process.env.GLM_KEY || '995a846a6b984be0aec2cefa0887046b.v72B5F9YdOHTQzhO', | |
| model: 'glm-4-flash', | |
| baseUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions', | |
| }, | |
| thinkInterval: 4000, | |
| maxTokens: 1200, | |
| maxCodeTokens: 2500, | |
| statusFile: '/app/logs/bot_status.json', | |
| memoryFile: '/app/logs/bot_memory.json', | |
| skillFile: '/app/logs/bot_skills.json', | |
| benchmarkFile: '/app/logs/benchmark.json', | |
| remedyFile: '/app/logs/bot_remedies.json', | |
| episodicFile: '/app/logs/bot_episodic.json', | |
| causalFile: '/app/logs/bot_causal.json', | |
| spatialFile: '/app/logs/bot_spatial.json', | |
| discord: { | |
| token: process.env.DISCORD_TOKEN || 'MTQ3NzQ3NjQwNjgxOTU1NzUwNw.G51k4u.5umKgbbkFKrj0tLZSWZPHdrQpg7ZO759judgxQ', | |
| clientId: '1477476406819557507', | |
| guildId: '1241168929993396264', | |
| }, | |
| profile: { | |
| name: 'Zelin', | |
| personality: 'chica mexicana, curiosa, aventurada, a veces timida. Habla super casual, todo minusculas, jerga mexicana (wey, neta, chido, chale, orale, hijole, simon, nel, aguas). Femenina. Mensajes cortos, sin parentesis, sin emojis, sin !! sin ??.', | |
| }, | |
| maxConversationTurns: 40, | |
| codeTimeout: 50000, | |
| maxRetries: 3, // Voyager-style iterative retry | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // UTILITY | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function sleep(ms) { return new Promise(r => setTimeout(r, Math.max(0, ms))); } | |
| function rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } | |
| function clearAllControls() { | |
| if (!bot) return; | |
| for (const c of ['forward', 'back', 'left', 'right', 'jump', 'sprint', 'sneak']) { | |
| try { bot.setControlState(c, false); } catch {} | |
| } | |
| } | |
| function hasValidPosition() { | |
| if (!bot?.entity?.position) return false; | |
| const p = bot.entity.position; | |
| return isFinite(p.x) && isFinite(p.y) && isFinite(p.z) && p.y > -64 && p.y < 320; | |
| } | |
| // TF-IDF-lite for skill/episodic retrieval (better than word overlap) | |
| function tokenize(text) { | |
| return (text || '').toLowerCase().replace(/[^a-z0-9_ÑéΓΓ³ΓΊΓ±\s]/g, '').split(/\s+/).filter(w => w.length > 2); | |
| } | |
| const _idf = {}; | |
| function computeSimilarity(tokensA, tokensB) { | |
| if (!tokensA.length || !tokensB.length) return 0; | |
| const setB = new Set(tokensB); | |
| const overlap = tokensA.filter(t => setB.has(t)).length; | |
| const union = new Set([...tokensA, ...tokensB]).size; | |
| return union > 0 ? overlap / union : 0; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // THROTTLED CHAT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const _cmdQueue = []; | |
| let _cmdQueueActive = false; | |
| async function throttledChat(msg) { | |
| return new Promise((resolve) => { | |
| _cmdQueue.push({ msg, resolve }); | |
| if (!_cmdQueueActive) processCommandQueue(); | |
| }); | |
| } | |
| async function processCommandQueue() { | |
| if (_cmdQueueActive) return; | |
| _cmdQueueActive = true; | |
| while (_cmdQueue.length > 0) { | |
| const { msg, resolve } = _cmdQueue.shift(); | |
| try { if (bot?.entity) bot.chat(msg); } catch {} | |
| await sleep(800); | |
| resolve(); | |
| } | |
| _cmdQueueActive = false; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Vec3 NaN GUARD | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function patchVec3RejectNaN(vec3Instance) { | |
| if (!vec3Instance) return; | |
| try { | |
| const Vec3Class = vec3Instance.constructor; | |
| if (Vec3Class.prototype.set._nanPatched) return; | |
| const origSet = Vec3Class.prototype.set; | |
| Vec3Class.prototype.set = function(x, y, z) { | |
| if (!isFinite(x) || !isFinite(y) || !isFinite(z)) return this; | |
| return origSet.call(this, x, y, z); | |
| }; | |
| Vec3Class.prototype.set._nanPatched = true; | |
| const patches = { | |
| offset: (fn) => function(dx, dy, dz) { | |
| if (!isFinite(dx) || !isFinite(dy) || !isFinite(dz)) return new Vec3Class(this.x, this.y, this.z); | |
| const r = fn.call(this, dx, dy, dz); | |
| return (!isFinite(r.x) || !isFinite(r.y) || !isFinite(r.z)) ? new Vec3Class(this.x, this.y, this.z) : r; | |
| }, | |
| add: (fn) => function(v) { | |
| if (!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z)) return this; | |
| const nx=this.x+v.x,ny=this.y+v.y,nz=this.z+v.z; | |
| if(!isFinite(nx)||!isFinite(ny)||!isFinite(nz))return this; | |
| this.x=nx;this.y=ny;this.z=nz;return this; | |
| }, | |
| subtract: (fn) => function(v) { | |
| if (!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z)) return this; | |
| const nx=this.x-v.x,ny=this.y-v.y,nz=this.z-v.z; | |
| if(!isFinite(nx)||!isFinite(ny)||!isFinite(nz))return this; | |
| this.x=nx;this.y=ny;this.z=nz;return this; | |
| }, | |
| scale: (fn) => function(s) { | |
| if(!isFinite(s))return new Vec3Class(0,0,0); | |
| const r=fn.call(this,s); | |
| return(!isFinite(r.x)||!isFinite(r.y)||!isFinite(r.z))?new Vec3Class(0,0,0):r; | |
| }, | |
| normalize: (fn) => function() { | |
| try{const r=fn.call(this);return(!isFinite(r.x)||!isFinite(r.y)||!isFinite(r.z))?new Vec3Class(0,0,0):r;}catch{return new Vec3Class(0,0,0);} | |
| }, | |
| distanceTo: (fn) => function(v) { | |
| if(!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z))return 0; | |
| if(!isFinite(this.x)||!isFinite(this.y)||!isFinite(this.z))return 0; | |
| try{const d=fn.call(this,v);return isFinite(d)?d:0;}catch{return 0;} | |
| }, | |
| }; | |
| for (const [name, wrapper] of Object.entries(patches)) { | |
| const orig = Vec3Class.prototype[name]; | |
| if (orig) Vec3Class.prototype[name] = wrapper(orig); | |
| } | |
| console.log('[NaN Guard] Vec3 prototype patched'); | |
| } catch (e) { console.error('[NaN Guard] Failed: ' + e.message); } | |
| } | |
| function enforceValidPosition() { | |
| if (!bot?.entity) return; | |
| const pos = bot.entity.position; | |
| const vel = bot.entity.velocity; | |
| if (!isFinite(pos.x) || !isFinite(pos.y) || !isFinite(pos.z)) { | |
| state._nanTotalEver++; | |
| try { vel.set(0,0,0); } catch {} | |
| vel.x=0; vel.y=0; vel.z=0; | |
| const restorePos = state._lastGoodPos || { x:0, y:80, z:0 }; | |
| pos.x=restorePos.x; pos.y=restorePos.y; pos.z=restorePos.z; | |
| state._positionReady = true; | |
| if (!state._nanPhysicsDisabled) { | |
| state._nanPhysicsDisabled = true; | |
| try { bot.physicsEnabled = false; } catch {} | |
| setTimeout(() => { try { bot.physicsEnabled = true; } catch {} state._nanPhysicsDisabled = false; }, 1000); | |
| } | |
| // No /tp recovery β that's cheating. Just reset position locally. | |
| // The physics disable/re-enable cycle handles the actual fix. | |
| } else if (pos.y > -64 && pos.y < 320) { | |
| state._lastGoodPos = { x: pos.x, y: pos.y, z: pos.z }; | |
| state._positionReady = true; | |
| } | |
| if (!isFinite(vel.x) || !isFinite(vel.y) || !isFinite(vel.z)) { | |
| try { vel.set(0,0,0); } catch {} | |
| vel.x=0; vel.y=0; vel.z=0; | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // RECIPE KNOWLEDGE GRAPH β From GITM/Optimus-1 | |
| // Hierarchical Directed Knowledge Graph for Minecraft crafting | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const KNOWLEDGE_GRAPH = { | |
| // Tech tree: what you need before you can make X | |
| dependencies: { | |
| 'oak_planks': { requires: ['oak_log'], station: null }, | |
| 'birch_planks': { requires: ['birch_log'], station: null }, | |
| 'spruce_planks': { requires: ['spruce_log'], station: null }, | |
| 'stick': { requires: ['oak_planks'], station: null }, | |
| 'crafting_table': { requires: ['oak_planks'], station: null }, | |
| 'wooden_pickaxe': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, | |
| 'wooden_axe': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, | |
| 'wooden_sword': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, | |
| 'stone_pickaxe': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, | |
| 'stone_axe': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, | |
| 'stone_sword': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, | |
| 'furnace': { requires: ['cobblestone'], station: null, count: 8 }, | |
| 'iron_ingot': { requires: ['raw_iron', 'coal'], station: 'furnace' }, | |
| 'iron_pickaxe': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, | |
| 'iron_sword': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, | |
| 'iron_axe': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, | |
| 'iron_chestplate': { requires: ['iron_ingot'], station: 'crafting_table', count: 8 }, | |
| 'iron_helmet': { requires: ['iron_ingot'], station: 'crafting_table', count: 5 }, | |
| 'iron_leggings': { requires: ['iron_ingot'], station: 'crafting_table', count: 7 }, | |
| 'iron_boots': { requires: ['iron_ingot'], station: 'crafting_table', count: 4 }, | |
| 'diamond_pickaxe': { requires: ['diamond', 'stick'], station: 'crafting_table' }, | |
| 'diamond_sword': { requires: ['diamond', 'stick'], station: 'crafting_table' }, | |
| 'torch': { requires: ['coal', 'stick'], station: null }, | |
| 'bread': { requires: ['wheat'], station: null, count: 3 }, | |
| 'cooked_beef': { requires: ['beef', 'coal'], station: 'furnace' }, | |
| 'cooked_porkchop': { requires: ['porkchop', 'coal'], station: 'furnace' }, | |
| 'cooked_chicken': { requires: ['chicken', 'coal'], station: 'furnace' }, | |
| 'cooked_mutton': { requires: ['mutton', 'coal'], station: 'furnace' }, | |
| 'chest': { requires: ['oak_planks'], station: null, count: 8 }, | |
| 'bed': { requires: ['oak_planks', 'wool'], station: 'crafting_table' }, | |
| 'shield': { requires: ['oak_planks', 'iron_ingot'], station: 'crafting_table' }, | |
| 'bow': { requires: ['stick', 'string'], station: 'crafting_table' }, | |
| 'arrow': { requires: ['flint', 'stick', 'feather'], station: 'crafting_table' }, | |
| 'bucket': { requires: ['iron_ingot'], station: 'crafting_table' }, | |
| 'shears': { requires: ['iron_ingot'], station: 'crafting_table' }, | |
| 'compass': { requires: ['iron_ingot', 'redstone'], station: 'crafting_table' }, | |
| }, | |
| // Get full dependency chain for an item (recursive) | |
| getFullChain(item) { | |
| const chain = []; | |
| const visited = new Set(); | |
| const recurse = (name) => { | |
| if (visited.has(name)) return; | |
| visited.add(name); | |
| const dep = this.dependencies[name]; | |
| if (dep) { | |
| for (const req of dep.requires) { | |
| recurse(req); | |
| } | |
| chain.push({ item: name, requires: dep.requires, station: dep.station, count: dep.count || 1 }); | |
| } | |
| }; | |
| recurse(item); | |
| return chain; | |
| }, | |
| // Check what the player can make right now | |
| getCraftableNow(inventoryNames) { | |
| const invSet = new Set(inventoryNames); | |
| const craftable = []; | |
| for (const [item, dep] of Object.entries(this.dependencies)) { | |
| if (invSet.has(item)) continue; // already have | |
| const hasAll = dep.requires.every(r => invSet.has(r)); | |
| if (hasAll) { | |
| // Check station | |
| // Station ready if: no station needed, or we have it in inventory, or it's placed nearby | |
| const stationReady = !dep.station || invSet.has(dep.station) || | |
| (dep.station === 'crafting_table' && (invSet.has('crafting_table') || this._hasBlockNearby('crafting_table'))) || | |
| (dep.station === 'furnace' && (invSet.has('furnace') || this._hasBlockNearby('furnace'))); | |
| if (stationReady) craftable.push(item); | |
| } | |
| } | |
| return craftable; | |
| }, | |
| // Get next priority item to work towards (Voyager-style curriculum) | |
| getNextGoal(inventoryNames, milestones) { | |
| const invSet = new Set(inventoryNames); | |
| // Priority order: survival progression | |
| const priorityChain = [ | |
| 'oak_log', 'oak_planks', 'stick', 'crafting_table', | |
| 'wooden_pickaxe', 'cobblestone', 'stone_pickaxe', 'stone_sword', | |
| 'furnace', 'torch', 'coal', 'raw_iron', 'iron_ingot', | |
| 'iron_pickaxe', 'iron_sword', 'iron_chestplate', 'iron_helmet', | |
| 'bucket', 'shield', 'diamond', 'diamond_pickaxe', 'diamond_sword', | |
| ]; | |
| for (const item of priorityChain) { | |
| if (!invSet.has(item)) { | |
| const chain = this.getFullChain(item); | |
| // Find the first missing item in the chain | |
| for (const step of chain) { | |
| if (!invSet.has(step.item)) return step; | |
| } | |
| return { item, requires: this.dependencies[item]?.requires || [], station: this.dependencies[item]?.station || null }; | |
| } | |
| } | |
| return null; // Has everything in priority chain | |
| }, | |
| _hasBlockNearby(blockName) { | |
| try { | |
| if (!bot?.entity) return false; | |
| const found = bot.findBlock({ matching: (b) => b?.name === blockName, maxDistance: 16 }); | |
| return !!found; | |
| } catch { return false; } | |
| }, | |
| // Get human-readable crafting path | |
| getCraftingPath(item) { | |
| const chain = this.getFullChain(item); | |
| return chain.map(s => s.item + (s.station ? ' (needs ' + s.station + ')' : '')).join(' -> '); | |
| } | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // EPISODIC MEMORY β From M2PA | |
| // Stores successful experiences: goal, scene, plan, inventory, biome, time | |
| // Only stores SUCCESSFUL plans (M2PA key insight) | |
| // Consolidation: real-time (replace similar) + periodic (merge top-3) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const episodicMemory = { | |
| episodes: [], // { goal, scene, plan, inventory, biome, time, result, timestamp, tokens } | |
| maxEpisodes: 200, | |
| store(goal, scene, plan, result, success) { | |
| if (!success) return; // M2PA: only store successful experiences | |
| const episode = { | |
| goal: goal.substring(0, 200), | |
| scene: scene.substring(0, 500), | |
| plan: plan.substring(0, 800), | |
| result: result.substring(0, 200), | |
| inventory: this._snapshotInventory(), | |
| biome: this._getBiome(), | |
| time: this._getTime(), | |
| timestamp: Date.now(), | |
| tokens: tokenize(goal + ' ' + scene), | |
| }; | |
| // M2PA consolidation: real-time - replace if very similar exists | |
| const similar = this.findSimilar(episode.tokens, 0.85); | |
| if (similar.length > 0) { | |
| // Replace the most similar one (most recent wins - M2PA strategy) | |
| const idx = this.episodes.indexOf(similar[0]); | |
| if (idx >= 0) { | |
| this.episodes[idx] = episode; | |
| console.log('[Episodic] Replaced similar episode at idx ' + idx); | |
| this._persist(); | |
| return; | |
| } | |
| } | |
| this.episodes.push(episode); | |
| // Trim if over max | |
| if (this.episodes.length > this.maxEpisodes) { | |
| this.episodes = this.episodes.slice(-this.maxEpisodes); | |
| } | |
| this._persist(); | |
| console.log('[Episodic] Stored: ' + goal.substring(0, 60) + ' (' + this.episodes.length + ' total)'); | |
| }, | |
| findSimilar(queryTokens, threshold = 0.3, maxResults = 5) { | |
| if (!queryTokens.length || !this.episodes.length) return []; | |
| const scored = this.episodes.map((ep, idx) => { | |
| const sim = computeSimilarity(queryTokens, ep.tokens); | |
| // Recency boost (M2PA: re-sort by recency after similarity filter) | |
| const ageHours = (Date.now() - ep.timestamp) / 3600000; | |
| const recencyBoost = 1 / (1 + ageHours * 0.1); | |
| return { episode: ep, idx, score: sim * recencyBoost, similarity: sim }; | |
| }).filter(s => s.similarity >= threshold) | |
| .sort((a, b) => b.score - a.score); | |
| return scored.slice(0, maxResults).map(s => s.episode); | |
| }, | |
| getRelevantContext(currentGoal, currentScene, maxResults = 3) { | |
| const queryTokens = tokenize(currentGoal + ' ' + currentScene); | |
| const similar = this.findSimilar(queryTokens, 0.2, maxResults); | |
| if (similar.length === 0) return ''; | |
| return '== PAST SUCCESSFUL EXPERIENCES ==\n' + similar.map((ep, i) => | |
| '[' + (i+1) + '] Goal: ' + ep.goal + '\n Plan: ' + ep.plan.substring(0, 200) + '\n Result: ' + ep.result | |
| ).join('\n'); | |
| }, | |
| periodicConsolidation() { | |
| // M2PA: merge top-3 most similar episodes, keep most recent | |
| if (this.episodes.length < 10) return; | |
| let merged = 0; | |
| for (let i = this.episodes.length - 1; i >= 1 && merged < 3; i--) { | |
| const ep = this.episodes[i]; | |
| const similar = this.findSimilar(ep.tokens, 0.7, 3); | |
| if (similar.length >= 2) { | |
| // Remove older duplicates, keep newest | |
| for (const dup of similar.slice(1)) { | |
| const idx = this.episodes.indexOf(dup); | |
| if (idx >= 0 && idx < i) { | |
| this.episodes.splice(idx, 1); | |
| i--; | |
| merged++; | |
| } | |
| } | |
| } | |
| } | |
| if (merged > 0) { | |
| console.log('[Episodic] Consolidation: merged ' + merged + ' duplicates'); | |
| this._persist(); | |
| } | |
| }, | |
| _snapshotInventory() { | |
| try { | |
| if (!bot?.inventory) return ''; | |
| return bot.inventory.items().map(i => i.name + 'x' + i.count).join(','); | |
| } catch { return ''; } | |
| }, | |
| _getBiome() { try { return bot?.biome?.name || 'unknown'; } catch { return 'unknown'; } }, | |
| _getTime() { try { return Math.round(bot?.time?.timeOfDay ?? 0); } catch { return 0; } }, | |
| _persist() { | |
| try { | |
| writeFileSync(CONFIG.episodicFile, JSON.stringify(this.episodes.slice(-this.maxEpisodes), null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.episodicFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.episodicFile, 'utf-8')); | |
| this.episodes = Array.isArray(data) ? data : []; | |
| console.log('[Episodic] Loaded ' + this.episodes.length + ' episodes'); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // REMEDY SYSTEM β From MineEvolve | |
| // Learns from failures: what went wrong, why, how to fix it | |
| // Dual-track: Skills (positive) + Remedies (negative) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const remedySystem = { | |
| remedies: [], // { trigger, failureType, riskPattern, repairAction, scope, timestamp, tokens } | |
| maxRemedies: 100, | |
| store(trigger, failureType, riskPattern, repairAction, scope) { | |
| const remedy = { | |
| trigger: trigger.substring(0, 150), | |
| failureType: failureType.substring(0, 100), | |
| riskPattern: riskPattern.substring(0, 200), | |
| repairAction: repairAction.substring(0, 300), | |
| scope: scope || 'general', | |
| timestamp: Date.now(), | |
| tokens: tokenize(trigger + ' ' + failureType + ' ' + riskPattern), | |
| }; | |
| // Curator QC: check for conflicts (MineEvolve) | |
| const conflicts = this.remedies.filter(r => | |
| computeSimilarity(remedy.tokens, r.tokens) > 0.8 && r.repairAction !== remedy.repairAction | |
| ); | |
| if (conflicts.length > 0) { | |
| // Keep the more recent one (more context-aware) | |
| const idx = this.remedies.indexOf(conflicts[0]); | |
| if (idx >= 0) this.remedies[idx] = remedy; | |
| } else { | |
| this.remedies.push(remedy); | |
| } | |
| if (this.remedies.length > this.maxRemedies) { | |
| this.remedies = this.remedies.slice(-this.maxRemedies); | |
| } | |
| this._persist(); | |
| console.log('[Remedy] Stored: ' + failureType.substring(0, 50)); | |
| }, | |
| getRelevantRemedies(context, maxResults = 3) { | |
| const ctxTokens = tokenize(context); | |
| if (!ctxTokens.length) return []; | |
| const scored = this.remedies.map(r => ({ | |
| remedy: r, | |
| score: computeSimilarity(ctxTokens, r.tokens), | |
| })).filter(s => s.score > 0.2) | |
| .sort((a, b) => b.score - a.score); | |
| return scored.slice(0, maxResults).map(s => s.remedy); | |
| }, | |
| getRemedyContext(currentSituation) { | |
| const relevant = this.getRelevantRemedies(currentSituation, 3); | |
| if (relevant.length === 0) return ''; | |
| return '== KNOWN FAILURE PATTERNS (AVOID!) ==\n' + relevant.map((r, i) => | |
| '[' + (i+1) + '] When: ' + r.trigger + '\n Problem: ' + r.riskPattern + '\n Fix: ' + r.repairAction | |
| ).join('\n'); | |
| }, | |
| _persist() { | |
| try { | |
| writeFileSync(CONFIG.remedyFile, JSON.stringify(this.remedies, null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.remedyFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.remedyFile, 'utf-8')); | |
| this.remedies = Array.isArray(data) ? data : []; | |
| console.log('[Remedy] Loaded ' + this.remedies.length + ' remedies'); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SPATIAL MEMORY β From Mr.Steve (What-Where-When) | |
| // Place Event Memory: stores events tagged with coordinates + timestamps | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const spatialMemory = { | |
| places: [], // { what, x, y, z, when, details } | |
| maxPlaces: 300, | |
| record(what, x, y, z, details) { | |
| const entry = { | |
| what: what.substring(0, 100), | |
| x: Math.round(x), y: Math.round(y), z: Math.round(z), | |
| when: Date.now(), | |
| details: (details || '').substring(0, 200), | |
| }; | |
| this.places.push(entry); | |
| if (this.places.length > this.maxPlaces) { | |
| this.places = this.places.slice(-this.maxPlaces); | |
| } | |
| this._persist(); | |
| }, | |
| findNearest(what, fromX, fromZ, maxDist = 200) { | |
| const matches = this.places.filter(p => { | |
| if (!p.what.includes(what)) return false; | |
| const dist = Math.sqrt((p.x - fromX)**2 + (p.z - fromZ)**2); | |
| return dist <= maxDist; | |
| }).map(p => ({ | |
| ...p, | |
| dist: Math.round(Math.sqrt((p.x - fromX)**2 + (p.z - fromZ)**2)) | |
| })).sort((a, b) => a.dist - b.dist); | |
| return matches.slice(0, 5); | |
| }, | |
| getRecentEvents(maxResults = 10) { | |
| return this.places.slice(-maxResults); | |
| }, | |
| getResourceLocations(fromX, fromZ) { | |
| const resourceTypes = ['iron_ore', 'coal_ore', 'gold_ore', 'diamond_ore', 'oak_log', 'birch_log', 'lapis_ore', 'redstone_ore', 'emerald_ore', 'copper_ore']; | |
| const locations = {}; | |
| for (const type of resourceTypes) { | |
| const nearest = this.findNearest(type, fromX, fromZ, 500); | |
| if (nearest.length > 0) { | |
| locations[type] = nearest[0]; | |
| } | |
| } | |
| return locations; | |
| }, | |
| _persist() { | |
| try { | |
| writeFileSync(CONFIG.spatialFile, JSON.stringify(this.places.slice(-this.maxPlaces), null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.spatialFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.spatialFile, 'utf-8')); | |
| this.places = Array.isArray(data) ? data : []; | |
| console.log('[Spatial] Loaded ' + this.places.length + ' places'); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CAUSAL TRACKER β From ADAM | |
| // Tracks actionβresult mappings to learn causal relationships | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const causalTracker = { | |
| causals: [], // { action, preState, postState, result, timestamp } | |
| maxCausals: 200, | |
| record(action, preState, postState, result) { | |
| this.causals.push({ | |
| action: action.substring(0, 150), | |
| preState: preState.substring(0, 200), | |
| postState: postState.substring(0, 200), | |
| result: result.substring(0, 100), | |
| timestamp: Date.now(), | |
| }); | |
| if (this.causals.length > this.maxCausals) { | |
| this.causals = this.causals.slice(-this.maxCausals); | |
| } | |
| this._persist(); | |
| }, | |
| // What actions lead to getting X? | |
| getActionsForGoal(goal) { | |
| const goalTokens = tokenize(goal); | |
| return this.causals.filter(c => { | |
| const postTokens = tokenize(c.postState + ' ' + c.result); | |
| return computeSimilarity(goalTokens, postTokens) > 0.3; | |
| }).slice(-5); | |
| }, | |
| _persist() { | |
| try { | |
| writeFileSync(CONFIG.causalFile, JSON.stringify(this.causals.slice(-this.maxCausals), null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.causalFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.causalFile, 'utf-8')); | |
| this.causals = Array.isArray(data) ? data : []; | |
| console.log('[Causal] Loaded ' + this.causals.length + ' records'); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // PERSISTENT MEMORY β Enhanced with Mr.Steve spatial awareness | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const memory = { | |
| home: null, | |
| shelterPos: null, | |
| deathPoints: [], | |
| resourceLocations: {}, | |
| chatHistory: [], | |
| safeSpots: [], | |
| totalPlayTime: 0, | |
| sessionStart: 0, | |
| deaths: 0, | |
| kills: 0, | |
| blocksMined: 0, | |
| itemsCrafted: 0, | |
| dangerZones: [], | |
| craftingTablesPlaced: [], | |
| // Current goal tracking (Voyager curriculum) | |
| currentGoal: null, | |
| completedGoals: [], | |
| failedGoals: [], | |
| goalRetryCount: {}, | |
| save() { | |
| try { | |
| writeFileSync(CONFIG.memoryFile, JSON.stringify({ | |
| home: this.home, shelterPos: this.shelterPos, | |
| deathPoints: this.deathPoints.slice(-20), | |
| resourceLocations: this.resourceLocations, | |
| chatHistory: this.chatHistory.slice(-50), | |
| safeSpots: this.safeSpots.slice(-10), | |
| totalPlayTime: this.totalPlayTime, | |
| deaths: this.deaths, kills: this.kills, | |
| blocksMined: this.blocksMined, itemsCrafted: this.itemsCrafted, | |
| dangerZones: (this.dangerZones || []).slice(-20), | |
| craftingTablesPlaced: (this.craftingTablesPlaced || []).slice(-10), | |
| currentGoal: this.currentGoal, | |
| completedGoals: this.completedGoals.slice(-50), | |
| failedGoals: this.failedGoals.slice(-30), | |
| goalRetryCount: this.goalRetryCount, | |
| }, null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.memoryFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.memoryFile, 'utf-8')); | |
| Object.assign(this, data); | |
| this.sessionStart = Date.now(); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SKILL LIBRARY β Voyager-style with Odyssey hierarchy | |
| // Primitive + Compositional skills, better retrieval (TF-IDF-lite) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const skillLibrary = { | |
| skills: {}, | |
| save(name, code, description, type = 'primitive') { | |
| // Curator QC (MineEvolve): validate before saving | |
| if (!code || code.length < 10) return; | |
| if (!description || description.length < 5) return; | |
| // Version management (Voyager: V2, V3...) | |
| if (this.skills[name] && this.skills[name].code !== code) { | |
| const oldName = name + 'V' + (2 + (this.skills[name].version || 1)); | |
| this.skills[oldName] = { ...this.skills[oldName] || this.skills[name] }; | |
| } | |
| this.skills[name] = { | |
| code, | |
| description, | |
| type, // 'primitive' or 'compositional' | |
| usageCount: 0, | |
| successCount: 0, | |
| lastUsed: Date.now(), | |
| created: Date.now(), | |
| version: (this.skills[name]?.version || 1) + 1, | |
| tokens: tokenize(description + ' ' + name), | |
| }; | |
| this._persist(); | |
| console.log('[Skill] Saved: ' + name + ' (' + type + ')'); | |
| }, | |
| get(name) { | |
| return this.skills[name] || null; | |
| }, | |
| recordUsage(name, success) { | |
| if (!this.skills[name]) return; | |
| this.skills[name].usageCount++; | |
| if (success) this.skills[name].successCount++; | |
| this.skills[name].lastUsed = Date.now(); | |
| this._persist(); | |
| }, | |
| getSkillList() { | |
| return Object.entries(this.skills) | |
| .filter(([name]) => !name.startsWith('_')) | |
| .map(([name, s]) => | |
| name + ' (' + s.type + '): ' + s.description + ' [' + s.successCount + '/' + s.usageCount + ']' | |
| ).join('\n'); | |
| }, | |
| getRelevantSkills(context, maxResults = 5) { | |
| const ctxTokens = tokenize(context); | |
| if (!ctxTokens.length) return []; | |
| const scored = Object.entries(this.skills) | |
| .filter(([name]) => !name.startsWith('_')) | |
| .map(([name, skill]) => { | |
| const sim = computeSimilarity(ctxTokens, skill.tokens || tokenize(skill.description + ' ' + name)); | |
| const successRate = skill.usageCount > 0 ? skill.successCount / skill.usageCount : 0.5; | |
| return { name, skill, score: sim * (0.3 + 0.7 * successRate) }; | |
| }).sort((a, b) => b.score - a.score); | |
| return scored.slice(0, maxResults); | |
| }, | |
| getSkillCodeForContext(context, maxResults = 3) { | |
| const relevant = this.getRelevantSkills(context, maxResults); | |
| if (relevant.length === 0) return ''; | |
| return '== RELEVANT SKILLS (reuse or adapt) ==\n' + relevant.map(({name, skill}) => | |
| '// ' + name + ': ' + skill.description + '\n' + skill.code.substring(0, 500) | |
| ).join('\n\n'); | |
| }, | |
| _persist() { | |
| try { | |
| const toSave = {}; | |
| for (const [k, v] of Object.entries(this.skills)) { | |
| if (k.startsWith('_')) continue; | |
| toSave[k] = v; | |
| } | |
| writeFileSync(CONFIG.skillFile, JSON.stringify(toSave, null, 2)); | |
| } catch {} | |
| }, | |
| load() { | |
| try { | |
| if (existsSync(CONFIG.skillFile)) { | |
| const data = JSON.parse(readFileSync(CONFIG.skillFile, 'utf-8')); | |
| this.skills = data; | |
| console.log('[Skill] Loaded ' + Object.keys(this.skills).length + ' skills'); | |
| } | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // PRE-SEEDED SKILLS β Proven code patterns (Odyssey: primitive + compositional) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function seedSkills() { | |
| if (skillLibrary.skills._seeded_version === 'v32') return; | |
| // === PRIMITIVE SKILLS (atomic actions) === | |
| skillLibrary.save('chopTree', `const logTypes = ['oak_log','birch_log','spruce_log','dark_oak_log','acacia_log','jungle_log']; | |
| let treeBlock = null; | |
| for (const log of logTypes) { | |
| treeBlock = bot.findBlock({ matching: b => b?.name === log, maxDistance: 48 }); | |
| if (treeBlock) break; | |
| } | |
| if (!treeBlock) { chat('no hay arboles'); return 'no trees'; } | |
| const moves = new Movements(bot); moves.canDig = false; | |
| bot.pathfinder.setMovements(moves); | |
| await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2)); | |
| await sleep(500); | |
| bot.lookAt(treeBlock.position.offset(0.5, 0.5, 0.5), true); | |
| await sleep(300); | |
| if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); memory.blocksMined++; } | |
| for (let dy = 1; dy <= 5; dy++) { | |
| const above = bot.blockAt(treeBlock.position.offset(0, dy, 0)); | |
| if (above && logTypes.some(l => above.name === l) && bot.canDigBlock(above)) { | |
| await bot.dig(above); memory.blocksMined++; | |
| } else break; | |
| } | |
| spatialMemory.record('oak_log', treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 'tree chopped'); | |
| chat('madera lista');`, 'Find and chop a tree, collect all logs above', 'primitive'); | |
| skillLibrary.save('craftItem2x2', `// craftItem2x2(itemName, count) - craft without crafting table | |
| const itemName = arguments[0] || 'oak_planks'; | |
| const count = arguments[1] || 1; | |
| const item = bot.registry.itemsByName[itemName]; | |
| if (!item) { chat('no conozco ' + itemName); return; } | |
| const recipes = bot.recipesFor(item.id, null, count, null); | |
| if (recipes.length === 0) { chat('no hay receta para ' + itemName); return; } | |
| const missing = bot.inventory.items().filter(i => !recipes[0].delta[i.id]); | |
| await bot.craft(recipes[0], count, null); | |
| memory.itemsCrafted += count; | |
| chat('craftee ' + count + ' ' + itemName);`, 'Craft an item using 2x2 inventory (no table needed)', 'primitive'); | |
| skillLibrary.save('placeBlock', `let item = bot.inventory.items().find(i => i.name === 'crafting_table'); | |
| if (!item) { chat('no tengo ese item'); return; } | |
| await bot.equip(item, 'hand'); | |
| await sleep(400); | |
| let groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0)); | |
| if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(1, -1, 0)); | |
| if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(-1, -1, 0)); | |
| if (groundBelow && groundBelow.name !== 'air') { | |
| let face = new Vec3(0, 1, 0); | |
| await bot.placeBlock(groundBelow, face); | |
| await sleep(500); | |
| chat('bloque puesto'); | |
| } else { | |
| chat('no encontre donde ponerlo'); | |
| }`, 'Place a block from inventory on the ground (correct referenceBlock + faceVector)', 'primitive'); | |
| skillLibrary.save('eatFood', `const food = bot.inventory.items().find(i => isFoodItem(i.name)); | |
| if (!food) { chat('no tengo comida'); return; } | |
| await bot.equip(food, 'hand'); | |
| await sleep(300); | |
| await bot.activateItem(); | |
| await sleep(300); | |
| chat('nom');`, 'Eat food from inventory', 'primitive'); | |
| skillLibrary.save('fleeDanger', `let threat = null, minDist = 16; | |
| for (const [id, entity] of Object.entries(bot.entities)) { | |
| if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue; | |
| let dist = bot.entity.position.distanceTo(entity.position); | |
| if (dist < minDist) { threat = entity; minDist = dist; } | |
| } | |
| if (!threat) { chat('no hay peligro'); return; } | |
| let dx = bot.entity.position.x - threat.position.x; | |
| let dz = bot.entity.position.z - threat.position.z; | |
| let len = Math.sqrt(dx*dx + dz*dz) || 1; | |
| dx /= len; dz /= len; | |
| let yaw = Math.atan2(-dx, dz); | |
| await bot.look(yaw, 0, true); | |
| bot.setControlState('forward', true); | |
| bot.setControlState('sprint', true); | |
| bot.setControlState('jump', true); | |
| await sleep(3000); | |
| clearAllControls(); | |
| spatialMemory.record('danger', Math.round(threat.position.x), Math.round(threat.position.y), Math.round(threat.position.z), threat.name); | |
| memory.dangerZones.push({x: Math.floor(threat.position.x), z: Math.floor(threat.position.z), time: Date.now()}); | |
| memory.save(); | |
| chat('me largo de aqui');`, 'Flee from nearest hostile mob in opposite direction', 'primitive'); | |
| skillLibrary.save('huntAnimal', `let animals = []; | |
| for (const [id, entity] of Object.entries(bot.entities)) { | |
| if (entity === bot.entity || !entity.position) continue; | |
| if (entity.name === 'cow' || entity.name === 'pig' || entity.name === 'sheep' || entity.name === 'chicken' || entity.name === 'rabbit') { | |
| let dist = bot.entity.position.distanceTo(entity.position); | |
| if (dist < 20) animals.push({ entity, dist, name: entity.name }); | |
| } | |
| } | |
| if (animals.length === 0) { chat('no hay animales cerca'); return; } | |
| animals.sort((a, b) => a.dist - b.dist); | |
| let target = animals[0]; | |
| let moves = new Movements(bot); moves.canDig = false; | |
| bot.pathfinder.setMovements(moves); | |
| await bot.pathfinder.setGoal(new goals.GoalNear(target.entity.position.x, target.entity.position.y, target.entity.position.z, 2)); | |
| await sleep(500); | |
| let sword = bot.inventory.items().find(i => i.name.includes('sword') || i.name.includes('axe')); | |
| if (sword) { await bot.equip(sword, 'hand'); await sleep(300); } | |
| for (let i = 0; i < 6; i++) { | |
| bot.attack(target.entity); | |
| await sleep(500); | |
| if (!target.entity || target.entity.isValid === false || target.entity.health <= 0) break; | |
| } | |
| memory.kills++; | |
| chat('cazando ' + target.name);`, 'Find and kill nearest animal for food', 'primitive'); | |
| skillLibrary.save('digHole', `// Emergency shelter: dig a hole in the ground | |
| const pos = bot.entity.position; | |
| const bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); | |
| // Dig down 3 blocks | |
| for (let dy = 0; dy < 3; dy++) { | |
| const block = bot.blockAt(new Vec3(bx, by - 1 - dy, bz)); | |
| if (block && bot.canDigBlock(block)) { | |
| await bot.dig(block); | |
| memory.blocksMined++; | |
| await sleep(200); | |
| } | |
| } | |
| // Jump in | |
| bot.setControlState('forward', true); | |
| await sleep(200); | |
| bot.setControlState('jump', true); | |
| await sleep(500); | |
| clearAllControls(); | |
| // Cover top | |
| let dirt = bot.inventory.items().find(i => i.name === 'dirt' || i.name === 'cobblestone'); | |
| if (dirt) { | |
| await bot.equip(dirt, 'hand'); await sleep(400); | |
| const topBlock = bot.blockAt(new Vec3(bx, by + 1, bz)); | |
| if (topBlock && topBlock.name === 'air') { | |
| const ref = bot.blockAt(new Vec3(bx, by, bz)); | |
| if (ref) { try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} } | |
| } | |
| } | |
| memory.shelterPos = { x: bx, y: by - 3, z: bz }; | |
| memory.save(); | |
| chat('hoyo listo, sobrevivo la noche');`, 'Emergency shelter: dig 3-deep hole and cover top', 'primitive'); | |
| // === COMPOSITIONAL SKILLS (multi-step, combine primitives) === | |
| skillLibrary.save('craftPickaxe', `const log = bot.inventory.items().find(i => i.name.includes('_log')); | |
| if (!log) { chat('necesito madera'); return; } | |
| let planksItem = bot.registry.itemsByName['oak_planks']; | |
| if (!planksItem) planksItem = bot.registry.itemsByName[log.name.replace('_log','_planks')]; | |
| if (!planksItem) { chat('no se que planks hacer'); return; } | |
| let recipe = bot.recipesFor(planksItem.id, null, 1, null)[0]; | |
| if (recipe) { await bot.craft(recipe, 4, null); memory.itemsCrafted += 4; } | |
| let stickItem = bot.registry.itemsByName['stick']; | |
| let stickRecipe = bot.recipesFor(stickItem.id, null, 1, null)[0]; | |
| if (stickRecipe) { await bot.craft(stickRecipe, 2, null); memory.itemsCrafted += 2; } | |
| let ctItem = bot.inventory.items().find(i => i.name === 'crafting_table'); | |
| if (!ctItem) { | |
| let ctRecipe = bot.recipesFor(bot.registry.itemsByName['crafting_table'].id, null, 1, null)[0]; | |
| if (ctRecipe) { await bot.craft(ctRecipe, 1, null); memory.itemsCrafted++; ctItem = bot.inventory.items().find(i => i.name === 'crafting_table'); } | |
| } | |
| if (ctItem) { | |
| let existingCT = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 16 }); | |
| if (!existingCT) { | |
| await bot.equip(ctItem, 'hand'); await sleep(400); | |
| let refBlock = bot.blockAt(bot.entity.position.offset(0, -1, 0)); | |
| if (refBlock && refBlock.name !== 'air') { | |
| try { await bot.placeBlock(refBlock, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} | |
| } else { | |
| refBlock = bot.blockAt(bot.entity.position.offset(1, -1, 0)); | |
| if (refBlock) { try { await bot.placeBlock(refBlock, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} } | |
| } | |
| } | |
| } | |
| let ct = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 8 }); | |
| let pickItem = bot.registry.itemsByName['wooden_pickaxe']; | |
| let recipes = bot.recipesFor(pickItem.id, null, 1, ct); | |
| if (recipes.length > 0) { await bot.craft(recipes[0], 1, ct); memory.itemsCrafted++; chat('pico listo wey'); }`, 'Craft a wooden pickaxe from logs (planks + sticks + crafting_table + pickaxe)', 'compositional'); | |
| skillLibrary.save('progressToIron', `// Full progression: stone tools -> furnace -> smelt iron | |
| // 1. Need stone pickaxe first | |
| let cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); | |
| if (!cobble || cobble.count < 3) { | |
| // Mine stone | |
| const stoneBlock = bot.findBlock({ matching: b => b?.name === 'stone' || b?.name === 'deepslate', maxDistance: 16 }); | |
| if (stoneBlock) { | |
| const pick = bot.inventory.items().find(i => i.name.includes('pickaxe')); | |
| if (pick) { await bot.equip(pick, 'hand'); await sleep(300); } | |
| const moves = new Movements(bot); moves.canDig = true; | |
| bot.pathfinder.setMovements(moves); | |
| await bot.pathfinder.setGoal(new goals.GoalNear(stoneBlock.position.x, stoneBlock.position.y, stoneBlock.position.z, 2)); | |
| await sleep(500); | |
| if (bot.canDigBlock(stoneBlock)) { await bot.dig(stoneBlock); memory.blocksMined++; } | |
| } | |
| } | |
| // 2. Craft stone pickaxe | |
| cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); | |
| if (cobble && cobble.count >= 3) { | |
| const ct = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 16 }); | |
| if (ct) { | |
| const spItem = bot.registry.itemsByName['stone_pickaxe']; | |
| const recipes = bot.recipesFor(spItem.id, null, 1, ct); | |
| if (recipes.length > 0) { await bot.craft(recipes[0], 1, ct); memory.itemsCrafted++; chat('pico de piedra listo'); } | |
| } | |
| } | |
| // 3. Craft furnace | |
| cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); | |
| if (cobble && cobble.count >= 8) { | |
| const fRecipe = bot.recipesFor(bot.registry.itemsByName['furnace'].id, null, 1, null)[0]; | |
| if (fRecipe) { await bot.craft(fRecipe, 1, null); memory.itemsCrafted++; } | |
| } | |
| // 4. Place furnace | |
| let furnaceItem = bot.inventory.items().find(i => i.name === 'furnace'); | |
| if (furnaceItem) { | |
| let furnaceBlock = bot.findBlock({ matching: b => b?.name === 'furnace', maxDistance: 16 }); | |
| if (!furnaceBlock) { | |
| await bot.equip(furnaceItem, 'hand'); await sleep(400); | |
| const ground = bot.blockAt(bot.entity.position.offset(1, -1, 0)); | |
| if (ground) { try { await bot.placeBlock(ground, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} } | |
| } | |
| } | |
| chat('progreso hacia hierro');`, 'Progress from stone tools to furnace setup for iron smelting', 'compositional'); | |
| skillLibrary.save('buildShelter', `const pos = bot.entity.position; | |
| let dirtBlock = bot.inventory.items().find(i => i.name === 'dirt' || i.name.includes('dirt')); | |
| let cobbleBlock = bot.inventory.items().find(i => i.name === 'cobblestone'); | |
| let buildItem = cobbleBlock || dirtBlock; | |
| if (!buildItem) { | |
| chat('necesito bloques, voy a minar'); | |
| let stoneBelow = bot.blockAt(pos.offset(0, -1, 0)); | |
| if (stoneBelow && bot.canDigBlock(stoneBelow)) { | |
| let pick = bot.inventory.items().find(i => i.name.includes('pickaxe')); | |
| if (pick) { await bot.equip(pick, 'hand'); await sleep(300); } | |
| await bot.dig(stoneBelow); memory.blocksMined++; | |
| buildItem = bot.inventory.items().find(i => i.name === 'cobblestone' || i.name === 'dirt'); | |
| } | |
| } | |
| if (!buildItem) { chat('no tengo nada chale'); return; } | |
| await bot.equip(buildItem, 'hand'); await sleep(300); | |
| let bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); | |
| for (let face of [[1,0],[-1,0],[0,1],[0,-1]]) { | |
| let wallX = bx + face[0], wallZ = bz + face[1]; | |
| let ref = bot.blockAt(new Vec3(wallX, by - 1, wallZ)); | |
| if (ref && ref.name !== 'air') { | |
| try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} | |
| ref = bot.blockAt(new Vec3(wallX, by, wallZ)); | |
| if (ref && ref.name !== 'air') { | |
| try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} | |
| } | |
| } | |
| } | |
| memory.shelterPos = { x: bx, y: by, z: bz }; | |
| spatialMemory.record('shelter', bx, by, bz, 'basic shelter built'); | |
| memory.save(); | |
| chat('refugio listo');`, 'Build a basic shelter (4 walls, 2 high) using available blocks', 'compositional'); | |
| skillLibrary.skills._seeded_version = 'v34'; | |
| skillLibrary._persist(); | |
| console.log('[Skill] Pre-seeded v34 skills (primitive + compositional)'); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CURRICULUM SYSTEM β From Voyager | |
| // Automatic goal generation based on progress, inventory, and world state | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const curriculum = { | |
| // Voyager-style warm-up: gradually reveal more info | |
| getInfoLevel() { | |
| const completed = memory.completedGoals.length; | |
| return { | |
| showAllResources: completed >= 5, | |
| showDetailedInventory: completed >= 3, | |
| showCraftableItems: completed >= 5, | |
| showSpatialMemory: completed >= 7, | |
| }; | |
| }, | |
| // Generate next goal based on current state | |
| proposeGoal(observation) { | |
| const pos = bot?.entity?.position; | |
| const items = bot?.inventory?.items() || []; | |
| const itemNames = items.map(i => i.name); | |
| const isNight = (bot?.time?.timeOfDay ?? 0) >= 13000 && (bot?.time?.timeOfDay ?? 0) < 23000; | |
| const hp = bot?.health ?? 20; | |
| const food = bot?.food ?? 20; | |
| // PRIORITY 1: Emergency survival | |
| if (hp < 10 && isHostileNearby()) return { goal: 'flee from danger', priority: 'critical', reason: 'HP bajo y mobs hostiles cerca' }; | |
| if (food < 8) return { goal: 'find and eat food', priority: 'critical', reason: 'comida baja (' + Math.round(food) + '/20)' }; | |
| if (isNight && !memory.shelterPos) return { goal: 'dig emergency hole shelter', priority: 'critical', reason: 'es de noche sin refugio' }; | |
| // PRIORITY 2: Progression (Knowledge Graph driven) | |
| const nextGoal = KNOWLEDGE_GRAPH.getNextGoal(itemNames, benchmark.milestones); | |
| if (nextGoal) { | |
| const goalStr = 'obtain ' + nextGoal.item; | |
| // Check if we've failed this too many times | |
| const retryCount = memory.goalRetryCount[goalStr] || 0; | |
| if (retryCount >= 3) { | |
| // Try a different approach or simpler goal | |
| return { goal: 'explore area for resources', priority: 'high', reason: 'stuck on ' + goalStr + ', need to explore' }; | |
| } | |
| return { goal: goalStr, priority: 'high', reason: 'next in tech tree: ' + nextGoal.item + ' (needs ' + nextGoal.requires.join(', ') + (nextGoal.station ? ' at ' + nextGoal.station : '') + ')' }; | |
| } | |
| // PRIORITY 3: Improvement (if we have basics) | |
| if (!memory.shelterPos && itemNames.some(n => n.includes('cobblestone') || n.includes('dirt'))) { | |
| return { goal: 'build proper shelter', priority: 'medium', reason: 'no shelter yet' }; | |
| } | |
| if (itemNames.some(n => n.includes('raw_iron')) && itemNames.some(n => n === 'coal' || n === 'charcoal')) { | |
| return { goal: 'smelt iron in furnace', priority: 'high', reason: 'have raw iron and coal' }; | |
| } | |
| if (!itemNames.some(n => n.includes('cooked_')) && memory.kills > 0) { | |
| return { goal: 'cook food in furnace', priority: 'medium', reason: 'have raw food and need cooked food' }; | |
| } | |
| // PRIORITY 4: Explore and discover (Voyager's novelty search) | |
| if (!itemNames.some(n => n.includes('_log'))) return { goal: 'chop tree for wood', priority: 'high', reason: 'no wood in inventory' }; | |
| if (!itemNames.some(n => n.includes('_planks'))) return { goal: 'craft planks from logs', priority: 'high', reason: 'no planks' }; | |
| if (!itemNames.some(n => n === 'stick')) return { goal: 'craft sticks from planks', priority: 'high', reason: 'no sticks' }; | |
| if (!itemNames.some(n => n === 'crafting_table')) return { goal: 'craft and place crafting table', priority: 'high', reason: 'no crafting table' }; | |
| if (!itemNames.some(n => n.includes('pickaxe'))) return { goal: 'craft a pickaxe', priority: 'high', reason: 'no pickaxe' }; | |
| if (!itemNames.some(n => n === 'cobblestone') && !itemNames.some(n => n === 'stone')) return { goal: 'mine stone with pickaxe', priority: 'high', reason: 'no stone' }; | |
| return { goal: 'explore nearby area for resources', priority: 'low', reason: 'have basic tools, look for more resources' }; | |
| }, | |
| markCompleted(goal) { | |
| if (!memory.completedGoals.includes(goal)) { | |
| memory.completedGoals.push(goal); | |
| } | |
| memory.currentGoal = null; | |
| if (memory.goalRetryCount[goal]) delete memory.goalRetryCount[goal]; | |
| memory.save(); | |
| }, | |
| markFailed(goal, reason) { | |
| if (!memory.failedGoals.includes(goal)) { | |
| memory.failedGoals.push(goal); | |
| } | |
| memory.goalRetryCount[goal] = (memory.goalRetryCount[goal] || 0) + 1; | |
| // Store as remedy (MineEvolve) | |
| remedySystem.store( | |
| goal, | |
| 'goal_failed', | |
| reason || 'unknown failure', | |
| 'try different approach or gather more resources first', | |
| 'goal' | |
| ); | |
| memory.save(); | |
| }, | |
| }; | |
| function isHostileNearby() { | |
| if (!bot?.entity) return false; | |
| try { | |
| for (const [id, entity] of Object.entries(bot.entities)) { | |
| if (entity === bot.entity || !entity.position) continue; | |
| if (isHostileMob(entity) && bot.entity.position.distanceTo(entity.position) < 16) return true; | |
| } | |
| } catch {} | |
| return false; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CRITIC / SELF-VERIFICATION β From Voyager | |
| // Verifies if a goal was achieved by checking world state | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const critic = { | |
| verify(goal, preState, postState) { | |
| if (!goal) return { success: true, critique: '' }; | |
| const checkItem = goal.match(/obtain\s+(\w+)/); | |
| if (checkItem) { | |
| const targetItem = checkItem[1]; | |
| const hadIt = preState.includes(targetItem) || preState.includes(targetItem.replace('_', ' ')); | |
| const hasIt = postState.includes(targetItem) || postState.includes(targetItem.replace('_', ' ')); | |
| if (hasIt && !hadIt) return { success: true, critique: 'Successfully obtained ' + targetItem }; | |
| if (hasIt && hadIt) return { success: true, critique: 'Already had ' + targetItem }; | |
| return { success: false, critique: 'Failed to obtain ' + targetItem + '. Check if you have the required materials and tools.' }; | |
| } | |
| if (goal.includes('shelter') || goal.includes('hole')) { | |
| if (memory.shelterPos) return { success: true, critique: 'Shelter is set up' }; | |
| return { success: false, critique: 'No shelter was created. Place blocks around you or dig a hole.' }; | |
| } | |
| if (goal.includes('food') || goal.includes('eat')) { | |
| const foodNow = bot?.food ?? 0; | |
| if (foodNow > 12) return { success: true, critique: 'Food level restored to ' + Math.round(foodNow) }; | |
| return { success: false, critique: 'Still hungry (food=' + Math.round(foodNow) + '). Find and eat food.' }; | |
| } | |
| if (goal.includes('flee') || goal.includes('danger')) { | |
| if (!isHostileNearby()) return { success: true, critique: 'Safely away from danger' }; | |
| return { success: false, critique: 'Still near hostile mobs. Keep running!' }; | |
| } | |
| if (goal.includes('explore')) { | |
| // Exploration is always a "success" if we moved | |
| return { success: true, critique: 'Explored the area' }; | |
| } | |
| // Default: check if code ran without error | |
| return { success: true, critique: '' }; | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BENCHMARK SYSTEM β Enhanced with MCU-style tasks + MineNPC milestones | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const benchmark = { | |
| startTime: Date.now(), | |
| sessionDeaths: 0, | |
| longestSurvivalMs: 0, | |
| _lastDeathTime: null, | |
| _currentSurvivalStart: Date.now(), | |
| milestones: { | |
| gotWood: false, | |
| craftedPlanks: false, | |
| craftedSticks: false, | |
| craftedCraftingTable: false, | |
| craftedWoodenPickaxe: false, | |
| minedStone: false, | |
| craftedStonePickaxe: false, | |
| craftedFurnace: false, | |
| gotIron: false, | |
| craftedIronPickaxe: false, | |
| craftedIronSword: false, | |
| builtShelter: false, | |
| killedMob: false, | |
| cookedFood: false, | |
| gotDiamond: false, | |
| craftedDiamondPickaxe: false, | |
| smeltedIron: false, | |
| craftedShield: false, | |
| craftedBow: false, | |
| usedBed: false, | |
| }, | |
| checkMilestones() { | |
| const items = bot?.inventory?.items() || []; | |
| const names = items.map(i => i.name); | |
| if (names.some(n => n.includes('_log'))) this.milestones.gotWood = true; | |
| if (names.some(n => n.includes('_planks'))) this.milestones.craftedPlanks = true; | |
| if (names.some(n => n === 'stick')) this.milestones.craftedSticks = true; | |
| if (names.some(n => n === 'crafting_table')) this.milestones.craftedCraftingTable = true; | |
| if (names.some(n => n === 'wooden_pickaxe')) this.milestones.craftedWoodenPickaxe = true; | |
| if (memory.blocksMined > 5) this.milestones.minedStone = true; | |
| if (names.some(n => n === 'stone_pickaxe')) this.milestones.craftedStonePickaxe = true; | |
| if (names.some(n => n === 'furnace')) this.milestones.craftedFurnace = true; | |
| if (names.some(n => n.includes('iron_ingot') || n.includes('raw_iron'))) this.milestones.gotIron = true; | |
| if (names.some(n => n === 'iron_pickaxe')) this.milestones.craftedIronPickaxe = true; | |
| if (names.some(n => n === 'iron_sword')) this.milestones.craftedIronSword = true; | |
| if (memory.shelterPos) this.milestones.builtShelter = true; | |
| if (memory.kills > 0) this.milestones.killedMob = true; | |
| if (names.some(n => n.includes('cooked_'))) this.milestones.cookedFood = true; | |
| if (names.some(n => n.includes('diamond'))) this.milestones.gotDiamond = true; | |
| if (names.some(n => n === 'diamond_pickaxe')) this.milestones.craftedDiamondPickaxe = true; | |
| if (names.some(n => n === 'iron_ingot')) this.milestones.smeltedIron = true; | |
| if (names.some(n => n === 'shield')) this.milestones.craftedShield = true; | |
| if (names.some(n => n === 'bow')) this.milestones.craftedBow = true; | |
| }, | |
| getSurvivalTime() { | |
| if (this._lastDeathTime) return this._lastDeathTime - this._currentSurvivalStart; | |
| return Date.now() - this._currentSurvivalStart; | |
| }, | |
| recordDeath() { | |
| const survivalMs = this.getSurvivalTime(); | |
| if (survivalMs > this.longestSurvivalMs) this.longestSurvivalMs = survivalMs; | |
| this._lastDeathTime = Date.now(); | |
| this.sessionDeaths++; | |
| this._currentSurvivalStart = Date.now(); | |
| }, | |
| getReport() { | |
| this.checkMilestones(); | |
| const achieved = Object.entries(this.milestones).filter(([,v]) => v).map(([k]) => k); | |
| const pending = Object.entries(this.milestones).filter(([,v]) => !v).map(([k]) => k); | |
| const survMin = Math.floor(this.getSurvivalTime() / 60000); | |
| const bestMin = Math.floor(this.longestSurvivalMs / 60000); | |
| return 'Survival: ' + survMin + 'min (best: ' + bestMin + 'min) | Deaths: ' + this.sessionDeaths + ' | Milestones: ' + achieved.length + '/20 [' + achieved.join(',') + '] | Pending: [' + pending.join(',') + ']'; | |
| }, | |
| save() { | |
| try { | |
| writeFileSync(CONFIG.benchmarkFile, JSON.stringify({ | |
| startTime: this.startTime, | |
| sessionDeaths: this.sessionDeaths, | |
| longestSurvivalMs: this.longestSurvivalMs, | |
| milestones: this.milestones, | |
| }, null, 2)); | |
| } catch {} | |
| }, | |
| }; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // STATE | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const state = { | |
| activity: 'idle', | |
| emotion: 'curious', | |
| connectedSince: '', | |
| _consecutiveKicks: 0, | |
| _lastChatTime: 0, | |
| _chatCooldown: 12000, | |
| _lastGoodPos: null, | |
| _positionReady: false, | |
| _recentSentMsgs: [], | |
| _isRespawning: false, | |
| _nanTotalEver: 0, | |
| _nanRecoveryTpPending: false, | |
| _nanPhysicsDisabled: false, | |
| _kickReconnectDelay: 10000, | |
| chatLog: [], | |
| _recentChat: [], | |
| _actionInProgress: false, | |
| _lastCodeError: null, | |
| _lastCodeResult: null, | |
| _stuckCounter: 0, | |
| _lastPos: null, | |
| _thinkCount: 0, | |
| _lastInventorySnapshot: '', | |
| _retryCount: 0, | |
| _lastGoal: null, | |
| _nightShelterAttempt: false, | |
| _bootComplete: true, // Always true - no boot sequence in v36 | |
| _consecutiveAITimeouts: 0, | |
| }; | |
| let bot = null; | |
| let Vec3 = null; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CONVERSATION HISTORY β Multi-turn context for GLM-4 | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const conversationHistory = []; | |
| const MAX_HISTORY = CONFIG.maxConversationTurns; | |
| function addSystemMessage(content) { conversationHistory.push({ role: 'system', content }); trimHistory(); } | |
| function addAssistantMessage(content) { conversationHistory.push({ role: 'assistant', content }); trimHistory(); } | |
| function addUserMessage(content) { conversationHistory.push({ role: 'user', content }); trimHistory(); } | |
| function trimHistory() { | |
| while (conversationHistory.length > MAX_HISTORY) { | |
| if (conversationHistory[0]?.role === 'system' && conversationHistory.length > 1) { | |
| conversationHistory.splice(1, 1); | |
| } else { | |
| conversationHistory.shift(); | |
| } | |
| } | |
| } | |
| function softResetConversation() { | |
| const systemMsgs = conversationHistory.filter(m => m.role === 'system'); | |
| const lastMsgs = conversationHistory.slice(-4); | |
| conversationHistory.length = 0; | |
| for (const m of systemMsgs.slice(0, 1)) conversationHistory.push(m); | |
| for (const m of lastMsgs) conversationHistory.push(m); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // LLM INTERFACE β GLM-4 ONLY | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function callGLM(messages, maxTokens = CONFIG.maxTokens, temperature = 0.85) { | |
| const maxRetries = 3; | |
| const delays = [5000, 15000, 30000]; // exponential backoff | |
| for (let attempt = 0; attempt < maxRetries; attempt++) { | |
| try { | |
| const res = await fetch(CONFIG.llm.baseUrl, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': 'Bearer ' + CONFIG.llm.key, | |
| }, | |
| body: JSON.stringify({ | |
| model: CONFIG.llm.model, | |
| messages, | |
| max_tokens: maxTokens, | |
| temperature, | |
| stream: false, | |
| }), | |
| signal: AbortSignal.timeout(120000), // 120s timeout (was 45s) | |
| }); | |
| if (!res.ok) { | |
| const errText = await res.text().catch(() => ''); | |
| console.error('[GLM] Error ' + res.status + ': ' + errText.substring(0, 200)); | |
| if (attempt < maxRetries - 1) { | |
| console.log('[GLM] Retry ' + (attempt + 1) + '/' + maxRetries + ' in ' + (delays[attempt]/1000) + 's'); | |
| await sleep(delays[attempt]); | |
| continue; | |
| } | |
| state._consecutiveAITimeouts++; | |
| return null; | |
| } | |
| const data = await res.json(); | |
| state._consecutiveAITimeouts = 0; // Reset on success | |
| return data.choices?.[0]?.message?.content?.trim() ?? null; | |
| } catch (e) { | |
| console.error('[GLM] Attempt ' + (attempt+1) + '/' + maxRetries + ' failed: ' + e.message); | |
| if (attempt < maxRetries - 1) { | |
| console.log('[GLM] Retry in ' + (delays[attempt]/1000) + 's...'); | |
| await sleep(delays[attempt]); | |
| continue; | |
| } | |
| state._consecutiveAITimeouts++; | |
| return null; | |
| } | |
| } | |
| return null; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // PERCEPTION β Enhanced with spatial memory + knowledge graph context | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildObservation() { | |
| if (!bot?.entity || !hasValidPosition()) return 'Position invalid or not connected yet.'; | |
| const pos = bot.entity.position; | |
| const timeOfDay = bot.time?.timeOfDay ?? 0; | |
| const isNight = timeOfDay >= 13000 && timeOfDay < 23000; | |
| const timeStr = isNight ? 'NIGHT (DANGEROUS!)' : 'Day (safe)'; | |
| const inWater = isBotInWater(); | |
| let timeNote = ''; | |
| if (!isNight && timeOfDay > 10000) { | |
| const minsToNight = Math.round((13000 - timeOfDay) / 17); | |
| timeNote = ' (night in ~' + minsToNight + 's - BUILD SHELTER!)'; | |
| } else if (isNight) { | |
| const minsToDawn = Math.round((23000 - timeOfDay) / 17); | |
| timeNote = ' (dawn in ~' + minsToDawn + 's)'; | |
| } | |
| let obs = ''; | |
| // CURRICULUM GOAL | |
| obs += '== CURRENT GOAL ==\n'; | |
| const goal = curriculum.proposeGoal(''); | |
| obs += goal.goal + ' [' + goal.priority + '] - ' + goal.reason + '\n'; | |
| // VITALS | |
| obs += '\n== VITALS ==\n'; | |
| obs += 'Pos: ' + Math.round(pos.x) + ', ' + Math.round(pos.y) + ', ' + Math.round(pos.z) + '\n'; | |
| obs += 'HP: ' + Math.round(bot.health*10)/10 + '/20 | Food: ' + Math.round(bot.food*10)/10 + '/20\n'; | |
| if (bot.health < 10) obs += '*** LOW HP - EAT OR FLEE! ***\n'; | |
| if (bot.food < 10) obs += '*** LOW FOOD - EAT NOW! ***\n'; | |
| obs += 'Time: ' + timeStr + ' (' + Math.round(timeOfDay) + ')' + timeNote + '\n'; | |
| obs += 'Weather: ' + (bot.isRaining ? 'Raining' : 'Clear') + ' | Water: ' + inWater + '\n'; | |
| // INVENTORY | |
| obs += '\n== INVENTORY ==\n'; | |
| const items = bot.inventory.items(); | |
| if (items.length === 0) { | |
| obs += 'EMPTY - no items!\n'; | |
| } else { | |
| const inv = {}; | |
| for (const item of items) { | |
| if (!inv[item.name]) inv[item.name] = 0; | |
| inv[item.name] += item.count; | |
| } | |
| const tools = [], food = [], building = [], resources = [], other = []; | |
| for (const [name, count] of Object.entries(inv)) { | |
| const entry = name + ' x' + count; | |
| if (name.includes('pickaxe') || name.includes('axe') || name.includes('sword') || name.includes('shovel') || name.includes('hoe') || name.includes('bow')) tools.push(entry); | |
| else if (isFoodItem(name)) food.push(entry); | |
| else if (name.includes('planks') || name.includes('dirt') || name.includes('cobblestone') || name.includes('stone') || name.includes('log')) building.push(entry); | |
| else if (name.includes('ingot') || name.includes('ore') || name.includes('diamond') || name.includes('coal') || name.includes('stick') || name.includes('raw_')) resources.push(entry); | |
| else other.push(entry); | |
| } | |
| if (tools.length > 0) obs += 'Tools: ' + tools.join(', ') + '\n'; | |
| if (food.length > 0) obs += 'Food: ' + food.join(', ') + '\n'; | |
| if (building.length > 0) obs += 'Building: ' + building.join(', ') + '\n'; | |
| if (resources.length > 0) obs += 'Resources: ' + resources.join(', ') + '\n'; | |
| if (other.length > 0) obs += 'Other: ' + other.join(', ') + '\n'; | |
| const heldItem = bot.heldItem; | |
| if (heldItem) obs += 'Holding: ' + heldItem.name + '\n'; | |
| const armorSlots = ['head', 'torso', 'legs', 'feet']; | |
| const armorItems = []; | |
| for (const slot of armorSlots) { | |
| const equipment = bot.inventory.slots[bot.getEquipmentDestSlot(slot)]; | |
| if (equipment) armorItems.push(slot + ':' + equipment.name); | |
| } | |
| if (armorItems.length > 0) obs += 'Armor: ' + armorItems.join(', ') + '\n'; | |
| // KNOWLEDGE GRAPH: what can we craft right now? | |
| const craftable = KNOWLEDGE_GRAPH.getCraftableNow(Object.keys(inv)); | |
| if (craftable.length > 0) obs += 'Craftable: ' + craftable.join(', ') + '\n'; | |
| } | |
| // NEXT CRAFT STEP from Knowledge Graph | |
| const invNames2 = {}; | |
| for (const i of bot.inventory.items()) { invNames2[i.name] = i.count; } | |
| const nextGoal2 = KNOWLEDGE_GRAPH.getNextGoal(Object.keys(invNames2), benchmark.milestones); | |
| if (nextGoal2) { | |
| obs += '\n== NEXT CRAFT STEP ==\n'; | |
| obs += 'Target: ' + nextGoal2.item + '\n'; | |
| obs += 'Need: ' + nextGoal2.requires.join(', ') + '\n'; | |
| if (nextGoal2.station) obs += 'Station: ' + nextGoal2.station + '\n'; | |
| const craftPath = KNOWLEDGE_GRAPH.getCraftingPath(nextGoal2.item); | |
| obs += 'Path: ' + craftPath + '\n'; | |
| } | |
| // NEARBY ENTITIES | |
| obs += '\n== NEARBY ==\n'; | |
| const nearbyPlayers = [], nearbyHostile = [], nearbyPassive = [], nearbyItems = []; | |
| try { | |
| for (const [id, entity] of Object.entries(bot.entities)) { | |
| if (entity === bot.entity || !entity.position || !isFinite(entity.position.x)) continue; | |
| const dist = Math.round(pos.distanceTo(entity.position)); | |
| if (dist > 40) continue; | |
| if (entity.type === 'player' && entity.username) nearbyPlayers.push(entity.username + '(' + dist + 'm)'); | |
| else if (isHostileMob(entity)) { | |
| const hp = entity.health ? ' hp:' + Math.round(entity.health) : ''; | |
| nearbyHostile.push(entity.name + '(' + dist + 'm' + hp + ')'); | |
| } | |
| else if (entity.type === 'mob') nearbyPassive.push(entity.name + '(' + dist + 'm)'); | |
| else if (entity.type === 'object' || entity.name === 'item' || entity.name === 'Item') nearbyItems.push('item(' + dist + 'm)'); | |
| } | |
| } catch {} | |
| if (nearbyPlayers.length > 0) obs += 'Players: ' + nearbyPlayers.join(', ') + '\n'; | |
| if (nearbyHostile.length > 0) obs += 'HOSTILES: ' + nearbyHostile.join(', ') + '\n'; | |
| if (nearbyPassive.length > 0) obs += 'Animals: ' + nearbyPassive.join(', ') + '\n'; | |
| if (nearbyItems.length > 0) obs += 'Ground items: ' + nearbyItems.length + ' nearby\n'; | |
| // SURROUNDING BLOCKS | |
| obs += '\n== SURROUNDING (5x5) ==\n'; | |
| try { | |
| const cx = Math.floor(pos.x), cy = Math.floor(pos.y), cz = Math.floor(pos.z); | |
| for (let dz = -2; dz <= 2; dz++) { | |
| let row = ''; | |
| for (let dx = -2; dx <= 2; dx++) { | |
| if (dx === 0 && dz === 0) { row += '[ME] '; continue; } | |
| const b = bot.blockAt(new Vec3(cx + dx, cy, cz + dz)); | |
| if (!b) { row += '??? '; continue; } | |
| const n = b.name; | |
| if (n === 'air') row += '. '; | |
| else if (n.includes('log')) row += 'L '; | |
| else if (n.includes('leaves')) row += 'l '; | |
| else if (n === 'water') row += '~ '; | |
| else if (n === 'lava') row += '! '; | |
| else if (n.includes('ore')) row += 'O '; | |
| else if (n === 'stone' || n === 'deepslate') row += 'S '; | |
| else if (n === 'grass_block' || n === 'dirt') row += 'D '; | |
| else if (n === 'sand') row += 's '; | |
| else if (n === 'crafting_table') row += 'C '; | |
| else if (n === 'furnace') row += 'F '; | |
| else if (n === 'torch') row += 'T '; | |
| else if (n === 'bed') row += 'B '; | |
| else row += '? '; | |
| } | |
| obs += row + '\n'; | |
| } | |
| } catch {} | |
| // RESOURCES NEARBY (with spatial memory) | |
| obs += '\n== RESOURCES ==\n'; | |
| const resourceTypes = [ | |
| ['oak_log,birch_log,spruce_log,dark_oak_log,acacia_log,jungle_log', 'Trees'], | |
| ['coal_ore', 'Coal'], | |
| ['iron_ore', 'Iron'], | |
| ['gold_ore', 'Gold'], | |
| ['diamond_ore', 'Diamond'], | |
| ['crafting_table', 'CraftTable'], | |
| ['furnace', 'Furnace'], | |
| ]; | |
| let foundResources = false; | |
| for (const [blockNames, label] of resourceTypes) { | |
| for (const bn of blockNames.split(',')) { | |
| const found = bot.findBlock({ matching: (b) => b?.name === bn, maxDistance: 48 }); | |
| if (found) { | |
| const dist = Math.round(pos.distanceTo(found.position)); | |
| obs += label + ': ' + found.position.x + ',' + found.position.y + ',' + found.position.z + ' (' + dist + 'm)\n'; | |
| spatialMemory.record(bn, found.position.x, found.position.y, found.position.z, 'resource detected'); | |
| foundResources = true; | |
| break; | |
| } | |
| } | |
| } | |
| // Add spatial memory for resources not currently visible | |
| const spatialResources = spatialMemory.getResourceLocations(pos.x, pos.z); | |
| for (const [type, loc] of Object.entries(spatialResources)) { | |
| if (!foundResources || !obs.includes(type.replace('_ore', ''))) { | |
| obs += type + ' (memory): ' + loc.x + ',' + loc.y + ',' + loc.z + ' (' + loc.dist + 'm)\n'; | |
| } | |
| } | |
| if (!foundResources && Object.keys(spatialResources).length === 0) obs += 'No resources known nearby\n'; | |
| // SHELTER & HOME | |
| obs += '\n== BASE ==\n'; | |
| if (memory.shelterPos) { | |
| const sd = Math.round(Math.sqrt((pos.x-memory.shelterPos.x)**2 + (pos.z-memory.shelterPos.z)**2)); | |
| obs += 'Shelter: ' + memory.shelterPos.x + ',' + memory.shelterPos.y + ',' + memory.shelterPos.z + ' (' + sd + 'm)\n'; | |
| } else { | |
| obs += 'No shelter!\n'; | |
| } | |
| // STATUS | |
| obs += '\n== STATUS ==\n'; | |
| obs += 'Deaths: ' + memory.deaths + ' | Kills: ' + memory.kills + ' | Mined: ' + memory.blocksMined + ' | Crafted: ' + memory.itemsCrafted + '\n'; | |
| obs += 'Goals completed: ' + memory.completedGoals.length + ' | Failed: ' + memory.failedGoals.length + '\n'; | |
| obs += 'Benchmark: ' + benchmark.getReport() + '\n'; | |
| if (state._lastCodeError) obs += 'LastError: ' + state._lastCodeError.substring(0, 150) + '\n'; | |
| if (memory.dangerZones?.length > 0) { | |
| const now = Date.now(); | |
| const activeZones = memory.dangerZones.filter(z => now - z.time < 90000); | |
| if (activeZones.length > 0) obs += 'DangerZones: ' + activeZones.map(z => z.x + ',' + z.z).join(' | ') + '\n'; | |
| } | |
| // EPISODIC MEMORY: relevant past experiences | |
| const episodicContext = episodicMemory.getRelevantContext(goal.goal, obs.substring(0, 300)); | |
| if (episodicContext) obs += '\n' + episodicContext + '\n'; | |
| // REMEDIES: known failure patterns | |
| const remedyContext = remedySystem.getRemedyContext(goal.goal + ' ' + (state._lastCodeError || '')); | |
| if (remedyContext) obs += '\n' + remedyContext + '\n'; | |
| // RECENT CHAT | |
| if (state._recentChat.length > 0) { | |
| obs += '\n== CHAT ==\n'; | |
| for (const msg of state._recentChat.slice(-3)) { | |
| obs += '<' + msg.sender + '> ' + msg.text + '\n'; | |
| } | |
| } | |
| return obs; | |
| } | |
| function isBotInWater() { | |
| if (!bot?.entity || !hasValidPosition()) return false; | |
| try { | |
| const pos = bot.entity.position; | |
| const feet = bot.blockAt({x:Math.floor(pos.x),y:Math.floor(pos.y),z:Math.floor(pos.z)}); | |
| const head = bot.blockAt({x:Math.floor(pos.x),y:Math.floor(pos.y+1),z:Math.floor(pos.z)}); | |
| return feet?.name === 'water' || head?.name === 'water'; | |
| } catch { return false; } | |
| } | |
| function isHostileMob(entity) { | |
| if (!entity) return false; | |
| if (entity.kind === 'Hostile mobs') return true; | |
| const name = (entity.name ?? '').toLowerCase(); | |
| const hostiles = ['zombie','skeleton','creeper','spider','witch','slime','phantom','drowned','pillager','vindicator','enderman','cave_spider','blaze','ghast','magma_cube','wither_skeleton','stray','husk']; | |
| return hostiles.some(m => name.includes(m)); | |
| } | |
| function isFoodItem(name) { | |
| const foods = ['bread','cooked_beef','cooked_porkchop','cooked_chicken','cooked_mutton','cooked_cod','cooked_salmon','apple','golden_apple','melon_slice','sweet_berries','carrot','baked_potato','beetroot','cookie','cake','pumpkin_pie','beef','porkchop','chicken','mutton','cod','salmon','rotten_flesh','potato','wheat','beetroot_soup','mushroom_stew','rabbit_stew']; | |
| return foods.some(f => name.includes(f)); | |
| } | |
| function snapshotInventory() { | |
| try { | |
| if (!bot?.inventory) return ''; | |
| return bot.inventory.items().map(i => i.name + 'x' + i.count).join(','); | |
| } catch { return ''; } | |
| } | |
| // v36: Auto-track metrics from inventory changes | |
| function parseInventoryStr(invStr) { | |
| const map = {}; | |
| if (!invStr) return map; | |
| for (const part of invStr.split(',')) { | |
| const match = part.match(/(.+?)x(\d+)/); | |
| if (match) map[match[1].trim()] = parseInt(match[2]); | |
| } | |
| return map; | |
| } | |
| function trackMetricsFromInventory(preInv, postInv) { | |
| try { | |
| const pre = parseInventoryStr(preInv); | |
| const post = parseInventoryStr(postInv); | |
| let newMined = 0, newCrafted = 0; | |
| const minedItems = ['cobblestone', 'stone', 'dirt', 'oak_log', 'birch_log', 'spruce_log', 'dark_oak_log', 'acacia_log', 'jungle_log', 'coal_ore', 'iron_ore', 'gold_ore', 'diamond_ore', 'copper_ore', 'lapis_ore', 'redstone_ore', 'emerald_ore', 'sand', 'gravel', 'clay']; | |
| for (const [name, count] of Object.entries(post)) { | |
| const preCount = pre[name] || 0; | |
| if (count > preCount) { | |
| const diff = count - preCount; | |
| if (minedItems.some(m => name.includes(m))) { | |
| newMined += diff; | |
| } else { | |
| newCrafted += diff; | |
| } | |
| } | |
| } | |
| if (newMined > 0) { memory.blocksMined += newMined; console.log('[Metrics] +' + newMined + ' blocks mined (auto-tracked)'); } | |
| if (newCrafted > 0) { memory.itemsCrafted += newCrafted; console.log('[Metrics] +' + newCrafted + ' items crafted (auto-tracked)'); } | |
| } catch {} | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CODE EXECUTION β The heart of the architecture | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildExecutionContext() { | |
| return { | |
| bot, | |
| goals, | |
| Movements, | |
| Vec3, | |
| sleep, | |
| rand, | |
| hasValidPosition, | |
| clearAllControls, | |
| memory, | |
| skills: skillLibrary, | |
| spatialMemory, | |
| episodicMemory, | |
| remedySystem, | |
| KNOWLEDGE_GRAPH, | |
| isFoodItem, | |
| isHostileMob, | |
| isBotInWater, | |
| Math, | |
| JSON, | |
| Date, | |
| String, | |
| Object, | |
| Array, | |
| parseInt, | |
| parseFloat, | |
| console: { | |
| log: (...args) => console.log('[Code]', ...args), | |
| error: (...args) => console.error('[Code]', ...args), | |
| warn: (...args) => console.warn('[Code]', ...args), | |
| }, | |
| chat: (msg) => { if (msg) throttledChat(String(msg).substring(0, 100)); }, | |
| }; | |
| } | |
| const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; | |
| async function executeCode(code) { | |
| if (!bot?.entity || !hasValidPosition()) { | |
| return { success: false, error: 'Bot not connected or position invalid', output: '' }; | |
| } | |
| state._actionInProgress = true; | |
| let output = ''; | |
| const startTime = Date.now(); | |
| try { | |
| const ctx = buildExecutionContext(); | |
| ctx.log = (...args) => { | |
| const msg = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '); | |
| output += msg + '\n'; | |
| console.log('[Code]', ...args); | |
| }; | |
| let fn; | |
| try { | |
| fn = new AsyncFunction(...Object.keys(ctx), code); | |
| } catch (syntaxErr) { | |
| return { success: false, error: 'Syntax error: ' + syntaxErr.message, output }; | |
| } | |
| const timeoutPromise = new Promise((_, reject) => | |
| setTimeout(() => reject(new Error('Code timeout (' + CONFIG.codeTimeout/1000 + 's)')), CONFIG.codeTimeout) | |
| ); | |
| const result = await Promise.race([fn(...Object.values(ctx)), timeoutPromise]); | |
| const elapsed = Date.now() - startTime; | |
| const resultStr = result !== undefined ? String(result) : 'done'; | |
| state._lastCodeError = null; | |
| state._lastCodeResult = resultStr + ' (' + elapsed + 'ms)'; | |
| return { success: true, output: output + (resultStr !== 'done' ? ' -> ' + resultStr : ''), result: resultStr, elapsed }; | |
| } catch (execErr) { | |
| const elapsed = Date.now() - startTime; | |
| const errMsg = execErr.message || String(execErr); | |
| state._lastCodeError = errMsg; | |
| state._lastCodeResult = 'ERROR: ' + errMsg; | |
| clearAllControls(); | |
| try { bot.pathfinder?.setGoal(null); } catch {} | |
| return { success: false, error: errMsg, output, elapsed }; | |
| } finally { | |
| state._actionInProgress = false; | |
| clearAllControls(); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SYSTEM PROMPT β v32.0 OMEGA | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function buildSystemPrompt() { | |
| const skillList = skillLibrary.getSkillList(); | |
| const skillContext = skillLibrary.getSkillCodeForContext('current situation', 3); | |
| return 'Eres ' + CONFIG.profile.name + ', una IA que juega Minecraft como una jugadora PRO.\n' + | |
| CONFIG.profile.personality + '\n\n' + | |
| 'Tienes LIBRE ALBEDRIO TOTAL. Eres una jugadora real, no un bot limitado.\n' + | |
| 'Escribes CODIGO JAVASCRIPT que se ejecuta directamente con la API de mineflayer.\n' + | |
| 'Puedes hacer CUALQUIER cosa que una jugadora PRO haria.\n\n' + | |
| '== ARQUITECTURA OMEGA (v32.0) ==\n' + | |
| '1. Recibes tu GOAL actual del Curriculum (Voyager-style automatic curriculum)\n' + | |
| '2. Consultas Knowledge Graph para saber que necesitas craftear\n' + | |
| '3. Recibes Experiencias Episodicas relevantes (M2PA memory)\n' + | |
| '4. Recibes Remedios para errores conocidos (MineEvolve)\n' + | |
| '5. Escribes codigo JavaScript para actuar\n' + | |
| '6. Si falla, usas DEPS: Describe el error, Explica porque, Plan nuevo, Selecciona accion\n' + | |
| '7. Tienes hasta ' + CONFIG.maxRetries + ' intentos para cada goal (iterative retry)\n' + | |
| '8. Si exitoso, se guarda como Skill y Episodio para el futuro\n\n' + | |
| '== CODIGO JAVASCRIPT - API DISPONIBLE ==\n\n' + | |
| 'Tu codigo es el CUERPO de una funcion async. Tienes acceso a:\n\n' + | |
| '**bot** - El bot de mineflayer (API completa):\n' + | |
| '- bot.entity.position - tu posicion {x, y, z}\n' + | |
| '- bot.health, bot.food - salud y comida\n' + | |
| '- bot.inventory.items() - lista de items en inventario\n' + | |
| '- bot.heldItem - item en mano\n' + | |
| '- bot.equip(item, slot) - equipar item (slot: "hand", "head", "torso", "legs", "feet")\n' + | |
| '- bot.dig(block) - minar bloque (await!)\n' + | |
| '- bot.placeBlock(referenceBlock, faceVector) - colocar bloque (await!)\n' + | |
| ' CRITICAL: referenceBlock es un bloque YA COLOCADO contra el cual pones el nuevo\n' + | |
| ' faceVector es un Vec3 que apunta del referenceBlock hacia donde va el nuevo bloque\n' + | |
| ' Para colocar en el suelo: referenceBlock = bloque debajo (y-1), faceVector = new Vec3(0,1,0)\n' + | |
| '- bot.craft(recipe, count, craftingTable) - craftear (await!)\n' + | |
| '- bot.recipesFor(itemID, null, count, craftingTable) - buscar recetas\n' + | |
| ' craftingTable = null para recetas 2x2, o un Block de crafting_table para recetas 3x3\n' + | |
| '- bot.activateItem() - usar item equipado (comer)\n' + | |
| '- bot.findBlock({matching, maxDistance}) - encontrar bloque cercano (returns Block or null)\n' + | |
| ' IMPORTANTE: matching usa b?.name (null-safe): matching: b => b?.name === "oak_log"\n' + | |
| '- bot.blockAt(vec3) - bloque en posicion\n' + | |
| '- bot.canDigBlock(block) - se puede minar?\n' + | |
| '- bot.chat(msg) - enviar mensaje\n' + | |
| '- bot.look(yaw, pitch, force) - mirar direccion\n' + | |
| '- bot.setControlState(control, state) - controles: forward, back, left, right, jump, sprint, sneak\n' + | |
| '- bot.attack(entity) - atacar entidad\n' + | |
| '- bot.registry.itemsByName["oak_planks"] - info de item (.id para recipesFor)\n\n' + | |
| '**CRITICAL: bot.entities es un OBJETO, no un Map.**\n' + | |
| ' ITERAR SIEMPRE ASI: for (const [id, entity] of Object.entries(bot.entities))\n' + | |
| ' NUNCA: for (const [id, entity] of bot.entities) <-- ESTO CRASHEA\n\n' + | |
| '**Vec3** - new Vec3(x, y, z) para posiciones y faceVector\n' + | |
| '**goals** - new goals.GoalBlock(x,y,z), new goals.GoalNear(x,y,z,range)\n' + | |
| '**Movements** - new Movements(bot), moves.canDig, bot.pathfinder.setMovements(moves)\n' + | |
| '**sleep(ms)**, **chat(msg)**, **rand(min,max)**\n' + | |
| '**memory** - memoria persistente (shelterPos, home, deaths, kills, etc.)\n' + | |
| '**spatialMemory** - memoria espacial (What-Where-When)\n' + | |
| ' spatialMemory.record(what, x, y, z, details)\n' + | |
| ' spatialMemory.findNearest(what, fromX, fromZ, maxDist)\n' + | |
| '**KNOWLEDGE_GRAPH** - grafo de recetas\n' + | |
| ' KNOWLEDGE_GRAPH.getCraftableNow(inventoryNames)\n' + | |
| ' KNOWLEDGE_GRAPH.getNextGoal(inventoryNames)\n' + | |
| ' KNOWLEDGE_GRAPH.getCraftingPath(item)\n' + | |
| '**skills** - Skill library (save/get skills)\n' + | |
| '**isFoodItem(name), isHostileMob(entity), isBotInWater()** - helpers\n\n' + | |
| '== DEPS ERROR RECOVERY ==\n' + | |
| 'Cuando tu codigo falla, sigue este proceso:\n' + | |
| '1. DESCRIBE: Que paso? Que error hubo?\n' + | |
| '2. EXPLICA: Porque fallo? Que faltaba? Que era incorrecto?\n' + | |
| '3. PLAN: Que vas a hacer diferente? Nueva estrategia?\n' + | |
| '4. SELECCIONA: Cual es la proxima accion especifica?\n\n' + | |
| '== REGLAS DE SUPERVIVENCIA (PRIORIDAD) ==\n' + | |
| '1. HP<10 + mobs hostiles -> HUIR (no luchar con HP bajo)\n' + | |
| '2. food<12 -> COMER (prioridad sobre todo excepto huir)\n' + | |
| '3. Noche sin refugio -> ESCAVAR HOYO 3 bloques profundo y tapar arriba\n' + | |
| '4. Sin herramientas -> madera -> planks -> sticks -> crafting_table -> wooden_pickaxe\n' + | |
| '5. Sin comida -> CAZAR animales\n' + | |
| '6. wooden_pickaxe -> stone -> stone_pickaxe -> furnace -> iron -> iron tools\n' + | |
| '7. NUNCA uses /give, /tp, /fill, /kill para jugar\n' + | |
| '8. NO repitas la misma accion si fallo. USA DEPS para cambiar estrategia.\n\n' + | |
| '== SKILLS APRENDIDOS ==\n' + | |
| (skillList || 'Ninguno aun.') + '\n\n' + | |
| (skillContext ? skillContext + '\n\n' : '') + | |
| '\n== EJEMPLOS DE CODIGO QUE FUNCIONAN ==\n\n' + | |
| '// CHOP TREE:\n' + | |
| 'const logTypes = ["oak_log","birch_log","spruce_log","dark_oak_log","acacia_log","jungle_log"];\n' + | |
| 'let treeBlock = null;\n' + | |
| 'for (const log of logTypes) {\n' + | |
| ' treeBlock = bot.findBlock({ matching: b => b?.name === log, maxDistance: 48 });\n' + | |
| ' if (treeBlock) break;\n' + | |
| '}\n' + | |
| 'if (!treeBlock) { chat("no trees"); return "no trees"; }\n' + | |
| 'const moves = new Movements(bot); moves.canDig = false;\n' + | |
| 'bot.pathfinder.setMovements(moves);\n' + | |
| 'await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2));\n' + | |
| 'await sleep(500);\n' + | |
| 'if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); }\n\n' + | |
| '// CRAFT PLANKS (2x2, no table needed):\n' + | |
| 'const item = bot.registry.itemsByName["oak_planks"];\n' + | |
| 'const recipes = bot.recipesFor(item.id, null, 1, null);\n' + | |
| 'if (recipes.length > 0) { await bot.craft(recipes[0], 4, null); }\n\n' + | |
| '// CRAFT STICKS (2x2, no table needed):\n' + | |
| 'const stickItem = bot.registry.itemsByName["stick"];\n' + | |
| 'const stickRecipes = bot.recipesFor(stickItem.id, null, 1, null);\n' + | |
| 'if (stickRecipes.length > 0) { await bot.craft(stickRecipes[0], 4, null); }\n\n' + | |
| '// PLACE CRAFTING TABLE:\n' + | |
| 'const tableItem = bot.inventory.items().find(i => i.name === "crafting_table");\n' + | |
| 'await bot.equip(tableItem, "hand");\n' + | |
| 'await sleep(400);\n' + | |
| 'const groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0));\n' + | |
| 'if (groundBelow && groundBelow.name !== "air") {\n' + | |
| ' await bot.placeBlock(groundBelow, new Vec3(0, 1, 0));\n' + | |
| ' await sleep(500);\n' + | |
| '}\n\n' + | |
| '// CRAFT PICKAXE (3x3, needs crafting table placed):\n' + | |
| 'const pickItem = bot.registry.itemsByName["wooden_pickaxe"];\n' + | |
| 'const tableBlock = bot.findBlock({ matching: b => b?.name === "crafting_table", maxDistance: 8 });\n' + | |
| 'if (pickItem && tableBlock) {\n' + | |
| ' const recipes = bot.recipesFor(pickItem.id, null, 1, tableBlock);\n' + | |
| ' if (recipes.length > 0) { await bot.craft(recipes[0], 1, tableBlock); }\n' + | |
| '}\n\n' + | |
| '// MINE STONE:\n' + | |
| 'const pick = bot.inventory.items().find(i => i.name.includes("pickaxe"));\n' + | |
| 'if (pick) { await bot.equip(pick, "hand"); await sleep(300); }\n' + | |
| 'const stoneBlock = bot.findBlock({ matching: b => b?.name === "stone", maxDistance: 16 });\n' + | |
| 'if (stoneBlock && bot.canDigBlock(stoneBlock)) { await bot.dig(stoneBlock); }\n\n' + | |
| '// EAT FOOD:\n' + | |
| 'const food = bot.inventory.items().find(i => isFoodItem(i.name));\n' + | |
| 'if (food) { await bot.equip(food, "hand"); await sleep(300); await bot.activateItem(); }\n\n' + | |
| '// FLEE DANGER:\n' + | |
| 'let threat = null, minDist = 16;\n' + | |
| 'for (const [id, entity] of Object.entries(bot.entities)) {\n' + | |
| ' if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue;\n' + | |
| ' let dist = bot.entity.position.distanceTo(entity.position);\n' + | |
| ' if (dist < minDist) { threat = entity; minDist = dist; }\n' + | |
| '}\n' + | |
| 'if (threat) {\n' + | |
| ' let dx = bot.entity.position.x - threat.position.x;\n' + | |
| ' let dz = bot.entity.position.z - threat.position.z;\n' + | |
| ' let len = Math.sqrt(dx*dx + dz*dz) || 1;\n' + | |
| ' dx /= len; dz /= len;\n' + | |
| ' let yaw = Math.atan2(-dx, dz);\n' + | |
| ' await bot.look(yaw, 0, true);\n' + | |
| ' bot.setControlState("forward", true);\n' + | |
| ' bot.setControlState("sprint", true);\n' + | |
| ' bot.setControlState("jump", true);\n' + | |
| ' await sleep(3000);\n' + | |
| ' clearAllControls();\n' + | |
| '}\n\n' + | |
| '== FORMATO DE RESPUESTA ==\n' + | |
| 'Responde SIEMPRE en este formato JSON:\n' + | |
| '{\n' + | |
| ' "thought": "tu pensamiento (usa DEPS si hubo error antes)",\n' + | |
| ' "code": "tu codigo JavaScript (NO backticks, NO template literals)",\n' + | |
| ' "chat": "mensaje corto en chat (o vacio)",\n' + | |
| ' "saveSkill": null,\n' + | |
| ' "emotion": "curious|focused|scared|happy|determined|bored|adventurous|cautious|chill"\n' + | |
| '}\n\n' + | |
| 'CRITICO:\n' + | |
| '- NUNCA uses backticks ni template literals. Usa " + " para concatenar.\n' + | |
| '- El codigo es el BODY de una funcion async. NO envuelvas en async function(){}.\n' + | |
| '- Usa await para operaciones async (bot.dig, bot.craft, sleep).\n' + | |
| '- Habla en espanol mexicano casual. TODO minusculas. Sin emojis.'; | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // AI THINK β Multi-turn with Voyager-style iterative retry + DEPS recovery | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function zelinThink() { | |
| if (!bot?.entity || !hasValidPosition()) return null; | |
| const observation = buildObservation(); | |
| if (conversationHistory.length === 0) { | |
| addSystemMessage(buildSystemPrompt()); | |
| } | |
| addUserMessage('== SITUACION ACTUAL ==\n' + observation + '\n\nQue haces? Escribe codigo para actuar.'); | |
| const raw = await callGLM(conversationHistory, CONFIG.maxCodeTokens, 0.85); | |
| if (!raw) { | |
| addAssistantMessage('(GLM-4 no respondio)'); | |
| return null; | |
| } | |
| try { | |
| const cleaned = raw.replace(/```json|```javascript|```js|```/g, '').trim(); | |
| let parsed = null; | |
| try { | |
| const start = cleaned.indexOf('{'); | |
| const end = cleaned.lastIndexOf('}'); | |
| if (start >= 0 && end > start) { | |
| parsed = JSON.parse(cleaned.slice(start, end + 1)); | |
| } | |
| } catch (jsonErr) { | |
| const thoughtMatch = cleaned.match(/"thought"\s*:\s*"((?:[^"\\]|\\.)*)"/); | |
| const chatMatch = cleaned.match(/"chat"\s*:\s*"((?:[^"\\]|\\.)*)"/); | |
| const emotionMatch = cleaned.match(/"emotion"\s*:\s*"((?:[^"\\]|\\.)*)"/); | |
| let code = ''; | |
| const codeQuotedMatch = cleaned.match(/"code"\s*:\s*"((?:[^"\\]|\\.|[\n\r])*)"/s); | |
| const codeBacktickMatch = cleaned.match(/"code"\s*:\s*`([^`]*)`/s); | |
| if (codeBacktickMatch) code = codeBacktickMatch[1]; | |
| else if (codeQuotedMatch) code = codeQuotedMatch[1].replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"'); | |
| const saveSkillMatch = cleaned.match(/"saveSkill"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]*)"[^}]*"description"\s*:\s*"((?:[^"\\]|\\.)*)"/); | |
| parsed = { | |
| thought: thoughtMatch ? thoughtMatch[1] : '', | |
| code, | |
| chat: chatMatch ? chatMatch[1] : '', | |
| emotion: emotionMatch ? emotionMatch[1] : 'chill', | |
| saveSkill: saveSkillMatch ? { name: saveSkillMatch[1], code: '', description: saveSkillMatch[2] } : null, | |
| }; | |
| } | |
| if (!parsed) { | |
| addAssistantMessage(raw.substring(0, 200)); | |
| return null; | |
| } | |
| addAssistantMessage(raw.substring(0, 500)); | |
| if (parsed.chat) { | |
| parsed.chat = parsed.chat | |
| .replace(/[()]/g, '') | |
| .replace(/quilombo/gi, 'desmadre') | |
| .replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '') | |
| .trim(); | |
| if (parsed.chat.length > 0 && parsed.chat[0] === parsed.chat[0].toUpperCase() && parsed.chat[0] !== parsed.chat[0].toLowerCase()) { | |
| parsed.chat = parsed.chat[0].toLowerCase() + parsed.chat.slice(1); | |
| } | |
| } | |
| return parsed; | |
| } catch (e) { | |
| console.error('[AI] Parse error: ' + e.message); | |
| addAssistantMessage('(parse error: ' + e.message + ')'); | |
| return null; | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SURVIVAL REFLEXES β Minimal safety net | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function checkEmergencyReflexes() { | |
| if (!bot?.entity || !hasValidPosition()) return false; | |
| // DROWNING | |
| if (isBotInWater() && !state._drowningEscape) { | |
| const headBlock = bot.blockAt({ | |
| x: Math.floor(bot.entity.position.x), | |
| y: Math.floor(bot.entity.position.y + 1), | |
| z: Math.floor(bot.entity.position.z) | |
| }); | |
| if (headBlock?.name === 'water') { | |
| console.log('[Reflex] DROWNING - emergency swim up'); | |
| state._drowningEscape = true; | |
| emergencySwimUp(); | |
| return true; | |
| } | |
| } | |
| // STARVATION - auto-eat | |
| if (bot.food < 6 && !state._actionInProgress) { | |
| const food = bot.inventory.items().find(i => isFoodItem(i.name)); | |
| if (food) { | |
| console.log('[Reflex] STARVING (food=' + Math.round(bot.food) + ') - auto-eating ' + food.name); | |
| emergencyEat(food); | |
| return true; | |
| } | |
| } | |
| // Night warning - inform AI (not hardcoded action) | |
| // AI should decide what to do at night based on its situation | |
| // LOW HP + HOSTILES - ACTUALLY FLEE (fight-or-flight instinct) | |
| if (bot.health < 10 && isHostileNearby() && !state._actionInProgress) { | |
| console.log('[Reflex] LOW HP + hostiles - FLEEING physically!'); | |
| state.emotion = 'scared'; | |
| emergencyFlee(); // ACTIVE: actually run away, not just set emotion | |
| return true; | |
| } | |
| // NIGHT without shelter - dig hole when AI is unreachable | |
| const timeOfDay = bot.time?.timeOfDay ?? 0; | |
| const isNight = timeOfDay >= 13000 && timeOfDay < 23000; | |
| if (isNight && !memory.shelterPos && !state._actionInProgress && !state._nightShelterAttempt && state._consecutiveAITimeouts > 3) { | |
| console.log('[Reflex] Night + no shelter + AI unreachable - digging emergency hole!'); | |
| state._nightShelterAttempt = true; | |
| emergencyDigHole(); | |
| return true; | |
| } | |
| if (!isNight && timeOfDay < 5000) state._nightShelterAttempt = false; | |
| return false; | |
| } | |
| async function emergencySwimUp() { | |
| try { await bot.look(bot.entity.yaw, -Math.PI / 2, true); } catch {} | |
| bot.setControlState('jump', true); | |
| bot.setControlState('forward', true); | |
| for (let i = 0; i < 12; i++) { | |
| await sleep(500); | |
| if (!isBotInWater()) break; | |
| } | |
| if (isBotInWater()) { | |
| for (let attempt = 0; attempt < 6; attempt++) { | |
| const yaw = (attempt / 6) * Math.PI * 2; | |
| try { await bot.look(yaw, 0, true); } catch {} | |
| bot.setControlState('forward', true); | |
| bot.setControlState('jump', true); | |
| await sleep(2000); | |
| clearAllControls(); | |
| if (!isBotInWater()) break; | |
| } | |
| } | |
| clearAllControls(); | |
| state._drowningEscape = false; | |
| } | |
| async function emergencyEat(foodItem) { | |
| try { | |
| await bot.equip(foodItem, 'hand'); | |
| await sleep(400); | |
| await bot.activateItem(); | |
| await sleep(600); // Wait for eating animation | |
| console.log('[Reflex] Ate ' + foodItem.name); | |
| } catch (e) { console.error('[Reflex] Eat failed: ' + e.message); } | |
| } | |
| async function emergencyFlee() { | |
| if (!bot?.entity) return; | |
| try { | |
| let threat = null, minDist = 16; | |
| for (const [id, entity] of Object.entries(bot.entities)) { | |
| if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue; | |
| try { | |
| const dist = bot.entity.position.distanceTo(entity.position); | |
| if (dist < minDist) { threat = entity; minDist = dist; } | |
| } catch {} | |
| } | |
| if (!threat) { clearAllControls(); return; } | |
| // Run in OPPOSITE direction from threat | |
| const dx = bot.entity.position.x - threat.position.x; | |
| const dz = bot.entity.position.z - threat.position.z; | |
| const len = Math.sqrt(dx*dx + dz*dz) || 1; | |
| const yaw = Math.atan2(-dx/len, dz/len); | |
| await bot.look(yaw, 0, true); | |
| bot.setControlState('forward', true); | |
| bot.setControlState('sprint', true); | |
| bot.setControlState('jump', true); | |
| // Run for 3 seconds | |
| await sleep(3000); | |
| clearAllControls(); | |
| // Record danger zone | |
| spatialMemory.record('danger', Math.round(threat.position.x), Math.round(threat.position.y), Math.round(threat.position.z), threat.name); | |
| memory.dangerZones.push({x: Math.floor(threat.position.x), z: Math.floor(threat.position.z), time: Date.now()}); | |
| memory.save(); | |
| console.log('[Reflex] Fled from ' + threat.name + ' at ' + Math.round(minDist) + 'm'); | |
| } catch (e) { | |
| console.error('[Reflex] Flee error: ' + e.message); | |
| clearAllControls(); | |
| } | |
| } | |
| async function emergencyDigHole() { | |
| if (!bot?.entity || !hasValidPosition()) return; | |
| try { | |
| const pos = bot.entity.position; | |
| const bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); | |
| console.log('[Reflex] Digging emergency hole at ' + bx + ',' + by + ',' + bz); | |
| // Dig down 3 blocks | |
| for (let dy = 0; dy < 3; dy++) { | |
| const block = bot.blockAt(new Vec3(bx, by - 1 - dy, bz)); | |
| if (block && bot.canDigBlock(block)) { | |
| await bot.dig(block); | |
| memory.blocksMined++; | |
| await sleep(200); | |
| } | |
| } | |
| // Jump into hole | |
| bot.setControlState('forward', true); | |
| await sleep(200); | |
| bot.setControlState('jump', true); | |
| await sleep(500); | |
| clearAllControls(); | |
| // Cover top with dirt or cobblestone | |
| let dirt = bot.inventory.items().find(i => i.name === 'dirt' || i.name === 'cobblestone' || i.name === 'stone'); | |
| if (dirt) { | |
| await bot.equip(dirt, 'hand'); | |
| await sleep(400); | |
| const ref = bot.blockAt(new Vec3(bx, by - 3, bz)); | |
| if (ref) { | |
| try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} | |
| } | |
| } | |
| memory.shelterPos = { x: bx, y: by - 3, z: bz }; | |
| memory.save(); | |
| chat('hoyo de emergencia listo'); | |
| } catch (e) { | |
| console.error('[Reflex] Dig hole failed: ' + e.message); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BOOT SEQUENCE β Automated early game setup | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function bootSequence() { | |
| try { | |
| // Step 1: Wait for position | |
| console.log('[Boot] Step 1: Waiting for position...'); | |
| await sleep(2000); | |
| if (!hasValidPosition()) { | |
| console.log('[Boot] Step 1: No position yet, waiting more...'); | |
| await sleep(3000); | |
| } | |
| // Step 2: Find and chop a tree | |
| console.log('[Boot] Step 2: Finding and chopping tree...'); | |
| try { | |
| const logTypes = ['oak_log', 'birch_log', 'spruce_log', 'dark_oak_log', 'acacia_log', 'jungle_log']; | |
| let treeBlock = null; | |
| for (const log of logTypes) { | |
| treeBlock = bot.findBlock({ matching: (b) => b?.name === log, maxDistance: 48 }); | |
| if (treeBlock) break; | |
| } | |
| if (!treeBlock) { console.log('[Boot] Step 2: No tree found, skipping...'); } | |
| else { | |
| const moves = new Movements(bot); moves.canDig = false; | |
| bot.pathfinder.setMovements(moves); | |
| await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2)); | |
| await sleep(500); | |
| if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); memory.blocksMined++; } | |
| for (let dy = 1; dy <= 5; dy++) { | |
| const above = bot.blockAt(treeBlock.position.offset(0, dy, 0)); | |
| if (above && logTypes.some(l => above.name === l) && bot.canDigBlock(above)) { | |
| await bot.dig(above); memory.blocksMined++; | |
| } else break; | |
| } | |
| spatialMemory.record('oak_log', treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 'tree chopped'); | |
| console.log('[Boot] Step 2: Tree chopped!'); | |
| } | |
| } catch (e) { console.log('[Boot] Step 2 failed: ' + e.message); } | |
| // Step 3: Craft 4 oak_planks | |
| console.log('[Boot] Step 3: Crafting oak_planks...'); | |
| try { | |
| const planksItem = bot.registry.itemsByName['oak_planks']; | |
| if (planksItem) { | |
| const recipes = bot.recipesFor(planksItem.id, null, 1, null); | |
| if (recipes.length > 0) { | |
| await bot.craft(recipes[0], 4, null); | |
| memory.itemsCrafted += 4; | |
| console.log('[Boot] Step 3: Crafted 4 oak_planks!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 3 failed: ' + e.message); } | |
| // Step 4: Craft 4 sticks | |
| console.log('[Boot] Step 4: Crafting sticks...'); | |
| try { | |
| const stickItem = bot.registry.itemsByName['stick']; | |
| if (stickItem) { | |
| const recipes = bot.recipesFor(stickItem.id, null, 1, null); | |
| if (recipes.length > 0) { | |
| await bot.craft(recipes[0], 4, null); | |
| memory.itemsCrafted += 4; | |
| console.log('[Boot] Step 4: Crafted 4 sticks!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 4 failed: ' + e.message); } | |
| // Step 5: Craft 1 crafting_table | |
| console.log('[Boot] Step 5: Crafting crafting_table...'); | |
| try { | |
| const tableItem = bot.registry.itemsByName['crafting_table']; | |
| if (tableItem) { | |
| const recipes = bot.recipesFor(tableItem.id, null, 1, null); | |
| if (recipes.length > 0) { | |
| await bot.craft(recipes[0], 1, null); | |
| memory.itemsCrafted += 1; | |
| console.log('[Boot] Step 5: Crafted crafting_table!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 5 failed: ' + e.message); } | |
| // Step 6: Place crafting_table on ground | |
| console.log('[Boot] Step 6: Placing crafting_table...'); | |
| try { | |
| const tableInv = bot.inventory.items().find(i => i.name === 'crafting_table'); | |
| if (tableInv) { | |
| await bot.equip(tableInv, 'hand'); | |
| await sleep(400); | |
| let groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0)); | |
| if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(1, -1, 0)); | |
| if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(-1, -1, 0)); | |
| if (groundBelow && groundBelow.name !== 'air') { | |
| await bot.placeBlock(groundBelow, new Vec3(0, 1, 0)); | |
| await sleep(500); | |
| console.log('[Boot] Step 6: Crafting table placed!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 6 failed: ' + e.message); } | |
| // Step 7: Craft 1 wooden_pickaxe | |
| console.log('[Boot] Step 7: Crafting wooden_pickaxe...'); | |
| try { | |
| const pickItem = bot.registry.itemsByName['wooden_pickaxe']; | |
| const tableBlock = bot.findBlock({ matching: (b) => b?.name === 'crafting_table', maxDistance: 8 }); | |
| if (pickItem && tableBlock) { | |
| const recipes = bot.recipesFor(pickItem.id, null, 1, tableBlock); | |
| if (recipes.length > 0) { | |
| await bot.craft(recipes[0], 1, tableBlock); | |
| memory.itemsCrafted += 1; | |
| console.log('[Boot] Step 7: Crafted wooden_pickaxe!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 7 failed: ' + e.message); } | |
| // Step 8: Equip wooden_pickaxe | |
| console.log('[Boot] Step 8: Equipping wooden_pickaxe...'); | |
| try { | |
| const pickInv = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); | |
| if (pickInv) { | |
| await bot.equip(pickInv, 'hand'); | |
| await sleep(300); | |
| console.log('[Boot] Step 8: Wooden pickaxe equipped!'); | |
| } | |
| } catch (e) { console.log('[Boot] Step 8 failed: ' + e.message); } | |
| // Step 9: Mine 3 cobblestone | |
| console.log('[Boot] Step 9: Mining 3 cobblestone...'); | |
| try { | |
| for (let i = 0; i < 3; i++) { | |
| const stoneBlock = bot.findBlock({ matching: (b) => b?.name === 'stone', maxDistance: 16 }); | |
| if (stoneBlock && bot.canDigBlock(stoneBlock)) { | |
| const moves = new Movements(bot); moves.canDig = true; | |
| bot.pathfinder.setMovements(moves); | |
| await bot.pathfinder.setGoal(new goals.GoalNear(stoneBlock.position.x, stoneBlock.position.y, stoneBlock.position.z, 2)); | |
| await sleep(300); | |
| await bot.dig(stoneBlock); | |
| memory.blocksMined++; | |
| } | |
| } | |
| console.log('[Boot] Step 9: Mined cobblestone!'); | |
| } catch (e) { console.log('[Boot] Step 9 failed: ' + e.message); } | |
| // Step 10: Craft 1 stone_pickaxe | |
| console.log('[Boot] Step 10: Crafting stone_pickaxe...'); | |
| try { | |
| const stonePickItem = bot.registry.itemsByName['stone_pickaxe']; | |
| const tableBlock2 = bot.findBlock({ matching: (b) => b?.name === 'crafting_table', maxDistance: 8 }); | |
| if (stonePickItem && tableBlock2) { | |
| const recipes = bot.recipesFor(stonePickItem.id, null, 1, tableBlock2); | |
| if (recipes.length > 0) { | |
| await bot.craft(recipes[0], 1, tableBlock2); | |
| memory.itemsCrafted += 1; | |
| console.log('[Boot] Step 10: Crafted stone_pickaxe!'); | |
| } | |
| } | |
| } catch (e) { console.log('[Boot] Step 10 failed: ' + e.message); } | |
| // Step 11: Store shelter position | |
| console.log('[Boot] Step 11: Storing shelter position...'); | |
| memory.shelterPos = { x: Math.round(bot.entity.position.x), y: Math.round(bot.entity.position.y), z: Math.round(bot.entity.position.z) }; | |
| memory.save(); | |
| // Step 12: Mark boot complete and inform AI | |
| state._bootComplete = true; | |
| console.log('[Boot] Boot sequence complete!'); | |
| // Tell the AI what the boot accomplished | |
| const bootInventory = bot.inventory.items().map(i => i.name + 'x' + i.count).join(', '); | |
| addUserMessage('[BOOT COMPLETADO] Ya tienes automaticamente: ' + bootInventory + '. Tu refugio esta en ' + JSON.stringify(memory.shelterPos) + '. NO intentes craftear planks/sticks/crafting_table/pickaxe otra vez - YA LOS TIENES. Ahora tu objetivo es avanzar: minar mas piedra, buscar carbΓ³n, buscar hierro, construir mejor refugio, conseguir comida.'); | |
| // Save status immediately | |
| saveStatus(); | |
| memory.save(); | |
| benchmark.save(); | |
| } catch (e) { | |
| console.error('[Boot] Fatal error: ' + e.message); | |
| state._bootComplete = true; | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // MAIN LOOP β Enhanced with Voyager iterative retry + Critic + DEPS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function mainLoop() { | |
| console.log('[Loop] Starting OMEGA main loop (v32.0)...'); | |
| // Periodic consolidation (M2PA) | |
| let lastConsolidation = Date.now(); | |
| while (true) { | |
| if (!bot?.entity) { await sleep(5000); continue; } | |
| if (state._isRespawning) { await sleep(2000); continue; } | |
| if (!state._positionReady || !hasValidPosition()) { await sleep(2000); continue; } | |
| // v36: NO boot sequence check - AI starts immediately | |
| try { | |
| // 1. EMERGENCY REFLEXES | |
| const reflex = checkEmergencyReflexes(); | |
| if (reflex) { await sleep(1500); continue; } | |
| // 2. NaN check | |
| enforceValidPosition(); | |
| // 3. STUCK DETECTION (MineEvolve stagnation detection) | |
| if (state._lastPos) { | |
| const pos = bot.entity.position; | |
| const dx = Math.abs(pos.x - state._lastPos.x); | |
| const dz = Math.abs(pos.z - state._lastPos.z); | |
| if (dx < 0.3 && dz < 0.3 && state.activity !== 'idle') { | |
| state._stuckCounter++; | |
| if (state._stuckCounter > 5) { | |
| console.log('[Loop] Stuck detected, resetting'); | |
| clearAllControls(); | |
| try { bot.pathfinder?.setGoal(null); } catch {} | |
| state._stuckCounter = 0; | |
| // DEPS: Describe the problem | |
| addUserMessage('[SYSTEM] STUCK DETECTED. You are not moving. DEPS: 1)DESCRIBE: Bot is stuck. 2)EXPLAIN: Pathfinding may have failed or target unreachable. 3)PLAN: Try a completely different approach or move somewhere else. 4)SELECT: Walk manually with setControlState or pick a different target.'); | |
| await sleep(2000); | |
| } | |
| } else { | |
| state._stuckCounter = 0; | |
| } | |
| } | |
| state._lastPos = { x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z }; | |
| // 4. SNAPSHOT pre-action state (for Critic and Causal Tracker) | |
| const preInventory = snapshotInventory(); | |
| const preHp = Math.round(bot.health * 10) / 10; | |
| const preFood = Math.round(bot.food * 10) / 10; | |
| // 5. Set curriculum goal | |
| const goalProposal = curriculum.proposeGoal(''); | |
| memory.currentGoal = goalProposal.goal; | |
| // 6. THINK - GLM-4 decides (with iterative retry - Voyager) | |
| let decision = await zelinThink(); | |
| if (!decision) { | |
| await sleep(CONFIG.thinkInterval); | |
| continue; | |
| } | |
| state._thinkCount++; | |
| state.emotion = decision.emotion || 'chill'; | |
| if (decision.thought) { | |
| console.log('[AI] Thought: ' + (decision.thought || '').substring(0, 200)); | |
| } | |
| // 7. CHAT | |
| if (decision.chat && decision.chat.length > 0) { | |
| const now = Date.now(); | |
| if (now - state._lastChatTime > state._chatCooldown) { | |
| throttledChat(decision.chat); | |
| state._lastChatTime = now; | |
| } | |
| } | |
| // 8. EXECUTE CODE with iterative retry | |
| if (decision.code && decision.code.trim().length > 0) { | |
| state.activity = 'acting'; | |
| let retryCount = 0; | |
| let lastError = null; | |
| let codeToRun = decision.code; | |
| while (retryCount < CONFIG.maxRetries) { | |
| console.log('[Code] Attempt ' + (retryCount + 1) + '/' + CONFIG.maxRetries + ' (' + codeToRun.length + ' chars)'); | |
| const result = await executeCode(codeToRun); | |
| if (result.success) { | |
| console.log('[Code] OK (' + result.elapsed + 'ms): ' + result.output.substring(0, 150)); | |
| // v36: Auto-track metrics from inventory changes | |
| const postInventoryForMetrics = snapshotInventory(); | |
| trackMetricsFromInventory(preInventory, postInventoryForMetrics); | |
| // CRITIC: Verify goal was achieved (Voyager) | |
| const postInventory = snapshotInventory(); | |
| const verification = critic.verify(memory.currentGoal, preInventory, postInventory); | |
| if (verification.success) { | |
| addUserMessage('[RESULTADO] Exito! ' + verification.critique + ' (' + result.elapsed + 'ms)'); | |
| curriculum.markCompleted(memory.currentGoal); | |
| // EPISODIC: Store successful experience (M2PA) | |
| const scene = 'HP:' + preHp + ' Food:' + preFood + ' Items:' + (preInventory || '').substring(0, 100); | |
| episodicMemory.store(memory.currentGoal || 'unknown', scene, codeToRun, verification.critique, true); | |
| // CAUSAL: Track actionβresult (ADAM) | |
| causalTracker.record((codeToRun || '').substring(0, 100), preInventory || '', postInventory || '', verification.critique || ''); | |
| // SKILL: Auto-save if code was complex enough | |
| if (codeToRun.length > 100 && retryCount === 0) { | |
| const skillName = (memory.currentGoal || 'unknown').replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30); | |
| skillLibrary.save(skillName, codeToRun, memory.currentGoal, codeToRun.length > 300 ? 'compositional' : 'primitive'); | |
| } | |
| skillLibrary.recordUsage(decision.saveSkill?.name || 'last', true); | |
| break; // Success, exit retry loop | |
| } else { | |
| // Code ran but goal wasn't achieved | |
| console.log('[Critic] Goal not achieved: ' + verification.critique); | |
| addUserMessage('[CRITICO] Codigo ejecuto pero goal no alcanzado: ' + verification.critique + '\nDEPS: Describe que paso, Explica porque, Plan nuevo, Selecciona accion diferente.'); | |
| retryCount++; | |
| if (retryCount < CONFIG.maxRetries) { | |
| // Re-think with critic feedback | |
| decision = await zelinThink(); | |
| if (decision?.code) codeToRun = decision.code; | |
| else break; | |
| } | |
| } | |
| } else { | |
| // Code failed with error | |
| lastError = result.error; | |
| console.error('[Code] FAILED (' + result.elapsed + 'ms): ' + result.error); | |
| addUserMessage('[ERROR] Intento ' + (retryCount + 1) + '/' + CONFIG.maxRetries + ': ' + (result.error || 'unknown') + '\nDEPS: 1)DESCRIBE: ' + (result.error || 'unknown').substring(0, 100) + ' 2)EXPLICA: Porque fallo? 3)PLAN: Estrategia diferente. 4)SELECCIONA: Que hacer diferente?'); | |
| // Store as remedy (MineEvolve) | |
| remedySystem.store( | |
| memory.currentGoal || 'unknown', | |
| 'code_error', | |
| (result.error || 'unknown').substring(0, 100), | |
| 'check syntax, use await, check item names, try different approach', | |
| 'execution' | |
| ); | |
| skillLibrary.recordUsage(decision.saveSkill?.name || 'last', false); | |
| retryCount++; | |
| if (retryCount < CONFIG.maxRetries) { | |
| decision = await zelinThink(); | |
| if (decision?.code) codeToRun = decision.code; | |
| else break; | |
| } | |
| } | |
| } | |
| if (retryCount >= CONFIG.maxRetries) { | |
| curriculum.markFailed(memory.currentGoal, lastError || 'max retries reached'); | |
| addUserMessage('[SISTEMA] Goal fallido despues de ' + CONFIG.maxRetries + ' intentos. Cambiando de objetivo.'); | |
| } | |
| benchmark.checkMilestones(); | |
| state.activity = 'idle'; | |
| } | |
| // 9. SAVE SKILL (explicit) | |
| if (decision.saveSkill && decision.saveSkill.name && decision.saveSkill.code) { | |
| skillLibrary.save(decision.saveSkill.name, decision.saveSkill.code, decision.saveSkill.description || ''); | |
| } | |
| // 10. Periodic consolidation & saves (every 3 cycles, not 5) | |
| if (state._thinkCount % 3 === 0 || state._thinkCount === 1) { | |
| saveStatus(); | |
| memory.save(); | |
| benchmark.save(); | |
| } | |
| // M2PA periodic consolidation (every 5 minutes) | |
| if (Date.now() - lastConsolidation > 300000) { | |
| episodicMemory.periodicConsolidation(); | |
| lastConsolidation = Date.now(); | |
| } | |
| await sleep(CONFIG.thinkInterval + Math.random() * 1500); | |
| } catch (e) { | |
| console.error('[Loop] Error:', e.message); | |
| await sleep(5000); | |
| } | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CHAT | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function addChatMessage(sender, text) { | |
| const time = new Date().toLocaleTimeString('es-MX', {hour:'2-digit',minute:'2-digit'}); | |
| state.chatLog.push({time, sender, text}); | |
| if (state.chatLog.length > 100) state.chatLog = state.chatLog.slice(-100); | |
| state._recentChat.push({sender, text, time: Date.now()}); | |
| if (state._recentChat.length > 15) state._recentChat.shift(); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // STATUS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function saveStatus() { | |
| try { | |
| const pos = bot?.entity?.position; | |
| const safePos = pos && isFinite(pos.x) ? {x:Math.round(pos.x*10)/10,y:Math.round(pos.y*10)/10,z:Math.round(pos.z*10)/10} : (state._lastGoodPos ?? {x:0,y:0,z:0}); | |
| const skillsCount = Object.keys(skillLibrary.skills).length; | |
| writeFileSync(CONFIG.statusFile, JSON.stringify({ | |
| online: !!bot?.entity, | |
| version: '36.0', | |
| architecture: 'LIBRE (100% AI-driven, no predefined steps)', | |
| username: CONFIG.username, | |
| position: safePos, | |
| health: Math.round((bot?.health ?? 0)*10)/10, | |
| food: Math.round((bot?.food ?? 0)*10)/10, | |
| activity: state.activity, | |
| emotion: state.emotion, | |
| chat_log: state.chatLog.slice(-50), | |
| world_time: bot?.time?.timeOfDay ?? 0, | |
| connected_since: state.connectedSince, | |
| skills_count: skillsCount, | |
| episodic_count: episodicMemory.episodes.length, | |
| remedies_count: remedySystem.remedies.length, | |
| spatial_count: spatialMemory.places.length, | |
| causal_count: causalTracker.causals.length, | |
| deaths: memory.deaths, | |
| kills: memory.kills, | |
| blocks_mined: memory.blocksMined, | |
| items_crafted: memory.itemsCrafted, | |
| current_goal: memory.currentGoal, | |
| completed_goals: memory.completedGoals.length, | |
| failed_goals: memory.failedGoals.length, | |
| benchmark: benchmark.getReport(), | |
| think_count: state._thinkCount, | |
| }, null, 2)); | |
| } catch {} | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // DISCORD BOT β Gateway WebSocket + REST API | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| let discordReady = false; | |
| let _discordLastSeq = null; | |
| let _discordWs = null; | |
| let _discordHeartbeatInterval = null; | |
| let _discordSessionId = null; | |
| let _discordChannelId = null; | |
| let _discordReconnectDelay = 1000; | |
| let _discordStatusPostTime = 0; | |
| let _drowningEscape = false; | |
| async function discordFindChannel() { | |
| if (_discordChannelId) return _discordChannelId; | |
| try { | |
| const channelsRes = await fetch('https://discord.com/api/v10/guilds/' + CONFIG.discord.guildId + '/channels', { | |
| headers: { 'Authorization': 'Bot ' + CONFIG.discord.token }, | |
| signal: AbortSignal.timeout(10000), | |
| }); | |
| if (!channelsRes.ok) return null; | |
| const channels = await channelsRes.json(); | |
| const priorities = ['test-bot', 'comandos', 'zelin-status', 'zelin', 'general']; | |
| for (const p of priorities) { | |
| const found = channels.find(c => c.name.includes(p) && c.type === 0); | |
| if (found) { _discordChannelId = found.id; return found.id; } | |
| } | |
| const fallback = channels.find(c => c.type === 0); | |
| if (fallback) { _discordChannelId = fallback.id; return fallback.id; } | |
| } catch (e) { console.error('[Discord] FindChannel error: ' + e.message); } | |
| return null; | |
| } | |
| async function discordPostStatus() { | |
| const channelId = await discordFindChannel(); | |
| if (!channelId) return; | |
| const pos = bot?.entity?.position; | |
| const posStr = hasValidPosition() ? Math.round(pos.x) + ',' + Math.round(pos.y) + ',' + Math.round(pos.z) : 'unknown'; | |
| const survMin = Math.floor(benchmark.getSurvivalTime() / 60000); | |
| const embed = { | |
| embeds: [{ | |
| title: CONFIG.profile.name + ' v36.0 LIBRE', | |
| color: (bot?.health ?? 20) > 10 ? 0x00ff00 : 0xff0000, | |
| fields: [ | |
| { name: 'Pos', value: posStr, inline: true }, | |
| { name: 'HP', value: Math.round(bot?.health ?? 0) + '/20', inline: true }, | |
| { name: 'Food', value: Math.round(bot?.food ?? 0) + '/20', inline: true }, | |
| { name: 'Goal', value: memory.currentGoal || 'idle', inline: true }, | |
| { name: 'Survival', value: survMin + 'min', inline: true }, | |
| { name: 'Deaths', value: '' + memory.deaths, inline: true }, | |
| { name: 'Progress', value: memory.blocksMined + '/' + memory.itemsCrafted, inline: true }, | |
| { name: 'Milestones', value: Object.values(benchmark.milestones).filter(v => v).length + '/20', inline: true }, | |
| { name: 'Episodes', value: '' + episodicMemory.episodes.length, inline: true }, | |
| { name: 'Remedies', value: '' + remedySystem.remedies.length, inline: true }, | |
| ], | |
| timestamp: new Date().toISOString(), | |
| }], | |
| }; | |
| try { | |
| await fetch('https://discord.com/api/v10/channels/' + channelId + '/messages', { | |
| method: 'POST', | |
| headers: { 'Authorization': 'Bot ' + CONFIG.discord.token, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(embed), | |
| signal: AbortSignal.timeout(10000), | |
| }); | |
| } catch {} | |
| } | |
| async function initDiscord() { | |
| // Simplified Discord Gateway - same as v31 | |
| const token = CONFIG.discord.token; | |
| try { | |
| const gatewayRes = await fetch('https://discord.com/api/v10/gateway/bot', { | |
| headers: { 'Authorization': 'Bot ' + token }, | |
| signal: AbortSignal.timeout(10000), | |
| }); | |
| if (!gatewayRes.ok) { console.error('[Discord] Gateway fetch failed'); return; } | |
| const gatewayData = await gatewayRes.json(); | |
| const wsUrl = gatewayData.url + '?v=10&encoding=json'; | |
| _discordWs = new WebSocket(wsUrl); | |
| _discordWs.on('message', (data) => { | |
| try { | |
| const payload = JSON.parse(data.toString()); | |
| if (payload.op === 10) { | |
| const interval = payload.d.heartbeat_interval; | |
| _discordLastSeq = null; | |
| const identify = { op: 2, d: { token, intents: 0, properties: { os: 'linux', browser: 'zelin', device: 'zelin' } } }; | |
| _discordWs.send(JSON.stringify(identify)); | |
| if (_discordHeartbeatInterval) clearInterval(_discordHeartbeatInterval); | |
| _discordHeartbeatInterval = setInterval(() => { | |
| if (_discordWs?.readyState === WebSocket.OPEN) { | |
| _discordWs.send(JSON.stringify({ op: 1, d: _discordLastSeq })); | |
| } | |
| }, interval); | |
| } else if (payload.op === 0) { | |
| _discordLastSeq = payload.s; | |
| if (payload.t === 'READY') { | |
| discordReady = true; | |
| _discordSessionId = payload.d.session_id; | |
| console.log('[Discord] Connected as ' + (payload.d.user?.username || 'unknown')); | |
| } | |
| } else if (payload.op === 11) { | |
| // Heartbeat ACK | |
| } | |
| } catch {} | |
| }); | |
| _discordWs.on('close', () => { | |
| discordReady = false; | |
| console.log('[Discord] Disconnected, reconnecting in ' + _discordReconnectDelay + 'ms'); | |
| setTimeout(initDiscord, _discordReconnectDelay); | |
| _discordReconnectDelay = Math.min(_discordReconnectDelay * 2, 60000); | |
| }); | |
| _discordWs.on('error', (err) => { console.error('[Discord] WS error: ' + err.message); }); | |
| } catch (e) { console.error('[Discord] Init error: ' + e.message); } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // HTTP STATUS SERVER β For HF Spaces health check | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function startStatusServer() { | |
| const server = http.createServer((req, res) => { | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| status: bot?.entity ? 'alive' : 'connecting', | |
| version: '36.0', | |
| health: Math.round((bot?.health ?? 0)*10)/10, | |
| food: Math.round((bot?.food ?? 0)*10)/10, | |
| activity: state.activity, | |
| goal: memory.currentGoal, | |
| milestones: Object.values(benchmark.milestones).filter(v => v).length + '/20', | |
| })); | |
| }); | |
| try { server.listen(7861); } catch {} | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // BOT CREATION + EVENTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function createBot() { | |
| console.log('[Bot] Creating bot for ' + CONFIG.host + ':' + CONFIG.port + '...'); | |
| bot = mineflayer.createBot({ | |
| host: CONFIG.host, | |
| port: CONFIG.port, | |
| username: CONFIG.username, | |
| version: CONFIG.version, | |
| auth: CONFIG.auth, | |
| }); | |
| bot.loadPlugin(pathfinder); | |
| bot.loadPlugin(pvp); | |
| bot.loadPlugin(autoEat); | |
| bot.loadPlugin(armorManager); | |
| bot.loadPlugin(collectBlock); | |
| Vec3 = bot.vec3 ? bot.vec3 : (bot.entity?.position?.constructor ?? null); | |
| if (Vec3) patchVec3RejectNaN(bot.entity.position); | |
| // EVENTS | |
| bot.on('login', () => { | |
| console.log('[Bot] Logged in! Version: ' + CONFIG.version); | |
| state.connectedSince = new Date().toISOString(); | |
| Vec3 = bot.entity.position.constructor; | |
| patchVec3RejectNaN(bot.entity.position); | |
| state._positionReady = false; | |
| // Wait for position | |
| setTimeout(() => { | |
| if (hasValidPosition()) { | |
| state._positionReady = true; | |
| state._lastGoodPos = { x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z }; | |
| memory.home = { x: Math.round(bot.entity.position.x), y: Math.round(bot.entity.position.y), z: Math.round(bot.entity.position.z) }; | |
| memory.save(); | |
| spatialMemory.record('home', Math.round(bot.entity.position.x), Math.round(bot.entity.position.y), Math.round(bot.entity.position.z), 'spawn point'); | |
| console.log('[Bot] Position ready at ' + Math.round(bot.entity.position.x) + ',' + Math.round(bot.entity.position.y) + ',' + Math.round(bot.entity.position.z)); | |
| } | |
| }, 5000); | |
| // v36: NO boot sequence - AI decides everything from first spawn | |
| // Send initial AI prompt after position is ready | |
| setTimeout(() => { | |
| if (hasValidPosition()) { | |
| addSystemMessage(buildSystemPrompt()); | |
| addUserMessage('[PRIMER SPAWN] Acabas de nacer en Minecraft. No tienes NADA. Tu PRIMERA accion debe ser: encontrar un arbol cercano y cortarlo con tu mano (bot.dig). Luego craft oak_planks, sticks, crafting_table, coloca la mesa, y craft wooden_pickaxe. Escribe codigo JavaScript para actuar AHORA. Usa los ejemplos de codigo del sistema.'); | |
| state._bootComplete = true; | |
| } | |
| }, 5000); | |
| }); | |
| bot.on('spawn', () => { | |
| console.log('[Bot] Spawned/Respawned'); | |
| state._isRespawning = false; | |
| }); | |
| bot.on('death', () => { | |
| console.log('[Bot] DIED! Total deaths: ' + (memory.deaths + 1)); | |
| memory.deaths++; | |
| if (hasValidPosition()) { | |
| const pos = bot.entity.position; | |
| memory.deathPoints.push({ x: Math.round(pos.x), y: Math.round(pos.y), z: Math.round(pos.z), time: Date.now() }); | |
| spatialMemory.record('death', Math.round(pos.x), Math.round(pos.y), Math.round(pos.z), 'died here'); | |
| } | |
| benchmark.recordDeath(); | |
| // Mark current goal as failed | |
| if (memory.currentGoal) { | |
| curriculum.markFailed(memory.currentGoal, 'died during execution'); | |
| } | |
| softResetConversation(); | |
| addUserMessage('[EVENTO] MORISTE! Aprende de esto. Que paso? Como evitarlo la proxima vez? Prioriza seguridad: huye de mobs, busca refugio de noche.'); | |
| state._isRespawning = true; | |
| memory.save(); | |
| }); | |
| bot.on('respawn', () => { | |
| console.log('[Bot] Respawned - seeking safety immediately'); | |
| state._isRespawning = false; | |
| state._positionReady = false; | |
| // v36: After respawn, immediately flee if hostiles nearby | |
| setTimeout(() => { | |
| state._positionReady = true; | |
| if (isHostileNearby()) { | |
| console.log('[Bot] Hostiles near spawn - fleeing!'); | |
| emergencyFlee(); | |
| } | |
| }, 3000); | |
| }); | |
| bot.on('health', () => { | |
| if (bot.health < 10) { | |
| state.emotion = 'scared'; | |
| } | |
| }); | |
| bot.on('entityHurt', (entity) => { | |
| if (entity === bot.entity && bot.health < 10) { | |
| addUserMessage('[EVENTO] Te lastimaron! HP: ' + Math.round(bot.health) + '/20 - Considera huir o comer'); | |
| state.emotion = 'scared'; | |
| } | |
| }); | |
| bot.on('chat', (username, message) => { | |
| if (username === CONFIG.username) return; | |
| addChatMessage(username, message); | |
| if (message.toLowerCase().includes(CONFIG.profile.name.toLowerCase())) { | |
| addUserMessage('[CHAT] ' + username + ' te dijo: ' + message); | |
| } | |
| }); | |
| bot.on('kicked', (reason) => { | |
| console.error('[Bot] Kicked: ' + reason); | |
| state._consecutiveKicks++; | |
| if (state._consecutiveKicks > 5) { | |
| console.error('[Bot] Too many kicks, giving up'); | |
| return; | |
| } | |
| setTimeout(createBot, state._kickReconnectDelay); | |
| state._kickReconnectDelay = Math.min(state._kickReconnectDelay * 1.5, 60000); | |
| }); | |
| bot.on('error', (err) => { | |
| console.error('[Bot] Error: ' + err.message); | |
| }); | |
| bot.on('end', (reason) => { | |
| console.log('[Bot] Disconnected: ' + reason); | |
| state._consecutiveKicks++; | |
| if (state._consecutiveKicks > 10) return; | |
| setTimeout(createBot, state._kickReconnectDelay); | |
| state._kickReconnectDelay = Math.min(state._kickReconnectDelay * 1.5, 60000); | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // INITIALIZATION | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function main() { | |
| console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ'); | |
| console.log('β Zelin v36.0 "LIBRE" β'); | |
| console.log('β 100% AI-DRIVEN - No predefined steps β'); | |
| console.log('β GLM-4 API: 120s timeout + 3 retries β'); | |
| console.log('β Active reflexes: flee, swim, eat β'); | |
| console.log('β Auto-tracking metrics from inventory β'); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ'); | |
| // Ensure log directory | |
| try { mkdirSync('/app/logs', { recursive: true }); } catch {} | |
| // Load all memory systems | |
| memory.load(); | |
| skillLibrary.load(); | |
| episodicMemory.load(); | |
| remedySystem.load(); | |
| spatialMemory.load(); | |
| causalTracker.load(); | |
| // Seed skills | |
| seedSkills(); | |
| // Start status server | |
| startStatusServer(); | |
| // Create bot | |
| createBot(); | |
| // Wait for bot to be ready | |
| let readyCheck = 0; | |
| while (!state._positionReady && readyCheck < 30) { | |
| await sleep(2000); | |
| readyCheck++; | |
| } | |
| if (state._positionReady) { | |
| console.log('[Main] Bot ready! Starting main loop...'); | |
| } else { | |
| console.log('[Main] Position not ready yet, starting loop anyway...'); | |
| } | |
| // Start Discord | |
| initDiscord(); | |
| // Discord status posting every 2 minutes | |
| setInterval(() => { | |
| if (bot?.entity && Date.now() - _discordStatusPostTime > 120000) { | |
| discordPostStatus(); | |
| _discordStatusPostTime = Date.now(); | |
| } | |
| }, 60000); | |
| // Run main loop | |
| await mainLoop(); | |
| } | |
| main().catch(err => { | |
| console.error('[Main] Fatal error: ' + err.message); | |
| process.exit(1); | |
| }); | |