Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Whitestone
9771472983 WIP: issue #728 (mimo swarm)
Some checks failed
CI / test (pull_request) Failing after 10s
CI / validate (pull_request) Failing after 15s
Review Approval Gate / verify-review (pull_request) Failing after 3s
2026-04-12 11:54:27 -04:00
3 changed files with 369 additions and 0 deletions

131
app.js
View File

@@ -58,6 +58,11 @@ let performanceTier = 'high';
let hermesWs = null;
let wsReconnectTimer = null;
let wsConnected = false;
// ═══ EVENNIA ROOM STATE ═══
let evenniaRoom = null; // {title, desc, exits[], objects[], occupants[], timestamp, roomKey}
let evenniaConnected = false;
let evenniaStaleTimer = null;
const EVENNIA_STALE_MS = 60000; // mark stale after 60s without update
let recentToolOutputs = [];
let workshopPanelCtx = null;
let workshopPanelTexture = null;
@@ -755,6 +760,8 @@ async function init() {
enterPrompt.addEventListener('click', () => {
enterPrompt.classList.add('fade-out');
document.getElementById('hud').style.display = 'block';
const erpPanel = document.getElementById('evennia-room-panel');
if (erpPanel) erpPanel.style.display = 'block';
setTimeout(() => { enterPrompt.remove(); }, 600);
}, { once: true });
@@ -2169,10 +2176,134 @@ function handleHermesMessage(data) {
else addChatMessage(msg.agent, msg.text, false);
});
}
} else if (data.type && data.type.startsWith('evennia.')) {
handleEvenniaEvent(data);
}
// ═══════════════════════════════════════════
// ═══════════════════════════════════════════
// EVENNIA ROOM SNAPSHOT PANEL (Issue #728)
// ═══════════════════════════════════════════
function handleEvenniaEvent(data) {
const evtType = data.type;
if (evtType === 'evennia.room_snapshot') {
evenniaRoom = {
roomKey: data.room_key || data.room_id || '',
title: data.title || 'Unknown Room',
desc: data.desc || '',
exits: data.exits || [],
objects: data.objects || [],
occupants: data.occupants || [],
timestamp: data.timestamp || new Date().toISOString()
};
evenniaConnected = true;
renderEvenniaRoomPanel();
resetEvenniaStaleTimer();
} else if (evtType === 'evennia.player_move') {
// Movement may indicate current room changed; update location text
if (data.to_room) {
const locEl = document.getElementById('hud-location-text');
if (locEl) locEl.textContent = data.to_room;
}
} else if (evtType === 'evennia.session_bound') {
evenniaConnected = true;
renderEvenniaRoomPanel();
} else if (evtType === 'evennia.player_join' || evtType === 'evennia.player_leave') {
// Refresh occupant display if we have room data
if (evenniaRoom) renderEvenniaRoomPanel();
}
}
function resetEvenniaStaleTimer() {
if (evenniaStaleTimer) clearTimeout(evenniaStaleTimer);
const dot = document.getElementById('erp-live-dot');
const status = document.getElementById('erp-status');
if (dot) dot.className = 'erp-live-dot connected';
if (status) { status.textContent = 'LIVE'; status.className = 'erp-status online'; }
evenniaStaleTimer = setTimeout(() => {
if (dot) dot.className = 'erp-live-dot stale';
if (status) { status.textContent = 'STALE'; status.className = 'erp-status stale'; }
}, EVENNIA_STALE_MS);
}
function renderEvenniaRoomPanel() {
const panel = document.getElementById('evennia-room-panel');
if (!panel) return;
panel.style.display = 'block';
const emptyEl = document.getElementById('erp-empty');
const roomEl = document.getElementById('erp-room');
if (!evenniaRoom) {
if (emptyEl) emptyEl.style.display = 'flex';
if (roomEl) roomEl.style.display = 'none';
return;
}
if (emptyEl) emptyEl.style.display = 'none';
if (roomEl) roomEl.style.display = 'block';
const titleEl = document.getElementById('erp-room-title');
const descEl = document.getElementById('erp-room-desc');
if (titleEl) titleEl.textContent = evenniaRoom.title;
if (descEl) descEl.textContent = evenniaRoom.desc;
renderEvenniaList('erp-exits', evenniaRoom.exits, (item) => {
const name = item.key || item.destination_id || item.name || '?';
const dest = item.destination_key || item.destination_id || '';
return { icon: '→', label: name, extra: dest && dest !== name ? dest : '' };
});
renderEvenniaList('erp-objects', evenniaRoom.objects, (item) => {
const name = item.short_desc || item.key || item.id || item.name || '?';
return { icon: '◇', label: name };
});
renderEvenniaList('erp-occupants', evenniaRoom.occupants, (item) => {
const name = item.character || item.name || item.account || '?';
return { icon: '◉', label: name };
});
const tsEl = document.getElementById('erp-footer-ts');
const roomKeyEl = document.getElementById('erp-footer-room');
if (tsEl) {
try {
const d = new Date(evenniaRoom.timestamp);
tsEl.textContent = d.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
} catch(e) { tsEl.textContent = '—'; }
}
if (roomKeyEl) roomKeyEl.textContent = evenniaRoom.roomKey;
}
function renderEvenniaList(containerId, items, mapFn) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
if (!items || items.length === 0) {
const empty = document.createElement('div');
empty.className = 'erp-section-empty';
empty.textContent = 'none';
container.appendChild(empty);
return;
}
items.forEach(item => {
const mapped = mapFn(item);
const row = document.createElement('div');
row.className = 'erp-item';
row.innerHTML = `<span class="erp-item-icon">${mapped.icon}</span><span>${mapped.label}</span>`;
if (mapped.extra) {
row.innerHTML += `<span class="erp-item-dest">${mapped.extra}</span>`;
}
container.appendChild(row);
});
}
// MNEMOSYNE — LIVE MEMORY BRIDGE
// ═══════════════════════════════════════════

View File

@@ -102,6 +102,44 @@
</div>
</div>
<!-- Evennia Room Snapshot Panel -->
<div id="evennia-room-panel" class="evennia-room-panel" style="display:none;">
<div class="erp-header">
<div class="erp-header-left">
<div class="erp-live-dot" id="erp-live-dot"></div>
<span class="erp-title">EVENNIA — ROOM SNAPSHOT</span>
</div>
<span class="erp-status" id="erp-status">OFFLINE</span>
</div>
<div class="erp-body" id="erp-body">
<div class="erp-empty" id="erp-empty">
<span class="erp-empty-icon"></span>
<span class="erp-empty-text">No Evennia connection</span>
<span class="erp-empty-sub">Waiting for room data...</span>
</div>
<div class="erp-room" id="erp-room" style="display:none;">
<div class="erp-room-title" id="erp-room-title"></div>
<div class="erp-room-desc" id="erp-room-desc"></div>
<div class="erp-section">
<div class="erp-section-header">EXITS</div>
<div class="erp-exits" id="erp-exits"></div>
</div>
<div class="erp-section">
<div class="erp-section-header">OBJECTS</div>
<div class="erp-objects" id="erp-objects"></div>
</div>
<div class="erp-section">
<div class="erp-section-header">OCCUPANTS</div>
<div class="erp-occupants" id="erp-occupants"></div>
</div>
</div>
</div>
<div class="erp-footer">
<span class="erp-footer-ts" id="erp-footer-ts"></span>
<span class="erp-footer-room" id="erp-footer-room"></span>
</div>
</div>
<!-- Top Left: Debug -->
<div id="debug-overlay" class="hud-debug"></div>

200
style.css
View File

@@ -2077,3 +2077,203 @@ canvas#nexus-canvas {
font-style: italic;
padding: 4px 0;
}
/* ═══ EVENNIA ROOM SNAPSHOT PANEL (Issue #728) ═══ */
.evennia-room-panel {
position: fixed;
right: 20px;
top: 80px;
width: 300px;
background: rgba(5, 5, 16, 0.85);
border: 1px solid rgba(74, 240, 192, 0.2);
border-right: 3px solid #4af0c0;
border-radius: var(--panel-radius);
backdrop-filter: blur(var(--panel-blur));
font-family: var(--font-body);
font-size: 11px;
color: var(--color-text);
z-index: 100;
overflow: hidden;
}
.erp-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid rgba(74, 240, 192, 0.12);
background: rgba(74, 240, 192, 0.03);
}
.erp-header-left {
display: flex;
align-items: center;
gap: 8px;
}
.erp-live-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-text-muted);
transition: background 0.3s ease;
}
.erp-live-dot.connected {
background: var(--color-primary);
animation: blink 1.4s ease-in-out infinite;
}
.erp-live-dot.stale {
background: var(--color-warning);
animation: blink 2s ease-in-out infinite;
}
.erp-title {
font-family: var(--font-display);
font-size: 10px;
letter-spacing: 0.12em;
color: var(--color-primary);
}
.erp-status {
font-size: 9px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-muted);
padding: 2px 6px;
border-radius: 3px;
background: rgba(138, 154, 184, 0.1);
}
.erp-status.online {
color: var(--color-primary);
background: rgba(74, 240, 192, 0.1);
}
.erp-status.stale {
color: var(--color-warning);
background: rgba(255, 170, 34, 0.1);
}
.erp-body {
padding: 8px 12px;
max-height: 360px;
overflow-y: auto;
}
/* Empty/offline state */
.erp-empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 20px 0;
text-align: center;
}
.erp-empty-icon {
font-size: 20px;
opacity: 0.4;
}
.erp-empty-text {
font-size: 11px;
color: var(--color-text-muted);
}
.erp-empty-sub {
font-size: 10px;
color: rgba(138, 154, 184, 0.5);
font-style: italic;
}
/* Room content */
.erp-room-title {
font-family: var(--font-display);
font-size: 13px;
font-weight: 600;
color: var(--color-primary);
margin-bottom: 6px;
letter-spacing: 0.04em;
}
.erp-room-desc {
font-size: 11px;
color: var(--color-text);
line-height: 1.5;
margin-bottom: 10px;
opacity: 0.85;
}
.erp-section {
margin-bottom: 8px;
}
.erp-section-header {
font-size: 9px;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--color-secondary);
margin-bottom: 4px;
padding-bottom: 2px;
border-bottom: 1px solid rgba(123, 92, 255, 0.15);
}
.erp-item {
font-size: 11px;
color: var(--color-text);
padding: 2px 0;
display: flex;
align-items: center;
gap: 6px;
}
.erp-item-icon {
color: var(--color-primary);
opacity: 0.6;
flex-shrink: 0;
font-size: 9px;
}
.erp-item-dest {
font-size: 10px;
color: var(--color-text-muted);
margin-left: auto;
}
.erp-objects .erp-item-icon {
color: var(--color-gold);
}
.erp-occupants .erp-item-icon {
color: var(--color-secondary);
}
.erp-section-empty {
font-size: 10px;
color: rgba(138, 154, 184, 0.4);
font-style: italic;
padding: 2px 0;
}
/* Footer */
.erp-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 12px;
border-top: 1px solid rgba(74, 240, 192, 0.1);
background: rgba(74, 240, 192, 0.02);
}
.erp-footer-ts {
font-size: 10px;
color: var(--color-text-muted);
}
.erp-footer-room {
font-size: 10px;
color: var(--color-secondary);
font-weight: 600;
}