|
44 | 44 | #prompt { color: #00ff88; margin-right: 8px; line-height: 24px; } |
45 | 45 | #input { flex: 1; background: transparent; border: none; outline: none; color: #00ff88; font-family: inherit; font-size: 14px; caret-color: #00ff88; } |
46 | 46 | .output { color: #bbb; } .error { color: #ff4444; } .info { color: #555; } .mesh-event { color: #886; } .tutorial-msg { color: #3a6a3a; font-style: italic; } |
| 47 | + #genome-panel { display:none; position:absolute; top:40px; right:8px; width:280px; max-height:50vh; overflow-y:auto; background:#0d0d0dee; border:1px solid #333; border-radius:4px; padding:8px 10px; font-size:11px; color:#00ff88; z-index:10; } |
| 48 | + #genome-panel .gp-title { color:#888; font-size:10px; margin-top:6px; } #genome-panel .gp-val { color:#bbb; } |
| 49 | + #genome-panel .gp-close { position:absolute; top:4px; right:8px; cursor:pointer; color:#666; font-size:14px; } |
| 50 | + #genome-panel .gp-close:hover { color:#ff4444; } |
| 51 | + #genome-cmd { width:100%; background:#111; border:1px solid #333; color:#00ff88; font-family:monospace; font-size:11px; padding:2px 4px; margin-top:6px; outline:none; } |
47 | 52 | </style> |
48 | 53 | </head> |
49 | 54 | <body> |
|
60 | 65 | <a href="https://crates.io/crates/unit">crates.io</a> |
61 | 66 | </div> |
62 | 67 | </div> |
63 | | -<div id="viz"><canvas id="viz-canvas"></canvas><span id="viz-label"></span></div> |
| 68 | +<div id="viz" style="position:relative"><canvas id="viz-canvas"></canvas><span id="viz-label"></span><div id="genome-panel"><span class="gp-close" onclick="closeGenome()">x</span><div id="genome-content"></div><input id="genome-cmd" placeholder="run command on this unit..." /></div></div> |
64 | 69 | <div id="chatter"></div> |
65 | 70 | <div id="terminal" onclick="document.getElementById('input').focus()"></div> |
66 | 71 | <div id="tutorial-bar"></div> |
|
231 | 236 | ctx.beginPath(); ctx.arc(n.x,n.y,pr+8,0,Math.PI*2); ctx.fillStyle = gc; ctx.fill(); |
232 | 237 | ctx.beginPath(); ctx.arc(n.x,n.y,pr,0,Math.PI*2); |
233 | 238 | ctx.fillStyle = n.color + Math.floor(n.alpha*200).toString(16).padStart(2,'0'); |
234 | | - ctx.fill(); ctx.strokeStyle = n.color+'44'; ctx.lineWidth=1; ctx.stroke(); |
| 239 | + ctx.fill(); |
| 240 | + if (n.id === selectedNodeId) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; } |
| 241 | + else { ctx.strokeStyle = n.color+'44'; ctx.lineWidth = 1; } |
| 242 | + ctx.stroke(); |
235 | 243 | ctx.fillStyle = '#888'; ctx.font = '9px monospace'; ctx.textAlign = 'center'; |
236 | 244 | ctx.fillText(n.label, n.x, n.y+pr+12); |
237 | 245 | if (n.fitness > 0) ctx.fillText('f:'+n.fitness, n.x, n.y+pr+21); |
|
264 | 272 | requestAnimationFrame(vizTick); |
265 | 273 | window.addEventListener('resize', () => { if (vizMode > 0) resizeCanvas(); }); |
266 | 274 |
|
| 275 | +// Genome inspector — click a node to inspect it. |
| 276 | +let selectedNodeId = null, genomeInterval = null; |
| 277 | +const genomePanel = document.getElementById('genome-panel'); |
| 278 | +const genomeContent = document.getElementById('genome-content'); |
| 279 | +const genomeCmd = document.getElementById('genome-cmd'); |
| 280 | + |
| 281 | +vizCanvas.addEventListener('click', (e) => { |
| 282 | + const rect = vizCanvas.getBoundingClientRect(); |
| 283 | + const dpr = devicePixelRatio || 1; |
| 284 | + const mx = (e.clientX - rect.left) * dpr, my = (e.clientY - rect.top) * dpr; |
| 285 | + let hit = null; |
| 286 | + for (const n of vizNodes) { |
| 287 | + const dx = n.x * dpr - mx, dy = n.y * dpr - my; |
| 288 | + if (Math.sqrt(dx*dx + dy*dy) < 25 * dpr) { hit = n; break; } |
| 289 | + } |
| 290 | + if (hit) { selectedNodeId = hit.id; openGenome(); } |
| 291 | + else closeGenome(); |
| 292 | +}); |
| 293 | + |
| 294 | +function openGenome() { |
| 295 | + genomePanel.style.display = 'block'; |
| 296 | + updateGenome(); |
| 297 | + if (genomeInterval) clearInterval(genomeInterval); |
| 298 | + genomeInterval = setInterval(updateGenome, 2000); |
| 299 | +} |
| 300 | +function closeGenome() { |
| 301 | + genomePanel.style.display = 'none'; |
| 302 | + selectedNodeId = null; |
| 303 | + if (genomeInterval) { clearInterval(genomeInterval); genomeInterval = null; } |
| 304 | +} |
| 305 | +function updateGenome() { |
| 306 | + if (!mesh || !selectedNodeId) return; |
| 307 | + const unit = mesh.units.find(u => u.id === selectedNodeId); |
| 308 | + if (!unit) { closeGenome(); return; } |
| 309 | + const eff = unit.energySpent > 0 ? (unit.energyEarned / unit.energySpent).toFixed(1) : '0.0'; |
| 310 | + const solWords = (unit.vm.eval('WORDS') || '').split(/\s+/).filter(w => w.startsWith('SOL-')); |
| 311 | + const stackOut = unit.vm.eval('.S').trim(); |
| 312 | + let html = `<b>#${unit.id}</b><br>`; |
| 313 | + html += `<span class="gp-title">fitness</span> <span class="gp-val">${unit.fitness}</span><br>`; |
| 314 | + html += `<span class="gp-title">energy</span> <span class="gp-val">${unit.energy}/${unit.energyMax} (eff: ${eff})</span><br>`; |
| 315 | + html += `<span class="gp-title">tasks</span> <span class="gp-val">${unit.tasksCompleted}</span><br>`; |
| 316 | + html += `<span class="gp-title">stack</span> <span class="gp-val">${stackOut || '(empty)'}</span><br>`; |
| 317 | + if (solWords.length) html += `<span class="gp-title">antibodies</span> <span class="gp-val">${solWords.join(' ')}</span><br>`; |
| 318 | + if (unit.userWords.length) html += `<span class="gp-title">user words</span> <span class="gp-val">${unit.userWords.length} defined</span><br>`; |
| 319 | + if (unit.learned.length) html += `<span class="gp-title">learned</span> <span class="gp-val">${unit.learned.join(' ')}</span><br>`; |
| 320 | + genomeContent.innerHTML = html; |
| 321 | +} |
| 322 | +genomeCmd.addEventListener('keydown', (e) => { |
| 323 | + if (e.key !== 'Enter') return; |
| 324 | + const cmd = genomeCmd.value.trim(); |
| 325 | + if (!cmd || !selectedNodeId || !mesh) return; |
| 326 | + const unit = mesh.units.find(u => u.id === selectedNodeId); |
| 327 | + if (!unit) return; |
| 328 | + const result = unit.vm.eval(cmd); |
| 329 | + if (result) genomeContent.innerHTML += `<span class="gp-val">> ${cmd}<br>${result.replace(/\n/g,'<br>')}</span>`; |
| 330 | + genomeCmd.value = ''; |
| 331 | +}); |
| 332 | + |
267 | 333 | // ========================================================================= |
268 | 334 | // Autonomous Behavior — units come alive |
269 | 335 | // ========================================================================= |
|
0 commit comments