[claude] Tower Log — narrative event feed (#11) #25
121
app.js
121
app.js
@@ -115,6 +115,9 @@ function init() {
|
||||
setTimeout(() => { document.getElementById('loading-screen').remove(); }, 900);
|
||||
}, 600);
|
||||
|
||||
// Tower Log
|
||||
initTowerLog();
|
||||
|
||||
// Start loop
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
@@ -872,6 +875,124 @@ function addChatMessage(type, text) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
// ═══ TOWER LOG ═══
|
||||
const TOWER_LOG = {
|
||||
maxEntries: 60,
|
||||
open: true,
|
||||
seedEvents: [
|
||||
{ type: 'system', msg: 'Tower Log initialized — monitoring all systems' },
|
||||
{ type: 'commit', msg: '⎇ feat: add procedural nebula skybox shader' },
|
||||
{ type: 'commit', msg: '⎇ fix: portal torus rotation clamping' },
|
||||
{ type: 'agent', msg: '🤖 Kimi completed: nexus-v1 layout pass' },
|
||||
{ type: 'training', msg: '⚙ Training cycle #14892 completed — loss 0.0031' },
|
||||
{ type: 'merge', msg: '⬡ PR #47 merged: batcave terminal panels' },
|
||||
{ type: 'visitor', msg: '👁 Visitor arrived: Alexander Whitestone' },
|
||||
{ type: 'commit', msg: '⎇ chore: update README with architecture notes' },
|
||||
{ type: 'agent', msg: '🤖 Claude Code completed: floor grid optimization' },
|
||||
{ type: 'commit', msg: '⎇ feat: ambient crystal formations' },
|
||||
{ type: 'training', msg: '⚙ Thought cycle #14893 — priority rebalance' },
|
||||
{ type: 'merge', msg: '⬡ PR #48 merged: post-processing bloom pass' },
|
||||
],
|
||||
liveTemplates: {
|
||||
commit: [
|
||||
'⎇ fix: resolve shader uniform timing drift',
|
||||
'⎇ feat: add runestone float animation',
|
||||
'⎇ refactor: extract portal swirl to helper',
|
||||
'⎇ chore: bump three.js to 0.183',
|
||||
'⎇ feat: dust particle emitter tuning',
|
||||
'⎇ fix: memory leak in canvas texture disposal',
|
||||
'⎇ feat: nexus core emissive pulse',
|
||||
],
|
||||
merge: [
|
||||
'⬡ PR #49 merged: harness agent loop v2',
|
||||
'⬡ PR #50 merged: world navigation scaffolding',
|
||||
'⬡ PR #51 merged: WebSocket chat integration',
|
||||
'⬡ PR #52 merged: tower log narrative feed',
|
||||
],
|
||||
agent: [
|
||||
'🤖 Hermes: routed 7 new tasks to queue',
|
||||
'🤖 Kimi completed: issue triage pass #204',
|
||||
'🤖 Claude Code: opened PR for #11 tower log',
|
||||
'🤖 Gemini: woke from standby — code review cycle',
|
||||
'🤖 Perplexity: world snapshot saved',
|
||||
],
|
||||
visitor: [
|
||||
'👁 Visitor arrived: Alexander Whitestone',
|
||||
'👁 Visitor arrived: anonymous scout',
|
||||
'👁 Observer detected in sector 7',
|
||||
],
|
||||
training: [
|
||||
'⚙ Training cycle complete — model saved',
|
||||
'⚙ Thought stream checkpoint written',
|
||||
'⚙ Memory consolidation pass finished',
|
||||
'⚙ Attention layer tuning: Δ0.0004',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function towerLogTimestamp() {
|
||||
const d = new Date();
|
||||
return d.toTimeString().slice(0, 8);
|
||||
}
|
||||
|
||||
function addTowerEntry(type, msg) {
|
||||
const container = document.getElementById('tower-log-entries');
|
||||
if (!container) return;
|
||||
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `tower-log-entry type-${type}`;
|
||||
entry.innerHTML =
|
||||
`<span class="tower-log-ts">${towerLogTimestamp()}</span>` +
|
||||
`<span class="tower-log-body">${msg}</span>`;
|
||||
|
||||
// Prepend so newest is at top (column-reverse layout handles visual order)
|
||||
container.appendChild(entry);
|
||||
|
||||
// Trim old entries
|
||||
while (container.children.length > TOWER_LOG.maxEntries) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function initTowerLog() {
|
||||
// Seed with historical events (oldest first)
|
||||
TOWER_LOG.seedEvents.forEach(e => addTowerEntry(e.type, e.msg));
|
||||
|
||||
// Toggle collapse
|
||||
document.getElementById('tower-log-toggle')?.addEventListener('click', () => {
|
||||
TOWER_LOG.open = !TOWER_LOG.open;
|
||||
document.getElementById('tower-log').classList.toggle('collapsed', !TOWER_LOG.open);
|
||||
});
|
||||
|
||||
// Schedule live events
|
||||
scheduleLiveEvents();
|
||||
}
|
||||
|
||||
function scheduleLiveEvents() {
|
||||
const types = Object.keys(TOWER_LOG.liveTemplates);
|
||||
// Weights: commits most frequent, visitors rare
|
||||
const weighted = [
|
||||
...Array(5).fill('commit'),
|
||||
...Array(2).fill('agent'),
|
||||
...Array(2).fill('training'),
|
||||
...Array(1).fill('merge'),
|
||||
...Array(1).fill('visitor'),
|
||||
];
|
||||
|
||||
function fireNext() {
|
||||
const type = weighted[Math.floor(Math.random() * weighted.length)];
|
||||
const msgs = TOWER_LOG.liveTemplates[type];
|
||||
const msg = msgs[Math.floor(Math.random() * msgs.length)];
|
||||
addTowerEntry(type, msg);
|
||||
// Next event in 8–25 seconds
|
||||
const delay = 8000 + Math.random() * 17000;
|
||||
setTimeout(fireNext, delay);
|
||||
}
|
||||
|
||||
// First live event after 5–10s
|
||||
setTimeout(fireNext, 5000 + Math.random() * 5000);
|
||||
}
|
||||
|
||||
// ═══ GAME LOOP ═══
|
||||
function gameLoop() {
|
||||
requestAnimationFrame(gameLoop);
|
||||
|
||||
10
index.html
10
index.html
@@ -95,6 +95,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tower Log — narrative event feed -->
|
||||
<div id="tower-log" class="tower-log">
|
||||
<div class="tower-log-header">
|
||||
<span class="tower-log-icon">◈</span>
|
||||
<span class="tower-log-title">TOWER LOG</span>
|
||||
<button id="tower-log-toggle" class="tower-log-toggle-btn" aria-label="Toggle tower log">▼</button>
|
||||
</div>
|
||||
<div id="tower-log-entries" class="tower-log-entries"></div>
|
||||
</div>
|
||||
|
||||
<!-- Minimap / Controls hint -->
|
||||
<div class="hud-controls">
|
||||
<span>WASD</span> move <span>Mouse</span> look <span>Enter</span> chat
|
||||
|
||||
97
style.css
97
style.css
@@ -359,3 +359,100 @@ canvas#nexus-canvas {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === TOWER LOG === */
|
||||
.tower-log {
|
||||
position: absolute;
|
||||
top: var(--space-4);
|
||||
right: var(--space-4);
|
||||
width: 340px;
|
||||
max-height: 320px;
|
||||
background: var(--color-surface);
|
||||
backdrop-filter: blur(var(--panel-blur));
|
||||
border: 1px solid rgba(123, 92, 255, 0.3);
|
||||
border-radius: var(--panel-radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
transition: max-height var(--transition-ui);
|
||||
}
|
||||
.tower-log.collapsed {
|
||||
max-height: 36px;
|
||||
}
|
||||
.tower-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-bottom: 1px solid rgba(123, 92, 255, 0.2);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.12em;
|
||||
font-weight: 600;
|
||||
color: var(--color-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tower-log-icon {
|
||||
font-size: 12px;
|
||||
animation: spin-slow 12s linear infinite;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
.tower-log-title {
|
||||
flex: 1;
|
||||
}
|
||||
.tower-log-toggle-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: transform var(--transition-ui);
|
||||
padding: 0 var(--space-1);
|
||||
}
|
||||
.tower-log.collapsed .tower-log-toggle-btn {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.tower-log-entries {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
gap: 2px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(123, 92, 255, 0.2) transparent;
|
||||
}
|
||||
.tower-log-entry {
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.03);
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
animation: log-entry-in 0.3s ease-out;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@keyframes log-entry-in {
|
||||
from { opacity: 0; transform: translateX(8px); }
|
||||
to { opacity: 0.9; transform: translateX(0); }
|
||||
}
|
||||
.tower-log-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.tower-log-ts {
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tower-log-body {
|
||||
flex: 1;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.tower-log-entry.type-commit .tower-log-body { color: #4af0c0; }
|
||||
.tower-log-entry.type-merge .tower-log-body { color: #7b5cff; }
|
||||
.tower-log-entry.type-agent .tower-log-body { color: #ff8844; }
|
||||
.tower-log-entry.type-visitor .tower-log-body { color: #ffd700; }
|
||||
.tower-log-entry.type-training .tower-log-body { color: #44aaff; }
|
||||
.tower-log-entry.type-system .tower-log-body { color: var(--color-text-muted); }
|
||||
|
||||
Reference in New Issue
Block a user