Skip to content

Commit d312105

Browse files
DavidLiedleclaude
andcommitted
Feature 3: Genome visualization — click-to-inspect in browser
Click any node in the mesh visualizer to open a genome inspector panel showing: unit ID, fitness, energy/efficiency, tasks, stack, SOL-* antibodies, user-defined words, learned words. Panel updates every 2 seconds while open. Includes a command input that executes Forth on the selected unit (not just the REPL unit). Selected node highlighted with white outline. Styled with the existing dark terminal aesthetic: semi-transparent #0d0d0d background, #00ff88 text, monospace font. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0ed34b4 commit d312105

1 file changed

Lines changed: 68 additions & 2 deletions

File tree

web/index.html

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
#prompt { color: #00ff88; margin-right: 8px; line-height: 24px; }
4545
#input { flex: 1; background: transparent; border: none; outline: none; color: #00ff88; font-family: inherit; font-size: 14px; caret-color: #00ff88; }
4646
.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; }
4752
</style>
4853
</head>
4954
<body>
@@ -60,7 +65,7 @@
6065
<a href="https://crates.io/crates/unit">crates.io</a>
6166
</div>
6267
</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>
6469
<div id="chatter"></div>
6570
<div id="terminal" onclick="document.getElementById('input').focus()"></div>
6671
<div id="tutorial-bar"></div>
@@ -231,7 +236,10 @@
231236
ctx.beginPath(); ctx.arc(n.x,n.y,pr+8,0,Math.PI*2); ctx.fillStyle = gc; ctx.fill();
232237
ctx.beginPath(); ctx.arc(n.x,n.y,pr,0,Math.PI*2);
233238
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();
235243
ctx.fillStyle = '#888'; ctx.font = '9px monospace'; ctx.textAlign = 'center';
236244
ctx.fillText(n.label, n.x, n.y+pr+12);
237245
if (n.fitness > 0) ctx.fillText('f:'+n.fitness, n.x, n.y+pr+21);
@@ -264,6 +272,64 @@
264272
requestAnimationFrame(vizTick);
265273
window.addEventListener('resize', () => { if (vizMode > 0) resizeCanvas(); });
266274

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">&gt; ${cmd}<br>${result.replace(/\n/g,'<br>')}</span>`;
330+
genomeCmd.value = '';
331+
});
332+
267333
// =========================================================================
268334
// Autonomous Behavior — units come alive
269335
// =========================================================================

0 commit comments

Comments
 (0)