This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/the-matrix/js/websocket.js
Replit Agent 45ffab77ab
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
feat(epic222): Workshop — Timmy wizard presence, world state, WS bootstrap
## Epic #222 — The Workshop: Timmy as Presence

### Backend
- lib/db/src/schema/world-events.ts: world_events table (id, type, agentId, summary, jobId, createdAt)
- lib/db/src/schema/index.ts: export worldEvents
- artifacts/api-server/src/lib/world-state.ts: in-memory world state (timmyState, agentStates, derived mood/activity)
- artifacts/api-server/src/routes/world.ts: GET /api/world/state
- artifacts/api-server/src/routes/index.ts: register worldRouter
- artifacts/api-server/src/routes/events.ts: WS world_state bootstrap on connect; visitor_enter/leave/message protocol; job events update world-state + log to DB

### Frontend (the-matrix/)
- world.js: Workshop room — dark stone floor, wooden desk, shelves, fireplace warm light, atmospheric fog
- agents.js: Timmy wizard — cone robe, sphere head, hat, crystal ball (glows on events), Pip familiar (wanders), speech bubble sprite; state-driven animations
- effects.js: floating ambient dust motes (amber + green)
- websocket.js: world_state bootstrap, visitor_enter, job events → crystal ball, chat → speech bubble
- ui.js: minimal HUD + event log + touch-first input bar
- main.js: updated imports, clean loop
- index.html: Workshop HTML — dark theme, input bar, payment panel

20/20 testkit PASS
2026-03-19 02:14:51 +00:00

120 lines
2.8 KiB
JavaScript

import { setAgentState, setSpeechBubble, applyAgentStates } from './agents.js';
import { appendSystemMessage } from './ui.js';
function resolveWsUrl() {
const explicit = import.meta.env.VITE_WS_URL;
if (explicit) return explicit;
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${proto}//${window.location.host}/api/ws`;
}
const WS_URL = resolveWsUrl();
let ws = null;
let connectionState = 'disconnected';
let jobCount = 0;
let reconnectTimer = null;
let visitorId = null;
const RECONNECT_DELAY_MS = 5000;
export function initWebSocket(_scene) {
visitorId = crypto.randomUUID();
connect();
}
function connect() {
if (ws) { ws.onclose = null; ws.close(); }
connectionState = 'connecting';
try {
ws = new WebSocket(WS_URL);
} catch {
connectionState = 'disconnected';
scheduleReconnect();
return;
}
ws.onopen = () => {
connectionState = 'connected';
clearTimeout(reconnectTimer);
send({ type: 'visitor_enter', visitorId, visitorName: 'visitor' });
};
ws.onmessage = event => {
try { handleMessage(JSON.parse(event.data)); } catch { /* ignore */ }
};
ws.onerror = () => { connectionState = 'disconnected'; };
ws.onclose = () => { connectionState = 'disconnected'; scheduleReconnect(); };
}
function scheduleReconnect() {
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(connect, RECONNECT_DELAY_MS);
}
function send(payload) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
function handleMessage(msg) {
switch (msg.type) {
case 'ping':
send({ type: 'pong' });
break;
case 'world_state': {
if (msg.agentStates) applyAgentStates(msg.agentStates);
if (msg.recentEvents) {
const last = msg.recentEvents.slice(-3);
last.forEach(ev => appendSystemMessage(ev.summary || ev.type));
}
break;
}
case 'timmy_state': {
break;
}
case 'agent_state': {
if (msg.agentId && msg.state) setAgentState(msg.agentId, msg.state);
break;
}
case 'job_started': {
jobCount++;
if (msg.agentId) setAgentState(msg.agentId, 'active');
appendSystemMessage(`job ${(msg.jobId || '').slice(0, 8)} started`);
break;
}
case 'job_completed': {
if (jobCount > 0) jobCount--;
if (msg.agentId) setAgentState(msg.agentId, 'idle');
appendSystemMessage(`job ${(msg.jobId || '').slice(0, 8)} complete`);
break;
}
case 'chat': {
if (msg.text) setSpeechBubble(msg.text);
break;
}
case 'agent_count':
case 'visitor_count':
break;
default:
break;
}
}
export function sendVisitorMessage(text) {
send({ type: 'visitor_message', visitorId, text });
}
export function getConnectionState() { return connectionState; }
export function getJobCount() { return jobCount; }