Compare commits
1 Commits
mimo/build
...
mimo/creat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1294f37b44 |
@@ -1,132 +1,169 @@
|
||||
# Legacy Matrix Audit
|
||||
# Legacy Matrix Audit — Migration Table
|
||||
|
||||
Purpose:
|
||||
Preserve useful work from `/Users/apayne/the-matrix` before the Nexus browser shell is rebuilt.
|
||||
Preserve quality work from `/Users/apayne/the-matrix` before the Nexus browser shell is rebuilt.
|
||||
|
||||
Canonical rule:
|
||||
- `Timmy_Foundation/the-nexus` is the only canonical 3D repo.
|
||||
- `/Users/apayne/the-matrix` is legacy source material, not a parallel product.
|
||||
- This document is the authoritative migration table for issue #685.
|
||||
|
||||
## Verified Legacy Matrix State
|
||||
## Verified Legacy State
|
||||
|
||||
Local legacy repo:
|
||||
- `/Users/apayne/the-matrix`
|
||||
Local legacy repo: `/Users/apayne/the-matrix`
|
||||
|
||||
Observed facts:
|
||||
- Vite browser app exists
|
||||
- `npm test` passes with `87 passed, 0 failed`
|
||||
- 23 JS modules under `js/`
|
||||
- package scripts include `dev`, `build`, `preview`, and `test`
|
||||
- Vite browser app, vanilla JS + Three.js 0.171.0
|
||||
- 24 JS modules under `js/`
|
||||
- Smoke suite: 87 passed, 0 failed
|
||||
- Package scripts: dev, build, preview, test
|
||||
- PWA manifest + service worker
|
||||
- Vite config with code-splitting (Three.js in separate chunk)
|
||||
- Quality-tier system for hardware detection
|
||||
- WebSocket client with reconnection, heartbeat, mock mode
|
||||
- Full avatar FPS movement + PiP camera
|
||||
- Sub-world portal system with zone triggers
|
||||
|
||||
## Known historical Nexus snapshot
|
||||
## Migration Table
|
||||
|
||||
Useful in-repo reference point:
|
||||
- `0518a1c3ae3c1d0afeb24dea9772102f5a3d9a66`
|
||||
Decision key:
|
||||
- **CARRY** = transplant concepts and patterns into Nexus vNext
|
||||
- **ARCHIVE** = keep as reference, do not directly transplant
|
||||
- **DROP** = do not preserve unless re-justified
|
||||
|
||||
That snapshot still contains browser-world root files such as:
|
||||
- `index.html`
|
||||
- `app.js`
|
||||
- `style.css`
|
||||
- `package.json`
|
||||
- `tests/`
|
||||
### Core Modules
|
||||
|
||||
## Rescue Candidates
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/main.js` | 180 | App bootstrap, render loop, WebGL context recovery | **CARRY** | Architectural pattern. Shows clean init/teardown lifecycle, context-loss recovery, visibility pause. Nexus needs this loop but should not copy the monolithic wiring. |
|
||||
| `js/world.js` | 95 | Scene, camera, renderer, grid, lights | **CARRY** | Foundational. Quality-tier-aware renderer setup, grid floor, lighting. Nexus already has a world but should adopt the tier-aware antialiasing and pixel-ratio capping. |
|
||||
| `js/config.js` | 68 | Connection config via URL params + env vars | **ARCHIVE** | Pattern reference only. Nexus config should route through Hermes harness, not Vite env vars. The URL-override pattern (ws, token, mock) is worth remembering. |
|
||||
| `js/quality.js` | 90 | Hardware detection, quality tier (low/medium/high) | **CARRY** | Directly useful. DPR capping, core/memory/screen heuristics, WebGL renderer sniffing. Nexus needs this for graceful degradation on Mac/iPad. |
|
||||
| `js/storage.js` | 39 | Safe localStorage with in-memory fallback | **CARRY** | Small, robust, sandbox-proof. Nexus should use this or equivalent. Prevents crashes in sandboxed iframes. |
|
||||
|
||||
### Carry forward into Nexus vNext
|
||||
### Agent System
|
||||
|
||||
1. `agent-defs.js`
|
||||
- agent identity definitions
|
||||
- useful as seed data/model for visible entities in the world
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/agent-defs.js` | 30 | Agent identity data (id, label, color, role, position) | **CARRY** | Seed data model. Nexus agents should be defined similarly — data-driven, not hardcoded in render logic. Color hex helper is trivial but useful. |
|
||||
| `js/agents.js` | 523 | Agent 3D objects, movement, state, connection lines, hot-add/remove | **CARRY** | Core visual system. Shared geometries (perf), movement interpolation, wallet-health stress glow, auto-placement algorithm, connection-line pulse. All valuable. Needs integration with real agent state from Hermes. |
|
||||
| `js/behaviors.js` | 413 | Autonomous agent behavior state machine | **ARCHIVE** | Pattern reference. The personality-weighted behavior selection, conversation pairing, and artifact-placement system are well-designed. But Nexus behaviors should be driven by Hermes, not a client-side simulation. Keep the architecture, drop the fake-autonomy. |
|
||||
| `js/presence.js` | 139 | Agent presence HUD (online/offline, uptime, state) | **CARRY** | Valuable UX. Live "who's here" panel with uptime tickers and state indicators. Needs real backend state, not mock assumptions. |
|
||||
|
||||
2. `agents.js`
|
||||
- agent objects, state machine, connection lines
|
||||
- useful for visualizing Timmy / subagents / system processes in a world-native way
|
||||
### Visitor & Interaction
|
||||
|
||||
3. `avatar.js`
|
||||
- visitor embodiment, movement, camera handling
|
||||
- strongly aligned with "training ground" and "walk the world" goals
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/visitor.js` | 141 | Visitor enter/leave protocol, chat input | **CARRY** | Session lifecycle. Device detection, visibility-based leave/return, chat input wiring. Directly applicable to Nexus visitor tracking. |
|
||||
| `js/avatar.js` | 360 | FPS movement, PiP dual-camera, touch input | **CARRY** | Visitor embodiment. WASD + arrow movement, first/third person swap, PiP canvas, touch joystick, right-click mouse-look. Strong work. Needs tuning for Nexus world bounds. |
|
||||
| `js/interaction.js` | 296 | Raycasting, click-to-select agents, info popup | **CARRY** | Essential for any browser world. OrbitControls, pointer/tap detection, agent popup with state/role, TALK button. The popup-anchoring-to-3D-position logic is particularly well done. |
|
||||
| `js/zones.js` | 161 | Proximity trigger zones (portal enter/exit, events) | **CARRY** | Spatial event system. Portal traversal, event triggers, once-only zones. Nexus portals (#672) need this exact pattern. |
|
||||
|
||||
4. `ui.js`
|
||||
- HUD, chat surfaces, overlays
|
||||
- useful if rebuilt against real harness data instead of stale fake state
|
||||
### Chat & Communication
|
||||
|
||||
5. `websocket.js`
|
||||
- browser-side live bridge patterns
|
||||
- useful if retethered to Hermes-facing transport
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/bark.js` | 141 | Speech bubble system with typing animation | **CARRY** | Timmy's voice in-world. Typing animation, queue, auto-dismiss, emotion tags, demo bark lines. Strong expressive presence. The demo lines ("The Tower watches. The Tower remembers.") are good seed content. |
|
||||
| `js/ui.js` | 285 | Chat panel, agent list, HUD, streaming tokens | **CARRY** | Chat infrastructure. Rolling chat buffer, per-agent localStorage history, streaming token display with cursor animation, HTML escaping. Needs reconnection to Hermes chat instead of WS mock. |
|
||||
| `js/transcript.js` | 183 | Conversation transcript logger, export | **ARCHIVE** | Pattern reference. The rolling buffer, structured JSON entries, TXT/JSON download, HUD badge are all solid. But transcript authority should live in Hermes, not browser localStorage. Keep the UX pattern, rebuild storage layer. |
|
||||
|
||||
6. `transcript.js`
|
||||
- local transcript capture pattern
|
||||
- useful if durable truth still routes through Hermes and browser cache remains secondary
|
||||
### Visual Effects
|
||||
|
||||
7. `ambient.js`
|
||||
- mood / atmosphere system
|
||||
- directly supports wizardly presentation without changing system authority
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/effects.js` | 195 | Matrix rain particles + starfield | **CARRY** | Atmospheric foundation. Quality-tier particle counts, frame-skip optimization, adaptive draw-range (FPS-budget recovery), bounding-sphere pre-compute. This is production-grade particle work. |
|
||||
| `js/ambient.js` | 212 | Mood-driven atmosphere (lighting, fog, rain, stars) | **CARRY** | Scene mood engine. Smooth eased transitions between mood states (calm, focused, excited, contemplative, stressed), per-mood lighting/fog/rain/star parameters. Directly supports Nexus atmosphere. |
|
||||
| `js/satflow.js` | 261 | Lightning payment particle flow | **CARRY** | Economy visualization. Bezier-arc particles, staggered travel, burst-on-arrival, pooling. If Nexus shows any payment/economy flow, this is the pattern. |
|
||||
|
||||
8. `satflow.js`
|
||||
- visual economy / payment flow motifs
|
||||
- useful if Timmy's economy/agent interactions become a real visible layer
|
||||
### Economy & Scene
|
||||
|
||||
9. `economy.js`
|
||||
- treasury / wallet panel ideas
|
||||
- useful if later backed by real sovereign metrics
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/economy.js` | 100 | Wallet/treasury HUD panel | **ARCHIVE** | UI pattern reference. Clean sats formatting, per-agent balance rows, health-colored dots, recent transactions. Worth rebuilding when backed by real sovereign metrics. |
|
||||
| `js/scene-objects.js` | 718 | Dynamic 3D object registry, portals, sub-worlds | **CARRY** | Critical. Geometry/material factories, animation system (rotate/bob/pulse/orbit), portal visual (torus ring + glow disc + zone), sub-world load/unload, text sprites, compound groups. This is the most complex and valuable module. Nexus portals (#672) should build on this. |
|
||||
|
||||
10. `presence.js`
|
||||
- who-is-here / online-state UI
|
||||
- useful for showing human + agent + process presence in the world
|
||||
### Backend Bridge
|
||||
|
||||
11. `interaction.js`
|
||||
- clicking, inspecting, selecting world entities
|
||||
- likely needed in any real browser-facing Nexus shell
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/websocket.js` | 598 | WebSocket client, message dispatcher, mock mode | **ARCHIVE** | Pattern reference only. Reconnection with exponential backoff, heartbeat/zombie detection, rich message dispatch (40+ message types), streaming chat support. The architecture is sound but must be reconnected to Hermes transport, not copied wholesale. The message-type catalog is the most valuable reference artifact. |
|
||||
| `js/demo.js` | ~300 | Demo autopilot (mock mode simulation) | **DROP** | Fake activity simulation. Deliberately creates the illusion of live data. Do not preserve. If Nexus needs a demo mode, build a clearly-labeled one that doesn't pretend to be real. |
|
||||
|
||||
12. `quality.js`
|
||||
- hardware-aware quality tiering
|
||||
- useful for local-first graceful degradation on Mac hardware
|
||||
### Testing & Build
|
||||
|
||||
13. `bark.js`
|
||||
- prominent speech / bark system
|
||||
- strong fit for Timmy's expressive presence in-world
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `test/smoke.mjs` | 235 | Automated browser smoke test suite | **CARRY** | Testing discipline. Module inventory check, export verification, HTML structure validation, Vite build test, bundle-size budget, PWA manifest check. Nexus should adopt this pattern (adapted for its own module structure). |
|
||||
| `vite.config.js` | 53 | Build config with code splitting, SW generation | **ARCHIVE** | Build tooling reference. manualChunks for Three.js, SW precache generation plugin. Relevant if Nexus re-commits to Vite. |
|
||||
| `sw.js` | ~40 | Service worker with precache | **ARCHIVE** | PWA reference. Relevant only if Nexus pursues offline-first PWA. |
|
||||
| `manifest.json` | ~20 | PWA manifest | **ARCHIVE** | PWA reference. |
|
||||
|
||||
14. `world.js`, `effects.js`, `scene-objects.js`, `zones.js`
|
||||
- broad visual foundation work
|
||||
- should be mined for patterns, not blindly transplanted
|
||||
### Server-Side (Python)
|
||||
|
||||
15. `test/smoke.mjs`
|
||||
- browser smoke discipline
|
||||
- should inform rebuilt validation in canonical Nexus repo
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `server/bridge.py` | ~900 | WebSocket bridge server | **ARCHIVE** | Reference. Hermes replaces this role. Keep for protocol schema reference. |
|
||||
| `server/gateway.py` | ~400 | HTTP gateway | **ARCHIVE** | Reference. |
|
||||
| `server/ollama_client.py` | ~280 | Ollama integration | **ARCHIVE** | Reference. Relevant if Nexus needs local model calls. |
|
||||
| `server/research.py` | ~450 | Research pipeline | **ARCHIVE** | Reference. |
|
||||
| `server/webhooks.py` | ~350 | Webhook handler | **ARCHIVE** | Reference. |
|
||||
| `server/test_*.py` | ~5 files | Server test suites | **ARCHIVE** | Testing patterns worth studying. |
|
||||
|
||||
### Archive as reference, not direct carry-forward
|
||||
## Summary by Decision
|
||||
|
||||
- demo/autopilot assumptions that pretend fake backend activity is real
|
||||
- any websocket schema that no longer matches Hermes truth
|
||||
- Vite-specific plumbing that is only useful if we consciously recommit to Vite
|
||||
### CARRY FORWARD (17 modules)
|
||||
These modules contain patterns, algorithms, or entire implementations that should move into the Nexus browser shell:
|
||||
|
||||
### Deliberately drop unless re-justified
|
||||
- `quality.js` — hardware detection
|
||||
- `storage.js` — safe persistence
|
||||
- `world.js` — scene foundation
|
||||
- `agent-defs.js` — agent data model
|
||||
- `agents.js` — agent visualization + movement
|
||||
- `presence.js` — online presence HUD
|
||||
- `visitor.js` — session lifecycle
|
||||
- `avatar.js` — FPS embodiment
|
||||
- `interaction.js` — click/select/raycast
|
||||
- `zones.js` — spatial triggers
|
||||
- `bark.js` — speech bubbles
|
||||
- `ui.js` — chat/HUD
|
||||
- `effects.js` — particle effects
|
||||
- `ambient.js` — mood atmosphere
|
||||
- `satflow.js` — payment flow particles
|
||||
- `scene-objects.js` — dynamic objects + portals
|
||||
- `test/smoke.mjs` — smoke test discipline
|
||||
|
||||
- anything that presents mock data as if it were live
|
||||
- anything that duplicates a better Hermes-native telemetry path
|
||||
- anything that turns the browser into the system of record
|
||||
### ARCHIVE AS REFERENCE (9 modules/files)
|
||||
Keep for patterns, protocol schemas, and architectural reference. Do not directly transplant:
|
||||
|
||||
- `config.js` — config pattern (use Hermes instead)
|
||||
- `behaviors.js` — behavior architecture (use Hermes-driven state)
|
||||
- `transcript.js` — transcript UX (use Hermes storage)
|
||||
- `economy.js` — economy UI pattern (use real metrics)
|
||||
- `websocket.js` — message protocol catalog + reconnection patterns
|
||||
- `vite.config.js` — build tooling
|
||||
- `sw.js`, `manifest.json` — PWA reference
|
||||
- `server/*.py` — server protocol schemas
|
||||
|
||||
### DELIBERATELY DROP (2)
|
||||
Do not preserve unless re-justified:
|
||||
|
||||
- `demo.js` — fake activity simulation; creates false impression of live system
|
||||
- `main.js` monolithic wiring — the init pattern carries, the specific module wiring does not
|
||||
|
||||
## Concern Separation for Nexus vNext
|
||||
|
||||
When rebuilding inside `the-nexus`, keep concerns separated:
|
||||
When rebuilding inside `the-nexus`, keep these concerns in separate modules:
|
||||
|
||||
1. World shell / rendering
|
||||
- scene, camera, movement, atmosphere
|
||||
|
||||
2. Presence and embodiment
|
||||
- avatar, agent placement, selection, bark/chat surfaces
|
||||
|
||||
3. Harness bridge
|
||||
- websocket / API bridge from Hermes truth into browser state
|
||||
|
||||
4. Visualization panels
|
||||
- metrics, presence, economy, portal states, transcripts
|
||||
|
||||
5. Validation
|
||||
- smoke tests, screenshot proof, provenance checks
|
||||
|
||||
6. Game portal layer
|
||||
- Morrowind / portal-specific interaction surfaces
|
||||
1. **World shell** — scene, camera, renderer, grid, lights, fog
|
||||
2. **Effects layer** — rain, stars, ambient mood transitions
|
||||
3. **Agent visualization** — 3D objects, labels, connection lines, movement
|
||||
4. **Visitor embodiment** — avatar, FPS controls, PiP camera
|
||||
5. **Interaction layer** — raycasting, selection, zones, portal traversal
|
||||
6. **Communication surface** — bark, chat panel, streaming tokens
|
||||
7. **Presence & HUD** — who's-online, economy panel, transcript controls
|
||||
8. **Harness bridge** — WebSocket/API transport to Hermes (NOT a copy of websocket.js)
|
||||
9. **Quality & config** — hardware detection, runtime configuration
|
||||
10. **Smoke tests** — automated validation
|
||||
|
||||
Do not collapse all of this into one giant app file again.
|
||||
Do not let visual shell code become telemetry authority.
|
||||
|
||||
14
app.js
14
app.js
@@ -5,7 +5,6 @@ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
|
||||
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
|
||||
import { SpatialMemory } from './nexus/components/spatial-memory.js';
|
||||
import { SessionRooms } from './nexus/components/session-rooms.js';
|
||||
import { EvenniaRoomPanel } from './nexus/components/evennia-room-panel.js';
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// NEXUS v1.1 — Portal System Update
|
||||
@@ -708,7 +707,6 @@ async function init() {
|
||||
createAshStorm();
|
||||
SpatialMemory.init(scene);
|
||||
SessionRooms.init(scene, camera, null);
|
||||
EvenniaRoomPanel.init();
|
||||
updateLoad(90);
|
||||
|
||||
loadSession();
|
||||
@@ -2076,7 +2074,6 @@ function connectHermes() {
|
||||
addChatMessage('system', 'Hermes link established.');
|
||||
updateWsHudStatus(true);
|
||||
refreshWorkshopPanel();
|
||||
EvenniaRoomPanel.setConnected(true);
|
||||
};
|
||||
|
||||
// Initialize MemPalace
|
||||
@@ -2105,7 +2102,6 @@ function connectHermes() {
|
||||
hermesWs = null;
|
||||
updateWsHudStatus(false);
|
||||
refreshWorkshopPanel();
|
||||
EvenniaRoomPanel.setConnected(false);
|
||||
if (wsReconnectTimer) clearTimeout(wsReconnectTimer);
|
||||
wsReconnectTimer = setTimeout(connectHermes, 5000);
|
||||
};
|
||||
@@ -2116,16 +2112,6 @@ function connectHermes() {
|
||||
}
|
||||
|
||||
function handleHermesMessage(data) {
|
||||
// ── Evennia room snapshot events (#728) ──
|
||||
if (data.type === 'evennia.room_snapshot') {
|
||||
EvenniaRoomPanel.onRoomSnapshot(data);
|
||||
return;
|
||||
}
|
||||
if (data.type === 'evennia.actor_located') {
|
||||
EvenniaRoomPanel.onActorLocated(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'chat') {
|
||||
addChatMessage(data.agent || 'timmy', data.text);
|
||||
} else if (data.type === 'tool_call') {
|
||||
|
||||
13
index.html
13
index.html
@@ -125,19 +125,6 @@
|
||||
<div class="agent-log-header">AGENT THOUGHT STREAM</div>
|
||||
<div id="agent-log-content" class="agent-log-content"></div>
|
||||
</div>
|
||||
<!-- Evennia Room Snapshot Operator Panel (#728) -->
|
||||
<div id="evennia-room-panel" class="evennia-room-panel" aria-live="polite">
|
||||
<div class="erp-header">
|
||||
<span class="erp-icon">◈</span>
|
||||
<span class="erp-title">EVENNIA ROOM</span>
|
||||
<span class="erp-status-dot erp-offline"></span>
|
||||
</div>
|
||||
<div class="erp-body erp-empty-state">
|
||||
<div class="erp-empty-icon">⊘</div>
|
||||
<div class="erp-empty-label">DISCONNECTED</div>
|
||||
<div class="erp-empty-hint">No link to Evennia world.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom: Chat Interface -->
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// EVENNIA ROOM SNAPSHOT OPERATOR PANEL (Issue #728)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
//
|
||||
// Renders the current Evennia room state in the Nexus HUD.
|
||||
// Consumes evennia.room_snapshot and evennia.actor_located
|
||||
// events from the Hermes WebSocket bridge.
|
||||
//
|
||||
// States:
|
||||
// offline — no WS connection
|
||||
// awaiting — connected but no room data yet
|
||||
// in-room — room snapshot loaded, render full panel
|
||||
//
|
||||
// Usage from app.js:
|
||||
// EvenniaRoomPanel.init();
|
||||
// EvenniaRoomPanel.onRoomSnapshot(data);
|
||||
// EvenniaRoomPanel.onActorLocated(data);
|
||||
// EvenniaRoomPanel.setConnected(bool);
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
export const EvenniaRoomPanel = (() => {
|
||||
|
||||
// ─── STATE ────────────────────────────────────────────
|
||||
let _connected = false;
|
||||
let _roomData = null; // latest evennia.room_snapshot payload
|
||||
let _actorRoomId = null; // from evennia.actor_located
|
||||
let _lastUpdate = null; // timestamp of last snapshot
|
||||
let _panelEl = null; // DOM root
|
||||
let _init = false;
|
||||
|
||||
// ─── DOM REFS ─────────────────────────────────────────
|
||||
function _el(id) { return document.getElementById(id); }
|
||||
|
||||
// ─── INIT ─────────────────────────────────────────────
|
||||
function init() {
|
||||
_panelEl = _el('evennia-room-panel');
|
||||
if (!_panelEl) {
|
||||
console.warn('[EvenniaRoomPanel] Panel element not found in DOM.');
|
||||
return;
|
||||
}
|
||||
_init = true;
|
||||
_render();
|
||||
console.log('[EvenniaRoomPanel] Initialized.');
|
||||
}
|
||||
|
||||
// ─── EVENT HANDLERS ───────────────────────────────────
|
||||
|
||||
function onRoomSnapshot(data) {
|
||||
_roomData = data;
|
||||
_lastUpdate = data.timestamp || new Date().toISOString();
|
||||
_actorRoomId = data.room_id || data.room_key || null;
|
||||
_render();
|
||||
}
|
||||
|
||||
function onActorLocated(data) {
|
||||
_actorRoomId = data.room_id || data.room_key || null;
|
||||
// If we get a location but no snapshot yet, show awaiting
|
||||
if (!_roomData || (_roomData.room_id !== _actorRoomId && _roomData.room_key !== _actorRoomId)) {
|
||||
_render();
|
||||
}
|
||||
}
|
||||
|
||||
function setConnected(connected) {
|
||||
_connected = connected;
|
||||
if (!connected) {
|
||||
// Clear room data on disconnect — stale data is lying
|
||||
_roomData = null;
|
||||
_actorRoomId = null;
|
||||
_lastUpdate = null;
|
||||
}
|
||||
_render();
|
||||
}
|
||||
|
||||
// ─── RENDER ───────────────────────────────────────────
|
||||
|
||||
function _render() {
|
||||
if (!_panelEl) return;
|
||||
|
||||
if (!_connected) {
|
||||
_renderOffline();
|
||||
} else if (!_roomData) {
|
||||
_renderAwaiting();
|
||||
} else {
|
||||
_renderRoom();
|
||||
}
|
||||
}
|
||||
|
||||
function _renderOffline() {
|
||||
_panelEl.innerHTML = `
|
||||
<div class="erp-header">
|
||||
<span class="erp-icon">◈</span>
|
||||
<span class="erp-title">EVENNIA ROOM</span>
|
||||
<span class="erp-status-dot erp-offline"></span>
|
||||
</div>
|
||||
<div class="erp-body erp-empty-state">
|
||||
<div class="erp-empty-icon">⊘</div>
|
||||
<div class="erp-empty-label">DISCONNECTED</div>
|
||||
<div class="erp-empty-hint">No link to Evennia world.</div>
|
||||
</div>
|
||||
`;
|
||||
_panelEl.classList.add('erp-state-offline');
|
||||
_panelEl.classList.remove('erp-state-awaiting', 'erp-state-inroom');
|
||||
}
|
||||
|
||||
function _renderAwaiting() {
|
||||
_panelEl.innerHTML = `
|
||||
<div class="erp-header">
|
||||
<span class="erp-icon">◈</span>
|
||||
<span class="erp-title">EVENNIA ROOM</span>
|
||||
<span class="erp-status-dot erp-online"></span>
|
||||
</div>
|
||||
<div class="erp-body erp-empty-state">
|
||||
<div class="erp-empty-icon erp-pulse">◎</div>
|
||||
<div class="erp-empty-label">AWAITING SNAPSHOT</div>
|
||||
<div class="erp-empty-hint">Connected. Waiting for room data…</div>
|
||||
</div>
|
||||
`;
|
||||
_panelEl.classList.add('erp-state-awaiting');
|
||||
_panelEl.classList.remove('erp-state-offline', 'erp-state-inroom');
|
||||
}
|
||||
|
||||
function _renderRoom() {
|
||||
const room = _roomData;
|
||||
const title = _esc(room.title || room.room_name || room.room_key || 'Unknown Room');
|
||||
const desc = _esc(room.desc || 'No description available.');
|
||||
const exits = Array.isArray(room.exits) ? room.exits : [];
|
||||
const objects = Array.isArray(room.objects) ? room.objects : [];
|
||||
const occupants = Array.isArray(room.occupants) ? room.occupants : [];
|
||||
const roomId = _esc(room.room_id || room.room_key || '—');
|
||||
const timeStr = _formatTime(_lastUpdate);
|
||||
|
||||
// Build exits list
|
||||
let exitsHtml = '';
|
||||
if (exits.length > 0) {
|
||||
exitsHtml = exits.map(e => {
|
||||
const name = _esc(e.key || e.name || '?');
|
||||
const dest = _esc(e.destination_name || e.destination_id || e.destination_key || '');
|
||||
return `<div class="erp-exit-row">
|
||||
<span class="erp-exit-arrow">→</span>
|
||||
<span class="erp-exit-name">${name}</span>
|
||||
${dest ? `<span class="erp-exit-dest">${dest}</span>` : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
exitsHtml = '<div class="erp-none">No visible exits.</div>';
|
||||
}
|
||||
|
||||
// Build objects list
|
||||
let objectsHtml = '';
|
||||
if (objects.length > 0) {
|
||||
objectsHtml = objects.map(o => {
|
||||
const name = _esc(o.key || o.id || '?');
|
||||
const desc = _esc(o.short_desc || '');
|
||||
return `<div class="erp-object-row">
|
||||
<span class="erp-object-icon">▪</span>
|
||||
<span class="erp-object-name">${name}</span>
|
||||
${desc ? `<span class="erp-object-desc">${desc}</span>` : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
objectsHtml = '<div class="erp-none">No visible objects.</div>';
|
||||
}
|
||||
|
||||
// Build occupants list
|
||||
let occupantsHtml = '';
|
||||
if (occupants.length > 0) {
|
||||
occupantsHtml = occupants.map(o => {
|
||||
const name = _esc(typeof o === 'string' ? o : (o.name || o.key || '?'));
|
||||
return `<span class="erp-occupant">${name}</span>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
_panelEl.innerHTML = `
|
||||
<div class="erp-header">
|
||||
<span class="erp-icon">◈</span>
|
||||
<span class="erp-title">${title}</span>
|
||||
<span class="erp-status-dot erp-online"></span>
|
||||
</div>
|
||||
<div class="erp-body">
|
||||
<div class="erp-room-id">${roomId}</div>
|
||||
<div class="erp-desc">${desc}</div>
|
||||
|
||||
<div class="erp-section">
|
||||
<div class="erp-section-label">EXITS</div>
|
||||
<div class="erp-exits">${exitsHtml}</div>
|
||||
</div>
|
||||
|
||||
<div class="erp-section">
|
||||
<div class="erp-section-label">OBJECTS</div>
|
||||
<div class="erp-objects">${objectsHtml}</div>
|
||||
</div>
|
||||
|
||||
${occupantsHtml ? `
|
||||
<div class="erp-section">
|
||||
<div class="erp-section-label">OCCUPANTS</div>
|
||||
<div class="erp-occupants">${occupantsHtml}</div>
|
||||
</div>` : ''}
|
||||
|
||||
<div class="erp-footer">
|
||||
<span class="erp-time">${timeStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
_panelEl.classList.add('erp-state-inroom');
|
||||
_panelEl.classList.remove('erp-state-offline', 'erp-state-awaiting');
|
||||
}
|
||||
|
||||
// ─── UTILS ────────────────────────────────────────────
|
||||
|
||||
function _esc(str) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = str;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function _formatTime(isoStr) {
|
||||
if (!isoStr) return '—';
|
||||
try {
|
||||
const d = new Date(isoStr);
|
||||
return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
|
||||
} catch {
|
||||
return '—';
|
||||
}
|
||||
}
|
||||
|
||||
// ─── PUBLIC API ───────────────────────────────────────
|
||||
return { init, onRoomSnapshot, onActorLocated, setConnected };
|
||||
|
||||
})();
|
||||
226
style.css
226
style.css
@@ -1580,229 +1580,3 @@ canvas#nexus-canvas {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════
|
||||
EVENNIA ROOM SNAPSHOT OPERATOR PANEL (#728)
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
.evennia-room-panel {
|
||||
width: 280px;
|
||||
background: rgba(5, 5, 16, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(74, 240, 192, 0.18);
|
||||
border-left: 3px solid var(--color-primary, #4af0c0);
|
||||
border-radius: 6px;
|
||||
font-family: var(--font-body, 'JetBrains Mono', monospace);
|
||||
font-size: 11px;
|
||||
color: var(--color-text, #e0f0ff);
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.evennia-room-panel.erp-state-offline {
|
||||
border-left-color: var(--color-danger, #ff4466);
|
||||
}
|
||||
|
||||
.evennia-room-panel.erp-state-awaiting {
|
||||
border-left-color: var(--color-warning, #ffaa22);
|
||||
}
|
||||
|
||||
.evennia-room-panel.erp-state-inroom {
|
||||
border-left-color: var(--color-primary, #4af0c0);
|
||||
}
|
||||
|
||||
.erp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid rgba(74, 240, 192, 0.1);
|
||||
background: rgba(74, 240, 192, 0.04);
|
||||
}
|
||||
|
||||
.erp-icon {
|
||||
font-size: 12px;
|
||||
color: var(--color-primary, #4af0c0);
|
||||
}
|
||||
|
||||
.erp-title {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.2px;
|
||||
color: var(--color-primary, #4af0c0);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.erp-status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.erp-status-dot.erp-offline {
|
||||
background: var(--color-danger, #ff4466);
|
||||
box-shadow: 0 0 6px var(--color-danger, #ff4466);
|
||||
}
|
||||
|
||||
.erp-status-dot.erp-online {
|
||||
background: var(--color-primary, #4af0c0);
|
||||
box-shadow: 0 0 6px var(--color-primary, #4af0c0);
|
||||
}
|
||||
|
||||
.erp-body {
|
||||
padding: 8px 10px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Empty / offline states */
|
||||
.erp-empty-state {
|
||||
text-align: center;
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
.erp-empty-icon {
|
||||
font-size: 22px;
|
||||
color: rgba(74, 240, 192, 0.25);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.erp-empty-icon.erp-pulse {
|
||||
animation: erpPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes erpPulse {
|
||||
0%, 100% { opacity: 0.35; }
|
||||
50% { opacity: 0.9; }
|
||||
}
|
||||
|
||||
.erp-empty-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--color-text-muted, #8a9ab8);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.erp-empty-hint {
|
||||
font-size: 10px;
|
||||
color: rgba(138, 154, 184, 0.55);
|
||||
}
|
||||
|
||||
/* Room content */
|
||||
.erp-room-id {
|
||||
font-size: 9px;
|
||||
color: rgba(138, 154, 184, 0.4);
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.erp-desc {
|
||||
font-size: 11px;
|
||||
color: rgba(224, 240, 255, 0.8);
|
||||
line-height: 1.45;
|
||||
margin-bottom: 10px;
|
||||
border-left: 2px solid rgba(74, 240, 192, 0.15);
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.erp-section {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.erp-section-label {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--color-primary, #4af0c0);
|
||||
margin-bottom: 4px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.erp-none {
|
||||
font-size: 10px;
|
||||
color: rgba(138, 154, 184, 0.35);
|
||||
font-style: italic;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
/* Exits */
|
||||
.erp-exit-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.erp-exit-arrow {
|
||||
color: var(--color-secondary, #7b5cff);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erp-exit-name {
|
||||
color: var(--color-secondary, #7b5cff);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.erp-exit-dest {
|
||||
color: rgba(138, 154, 184, 0.45);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Objects */
|
||||
.erp-object-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5px;
|
||||
padding: 2px 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.erp-object-icon {
|
||||
color: var(--color-gold, #ffd700);
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.erp-object-name {
|
||||
color: rgba(224, 240, 255, 0.75);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.erp-object-desc {
|
||||
color: rgba(138, 154, 184, 0.45);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Occupants */
|
||||
.erp-occupants {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.erp-occupant {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(74, 240, 192, 0.08);
|
||||
border: 1px solid rgba(74, 240, 192, 0.15);
|
||||
border-radius: 3px;
|
||||
color: var(--color-primary, #4af0c0);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.erp-footer {
|
||||
margin-top: 6px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid rgba(74, 240, 192, 0.06);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.erp-time {
|
||||
font-size: 9px;
|
||||
color: rgba(138, 154, 184, 0.3);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user