diff --git a/app.js b/app.js
index 4147640..5a2bcaa 100644
--- a/app.js
+++ b/app.js
@@ -35,7 +35,6 @@ let activePortal = null; // Portal currently in proximity
let activeVisionPoint = null; // Vision point currently in proximity
let portalOverlayActive = false;
let visionOverlayActive = false;
-let atlasOverlayActive = false;
let thoughtStreamMesh;
let harnessPulseMesh;
let powerMeterBars = [];
@@ -76,569 +75,6 @@ const orbitState = {
let flyY = 2;
// ═══ INIT ═══
-
-// ═══ SOVEREIGN SYMBOLIC ENGINE (GOFAI) ═══
-class SymbolicEngine {
- constructor() {
- this.facts = new Map();
- this.factIndices = new Map();
- this.factMask = 0n;
- this.rules = [];
- this.reasoningLog = [];
- }
-
- addFact(key, value) {
- this.facts.set(key, value);
- if (!this.factIndices.has(key)) {
- this.factIndices.set(key, BigInt(this.factIndices.size));
- }
- const bitIndex = this.factIndices.get(key);
- if (value) {
- this.factMask |= (1n << bitIndex);
- } else {
- this.factMask &= ~(1n << bitIndex);
- }
- }
-
- addRule(condition, action, description) {
- this.rules.push({ condition, action, description });
- }
-
- reason() {
- this.rules.forEach(rule => {
- if (rule.condition(this.facts)) {
- const result = rule.action(this.facts);
- if (result) {
- this.logReasoning(rule.description, result);
- }
- }
- });
- }
-
- logReasoning(ruleDesc, outcome) {
- const entry = { timestamp: Date.now(), rule: ruleDesc, outcome: outcome };
- this.reasoningLog.unshift(entry);
- if (this.reasoningLog.length > 5) this.reasoningLog.pop();
-
- const container = document.getElementById('symbolic-log-content');
- if (container) {
- const logDiv = document.createElement('div');
- logDiv.className = 'symbolic-log-entry';
- logDiv.innerHTML = `[RULE] ${ruleDesc}→ ${outcome}`;
- container.prepend(logDiv);
- if (container.children.length > 5) container.lastElementChild.remove();
- }
- }
-}
-
-class AgentFSM {
- constructor(agentId, initialState) {
- this.agentId = agentId;
- this.state = initialState;
- this.transitions = {};
- }
-
- addTransition(fromState, toState, condition) {
- if (!this.transitions[fromState]) this.transitions[fromState] = [];
- this.transitions[fromState].push({ toState, condition });
- }
-
- update(facts) {
- const possibleTransitions = this.transitions[this.state] || [];
- for (const transition of possibleTransitions) {
- if (transition.condition(facts)) {
- console.log(`[FSM] Agent ${this.agentId} transitioning: ${this.state} -> ${transition.toState}`);
- this.state = transition.toState;
- return true;
- }
- }
- return false;
- }
-}
-
-class KnowledgeGraph {
- constructor() {
- this.nodes = new Map();
- this.edges = [];
- }
-
- addNode(id, type, metadata = {}) {
- this.nodes.set(id, { id, type, ...metadata });
- }
-
- addEdge(from, to, relation) {
- this.edges.push({ from, to, relation });
- }
-
- query(from, relation) {
- return this.edges
- .filter(e => e.from === from && e.relation === relation)
- .map(e => this.nodes.get(e.to));
- }
-}
-
-class Blackboard {
- constructor() {
- this.data = {};
- this.subscribers = [];
- }
-
- write(key, value, source) {
- const oldValue = this.data[key];
- this.data[key] = value;
- this.notify(key, value, oldValue, source);
- }
-
- read(key) { return this.data[key]; }
-
- subscribe(callback) { this.subscribers.push(callback); }
-
- notify(key, value, oldValue, source) {
- this.subscribers.forEach(sub => sub(key, value, oldValue, source));
- const container = document.getElementById('blackboard-log-content');
- if (container) {
- const entry = document.createElement('div');
- entry.className = 'blackboard-entry';
- entry.innerHTML = `[${source}] ${key}: ${JSON.stringify(value)}`;
- container.prepend(entry);
- if (container.children.length > 8) container.lastElementChild.remove();
- }
- }
-}
-
-class SymbolicPlanner {
- constructor() {
- this.actions = [];
- this.currentPlan = [];
- }
-
- addAction(name, preconditions, effects) {
- this.actions.push({ name, preconditions, effects });
- }
-
- heuristic(state, goal) {
- let h = 0;
- for (let key in goal) {
- if (state[key] !== goal[key]) {
- h += Math.abs((state[key] || 0) - (goal[key] || 0));
- }
- }
- return h;
- }
-
- findPlan(initialState, goalState) {
- let openSet = [{ state: initialState, plan: [], g: 0, h: this.heuristic(initialState, goalState) }];
- let visited = new Map();
- visited.set(JSON.stringify(initialState), 0);
-
- while (openSet.length > 0) {
- openSet.sort((a, b) => (a.g + a.h) - (b.g + b.h));
- let { state, plan, g } = openSet.shift();
-
- if (this.isGoalReached(state, goalState)) return plan;
-
- for (let action of this.actions) {
- if (this.arePreconditionsMet(state, action.preconditions)) {
- let nextState = { ...state, ...action.effects };
- let stateStr = JSON.stringify(nextState);
- let nextG = g + 1;
-
- if (!visited.has(stateStr) || nextG < visited.get(stateStr)) {
- visited.set(stateStr, nextG);
- openSet.push({
- state: nextState,
- plan: [...plan, action.name],
- g: nextG,
- h: this.heuristic(nextState, goalState)
- });
- }
- }
- }
- }
- return null;
- }
-
- isGoalReached(state, goal) {
- for (let key in goal) {
- if (state[key] !== goal[key]) return false;
- }
- return true;
- }
-
- arePreconditionsMet(state, preconditions) {
- for (let key in preconditions) {
- if (state[key] < preconditions[key]) return false;
- }
- return true;
- }
-
- logPlan(plan) {
- this.currentPlan = plan;
- const container = document.getElementById('planner-log-content');
- if (container) {
- container.innerHTML = '';
- if (!plan || plan.length === 0) {
- container.innerHTML = '
NO ACTIVE PLAN
';
- return;
- }
- plan.forEach((step, i) => {
- const div = document.createElement('div');
- div.className = 'planner-step';
- div.innerHTML = `${i+1}. ${step}`;
- container.appendChild(div);
- });
- }
- }
-}
-
-class HTNPlanner {
- constructor() {
- this.methods = {};
- this.primitiveTasks = {};
- }
-
- addMethod(taskName, preconditions, subtasks) {
- if (!this.methods[taskName]) this.methods[taskName] = [];
- this.methods[taskName].push({ preconditions, subtasks });
- }
-
- addPrimitiveTask(taskName, preconditions, effects) {
- this.primitiveTasks[taskName] = { preconditions, effects };
- }
-
- findPlan(initialState, tasks) {
- return this.decompose(initialState, tasks, []);
- }
-
- decompose(state, tasks, plan) {
- if (tasks.length === 0) return plan;
- const [task, ...remainingTasks] = tasks;
- if (this.primitiveTasks[task]) {
- const { preconditions, effects } = this.primitiveTasks[task];
- if (this.arePreconditionsMet(state, preconditions)) {
- const nextState = { ...state, ...effects };
- return this.decompose(nextState, remainingTasks, [...plan, task]);
- }
- return null;
- }
- const methods = this.methods[task] || [];
- for (const method of methods) {
- if (this.arePreconditionsMet(state, method.preconditions)) {
- const result = this.decompose(state, [...method.subtasks, ...remainingTasks], plan);
- if (result) return result;
- }
- }
- return null;
- }
-
- arePreconditionsMet(state, preconditions) {
- for (const key in preconditions) {
- if (state[key] < (preconditions[key] || 0)) return false;
- }
- return true;
- }
-}
-
-class CaseBasedReasoner {
- constructor() {
- this.caseLibrary = [];
- }
-
- addCase(situation, action, outcome) {
- this.caseLibrary.push({ situation, action, outcome, timestamp: Date.now() });
- }
-
- findSimilarCase(currentSituation) {
- let bestMatch = null;
- let maxSimilarity = -1;
- this.caseLibrary.forEach(c => {
- let similarity = this.calculateSimilarity(currentSituation, c.situation);
- if (similarity > maxSimilarity) {
- maxSimilarity = similarity;
- bestMatch = c;
- }
- });
- return maxSimilarity > 0.7 ? bestMatch : null;
- }
-
- calculateSimilarity(s1, s2) {
- let score = 0, total = 0;
- for (let key in s1) {
- if (s2[key] !== undefined) {
- score += 1 - Math.abs(s1[key] - s2[key]);
- total += 1;
- }
- }
- return total > 0 ? score / total : 0;
- }
-
- logCase(c) {
- const container = document.getElementById('cbr-log-content');
- if (container) {
- const div = document.createElement('div');
- div.className = 'cbr-entry';
- div.innerHTML = `
- SIMILAR CASE FOUND (${(this.calculateSimilarity(symbolicEngine.facts, c.situation) * 100).toFixed(0)}%)
- SUGGESTED: ${c.action}
- PREVIOUS OUTCOME: ${c.outcome}
- `;
- container.prepend(div);
- if (container.children.length > 3) container.lastElementChild.remove();
- }
- }
-}
-
-class NeuroSymbolicBridge {
- constructor(symbolicEngine, blackboard) {
- this.engine = symbolicEngine;
- this.blackboard = blackboard;
- this.perceptionLog = [];
- }
-
- perceive(rawState) {
- const concepts = [];
- if (rawState.stability < 0.4 && rawState.energy > 60) concepts.push('UNSTABLE_OSCILLATION');
- if (rawState.energy < 30 && rawState.activePortals > 2) concepts.push('CRITICAL_DRAIN_PATTERN');
- concepts.forEach(concept => {
- this.engine.addFact(concept, true);
- this.logPerception(concept);
- });
- return concepts;
- }
-
- logPerception(concept) {
- const container = document.getElementById('neuro-bridge-log-content');
- if (container) {
- const div = document.createElement('div');
- div.className = 'neuro-bridge-entry';
- div.innerHTML = `🧠 ${concept}`;
- container.prepend(div);
- if (container.children.length > 5) container.lastElementChild.remove();
- }
- }
-}
-
-class MetaReasoningLayer {
- constructor(planner, blackboard) {
- this.planner = planner;
- this.blackboard = blackboard;
- this.reasoningCache = new Map();
- this.performanceMetrics = { totalReasoningTime: 0, calls: 0 };
- }
-
- getCachedPlan(stateKey) {
- const cached = this.reasoningCache.get(stateKey);
- if (cached && (Date.now() - cached.timestamp < 10000)) return cached.plan;
- return null;
- }
-
- cachePlan(stateKey, plan) {
- this.reasoningCache.set(stateKey, { plan, timestamp: Date.now() });
- }
-
- reflect() {
- const avgTime = this.performanceMetrics.totalReasoningTime / (this.performanceMetrics.calls || 1);
- const container = document.getElementById('meta-log-content');
- if (container) {
- container.innerHTML = `
- CACHE SIZE: ${this.reasoningCache.size}
- AVG LATENCY: ${avgTime.toFixed(2)}ms
- STATUS: ${avgTime > 50 ? 'OPTIMIZING' : 'NOMINAL'}
- `;
- }
- }
-
- track(startTime) {
- const duration = performance.now() - startTime;
- this.performanceMetrics.totalReasoningTime += duration;
- this.performanceMetrics.calls++;
- }
-}
-
-// ═══ ADAPTIVE CALIBRATOR (LOCAL EFFICIENCY) ═══
-class AdaptiveCalibrator {
- constructor(modelId, initialParams) {
- this.model = modelId;
- this.weights = {
- 'input_tokens': 0.0,
- 'complexity_score': 0.0,
- 'task_type_indicator': 0.0,
- 'bias': initialParams.base_rate || 0.0
- };
- this.learningRate = 0.01;
- this.history = [];
- }
-
- predict(features) {
- let prediction = this.weights['bias'];
- for (let feature in features) {
- if (this.weights[feature] !== undefined) {
- prediction += this.weights[feature] * features[feature];
- }
- }
- return Math.max(0, prediction);
- }
-
- update(features, actualCost) {
- const predicted = this.predict(features);
- const error = actualCost - predicted;
- for (let feature in features) {
- if (this.weights[feature] !== undefined) {
- this.weights[feature] += this.learningRate * error * features[feature];
- }
- }
- this.history.push({ predicted, actual: actualCost, timestamp: Date.now() });
-
- const container = document.getElementById('calibrator-log-content');
- if (container) {
- const div = document.createElement('div');
- div.className = 'calibrator-entry';
- div.innerHTML = `CALIBRATED: ${predicted.toFixed(4)} ERR: ${error.toFixed(4)}`;
- container.prepend(div);
- if (container.children.length > 5) container.lastElementChild.remove();
- }
- }
-}
-
-
-// ═══ NOSTR AGENT REGISTRATION ═══
-class NostrAgent {
- constructor(pubkey) {
- this.pubkey = pubkey;
- this.relays = ['wss://relay.damus.io', 'wss://nos.lol'];
- }
-
- async announce(metadata) {
- console.log(`[NOSTR] Announcing agent ${this.pubkey}...`);
- const event = {
- kind: 0,
- pubkey: this.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: JSON.stringify(metadata),
- id: 'mock_id',
- sig: 'mock_sig'
- };
-
- this.relays.forEach(url => {
- console.log(`[NOSTR] Publishing to ${url}: `, event);
- });
-
- const container = document.getElementById('nostr-log-content');
- if (container) {
- const div = document.createElement('div');
- div.className = 'nostr-entry';
- div.innerHTML = `[${this.pubkey.substring(0,8)}...] ANNOUNCED`;
- container.prepend(div);
- }
- }
-}
-
-// ═══ L402 CLIENT LOGIC ═══
-class L402Client {
- async fetchWithL402(url) {
- console.log(`[L402] Fetching ${url}...`);
- const response = await fetch(url);
-
- if (response.status === 402) {
- const authHeader = response.headers.get('WWW-Authenticate');
- console.log(`[L402] Challenge received: ${authHeader}`);
-
- const container = document.getElementById('l402-log-content');
- if (container) {
- const div = document.createElement('div');
- div.className = 'l402-entry';
- div.innerHTML = `CHALLENGE Payment Required`;
- container.prepend(div);
- }
- return { status: 402, challenge: authHeader };
- }
-
- return response.json();
- }
-}
-
-let nostrAgent, l402Client;
-
-
-// ═══ PARALLEL SYMBOLIC EXECUTION (PSE) ═══
-class PSELayer {
- constructor() {
- this.worker = new Worker('gofai_worker.js');
- this.worker.onmessage = (e) => this.handleWorkerMessage(e);
- this.pendingRequests = new Map();
- }
-
- handleWorkerMessage(e) {
- const { type, results, plan } = e.data;
- if (type === 'REASON_RESULT') {
- results.forEach(res => symbolicEngine.logReasoning(res.rule, res.outcome));
- } else if (type === 'PLAN_RESULT') {
- symbolicPlanner.logPlan(plan);
- }
- }
-
- offloadReasoning(facts, rules) {
- this.worker.postMessage({ type: 'REASON', data: { facts, rules } });
- }
-
- offloadPlanning(initialState, goalState, actions) {
- this.worker.postMessage({ type: 'PLAN', data: { initialState, goalState, actions } });
- }
-}
-
-let pseLayer;
-
-let metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
-let agentFSMs = {};
-
-function setupGOFAI() {
- knowledgeGraph = new KnowledgeGraph();
- blackboard = new Blackboard();
- symbolicEngine = new SymbolicEngine();
- symbolicPlanner = new SymbolicPlanner();
- cbr = new CaseBasedReasoner();
- neuroBridge = new NeuroSymbolicBridge(symbolicEngine, blackboard);
- metaLayer = new MetaReasoningLayer(symbolicPlanner, blackboard);
- nostrAgent = new NostrAgent("npub1...");
- l402Client = new L402Client();
- nostrAgent.announce({ name: "Timmy Nexus Agent", capabilities: ["GOFAI", "L402"] });
- pseLayer = new PSELayer();
- calibrator = new AdaptiveCalibrator('nexus-v1', { base_rate: 0.05 });
-
- // Setup initial facts
- symbolicEngine.addFact('energy', 100);
- symbolicEngine.addFact('stability', 1.0);
-
- // Setup FSM
- agentFSMs['timmy'] = new AgentFSM('timmy', 'IDLE');
- agentFSMs['timmy'].addTransition('IDLE', 'ANALYZING', (facts) => facts.get('activePortals') > 0);
-
- // Setup Planner
- symbolicPlanner.addAction('Stabilize Matrix', { energy: 50 }, { stability: 1.0 });
-}
-
-function updateGOFAI(delta, elapsed) {
- const startTime = performance.now();
-
- // Simulate perception
- neuroBridge.perceive({ stability: 0.3, energy: 80, activePortals: 1 });
-
- // Run reasoning
- if (Math.floor(elapsed * 2) > Math.floor((elapsed - delta) * 2)) {
- symbolicEngine.reason();
- pseLayer.offloadReasoning(Array.from(symbolicEngine.facts.entries()), symbolicEngine.rules.map(r => ({ description: r.description })));
- document.getElementById("pse-task-count").innerText = parseInt(document.getElementById("pse-task-count").innerText) + 1;
- metaLayer.reflect();
-
- // Simulate calibration update
- calibrator.update({ input_tokens: 100, complexity_score: 0.5 }, 0.06);
- if (Math.random() > 0.95) l402Client.fetchWithL402("http://localhost:8080/api/cost-estimate");
- }
-
- metaLayer.track(startTime);
-}
-
async function init() {
clock = new THREE.Clock();
playerPos = new THREE.Vector3(0, 2, 12);
@@ -658,7 +94,6 @@ async function init() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x050510, 0.012);
- setupGOFAI();
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.copy(playerPos);
@@ -1512,91 +947,14 @@ function createPortal(config) {
scene.add(group);
- const portalObj = {
+ return {
config,
group,
ring,
swirl,
pSystem,
- light,
- customElements: {}
+ light
};
-
- // ═══ DISTINCT VISUAL IDENTITIES ═══
- if (config.id === 'archive') {
- // Floating Data Cubes
- const cubes = [];
- for (let i = 0; i < 6; i++) {
- const cubeGeo = new THREE.BoxGeometry(0.4, 0.4, 0.4);
- const cubeMat = new THREE.MeshStandardMaterial({
- color: portalColor,
- emissive: portalColor,
- emissiveIntensity: 1.5,
- transparent: true,
- opacity: 0.8
- });
- const cube = new THREE.Mesh(cubeGeo, cubeMat);
- group.add(cube);
- cubes.push(cube);
- }
- portalObj.customElements.cubes = cubes;
- } else if (config.id === 'chapel') {
- // Glowing Core + Halo
- const coreGeo = new THREE.SphereGeometry(1.2, 32, 32);
- const coreMat = new THREE.MeshPhysicalMaterial({
- color: 0xffffff,
- emissive: portalColor,
- emissiveIntensity: 2,
- transparent: true,
- opacity: 0.4,
- transmission: 0.9,
- thickness: 2
- });
- const core = new THREE.Mesh(coreGeo, coreMat);
- core.position.y = 3.5;
- group.add(core);
- portalObj.customElements.core = core;
-
- const haloGeo = new THREE.TorusGeometry(3.5, 0.05, 16, 100);
- const haloMat = new THREE.MeshBasicMaterial({ color: portalColor, transparent: true, opacity: 0.3 });
- const halo = new THREE.Mesh(haloGeo, haloMat);
- halo.position.y = 3.5;
- group.add(halo);
- portalObj.customElements.halo = halo;
- } else if (config.id === 'courtyard') {
- // Double Rotating Rings
- const outerRingGeo = new THREE.TorusGeometry(4.2, 0.1, 16, 80);
- const outerRingMat = new THREE.MeshStandardMaterial({
- color: portalColor,
- emissive: portalColor,
- emissiveIntensity: 0.8,
- transparent: true,
- opacity: 0.5
- });
- const outerRing = new THREE.Mesh(outerRingGeo, outerRingMat);
- outerRing.position.y = 3.5;
- group.add(outerRing);
- portalObj.customElements.outerRing = outerRing;
- } else if (config.id === 'gate') {
- // Spiky Monoliths
- const spikes = [];
- for (let i = 0; i < 8; i++) {
- const spikeGeo = new THREE.ConeGeometry(0.2, 1.5, 4);
- const spikeMat = new THREE.MeshStandardMaterial({ color: 0x111111, emissive: portalColor, emissiveIntensity: 0.5 });
- const spike = new THREE.Mesh(spikeGeo, spikeMat);
- const angle = (i / 8) * Math.PI * 2;
- spike.position.set(Math.cos(angle) * 3.5, 3.5 + Math.sin(angle) * 3.5, 0);
- spike.rotation.z = angle + Math.PI / 2;
- group.add(spike);
- spikes.push(spike);
- }
- portalObj.customElements.spikes = spikes;
-
- // Darker Swirl
- swirl.material.uniforms.uColor.value = new THREE.Color(0x220000);
- }
-
- return portalObj;
}
// ═══ PARTICLES ═══
@@ -1800,14 +1158,10 @@ function setupControls() {
input.focus();
}
}
- if (e.key.toLowerCase() === 'm' && document.activeElement !== document.getElementById('chat-input')) {
- openPortalAtlas();
- }
if (e.key === 'Escape') {
document.getElementById('chat-input').blur();
if (portalOverlayActive) closePortalOverlay();
if (visionOverlayActive) closeVisionOverlay();
- if (atlasOverlayActive) closePortalAtlas();
}
if (e.key.toLowerCase() === 'v' && document.activeElement !== document.getElementById('chat-input')) {
cycleNavMode();
@@ -1876,44 +1230,18 @@ function setupControls() {
chatOpen = !chatOpen;
document.getElementById('chat-panel').classList.toggle('collapsed', !chatOpen);
});
- document.getElementById('chat-send').addEventListener('click', () => sendChatMessage());
-
- // Chat quick actions
- document.getElementById('chat-quick-actions').addEventListener('click', (e) => {
- const btn = e.target.closest('.quick-action-btn');
- if (!btn) return;
-
- const action = btn.dataset.action;
-
- switch(action) {
- case 'status':
- sendChatMessage("Timmy, what is the current system status?");
- break;
- case 'agents':
- sendChatMessage("Timmy, check on all active agents.");
- break;
- case 'portals':
- openPortalAtlas();
- break;
- case 'help':
- sendChatMessage("Timmy, I need assistance with Nexus navigation.");
- break;
- }
- });
+ document.getElementById('chat-send').addEventListener('click', sendChatMessage);
document.getElementById('portal-close-btn').addEventListener('click', closePortalOverlay);
document.getElementById('vision-close-btn').addEventListener('click', closeVisionOverlay);
-
- document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
- document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
}
-function sendChatMessage(overrideText = null) {
+function sendChatMessage() {
const input = document.getElementById('chat-input');
- const text = overrideText || input.value.trim();
+ const text = input.value.trim();
if (!text) return;
addChatMessage('user', text);
- if (!overrideText) input.value = '';
+ input.value = '';
setTimeout(() => {
const responses = [
'Processing your request through the harness...',
@@ -2192,86 +1520,6 @@ function closeVisionOverlay() {
document.getElementById('vision-overlay').style.display = 'none';
}
-// ═══ PORTAL ATLAS ═══
-function openPortalAtlas() {
- atlasOverlayActive = true;
- document.getElementById('atlas-overlay').style.display = 'flex';
- populateAtlas();
-}
-
-function closePortalAtlas() {
- atlasOverlayActive = false;
- document.getElementById('atlas-overlay').style.display = 'none';
-}
-
-function populateAtlas() {
- const grid = document.getElementById('atlas-grid');
- grid.innerHTML = '';
-
- let onlineCount = 0;
- let standbyCount = 0;
-
- portals.forEach(portal => {
- const config = portal.config;
- if (config.status === 'online') onlineCount++;
- if (config.status === 'standby') standbyCount++;
-
- const card = document.createElement('div');
- card.className = 'atlas-card';
- card.style.setProperty('--portal-color', config.color);
-
- const statusClass = `status-${config.status || 'online'}`;
-
- card.innerHTML = `
-
- ${config.description}
-
- `;
-
- card.addEventListener('click', () => {
- focusPortal(portal);
- closePortalAtlas();
- });
-
- grid.appendChild(card);
- });
-
- document.getElementById('atlas-online-count').textContent = onlineCount;
- document.getElementById('atlas-standby-count').textContent = standbyCount;
-
- // Update Bannerlord HUD status
- const bannerlord = portals.find(p => p.config.id === 'bannerlord');
- if (bannerlord) {
- const statusEl = document.getElementById('bannerlord-status');
- statusEl.className = 'hud-status-item ' + (bannerlord.config.status || 'offline');
- }
-}
-
-function focusPortal(portal) {
- // Teleport player to a position in front of the portal
- const offset = new THREE.Vector3(0, 0, 6).applyEuler(new THREE.Euler(0, portal.config.rotation?.y || 0, 0));
- playerPos.copy(portal.group.position).add(offset);
- playerPos.y = 2; // Keep at eye level
-
- // Rotate player to face the portal
- playerRot.y = (portal.config.rotation?.y || 0) + Math.PI;
- playerRot.x = 0;
-
- addChatMessage('system', `Navigation focus: ${portal.config.name}`);
-
- // If in orbit mode, reset target
- if (NAV_MODES[navModeIdx] === 'orbit') {
- orbitState.target.copy(portal.group.position);
- orbitState.target.y = 3.5;
- }
-}
-
// ═══ GAME LOOP ═══
let lastThoughtTime = 0;
let pulseTimer = 0;
@@ -2387,38 +1635,6 @@ function gameLoop() {
}
// Pulse light
portal.light.intensity = 1.5 + Math.sin(elapsed * 2) * 0.5;
-
- // Custom animations for distinct identities
- if (portal.config.id === 'archive' && portal.customElements.cubes) {
- portal.customElements.cubes.forEach((cube, i) => {
- cube.rotation.x += delta * (0.5 + i * 0.1);
- cube.rotation.y += delta * (0.3 + i * 0.1);
- const orbitSpeed = 0.5 + i * 0.2;
- const orbitRadius = 4 + Math.sin(elapsed * 0.5 + i) * 0.5;
- cube.position.x = Math.cos(elapsed * orbitSpeed + i) * orbitRadius;
- cube.position.z = Math.sin(elapsed * orbitSpeed + i) * orbitRadius;
- cube.position.y = 3.5 + Math.sin(elapsed * 1.2 + i) * 1.5;
- });
- }
-
- if (portal.config.id === 'chapel' && portal.customElements.halo) {
- portal.customElements.halo.rotation.z -= delta * 0.2;
- portal.customElements.halo.scale.setScalar(1 + Math.sin(elapsed * 0.8) * 0.05);
- portal.customElements.core.material.emissiveIntensity = 2 + Math.sin(elapsed * 3) * 1;
- }
-
- if (portal.config.id === 'courtyard' && portal.customElements.outerRing) {
- portal.customElements.outerRing.rotation.z -= delta * 0.5;
- portal.customElements.outerRing.rotation.y = Math.cos(elapsed * 0.4) * 0.2;
- }
-
- if (portal.config.id === 'gate' && portal.customElements.spikes) {
- portal.customElements.spikes.forEach((spike, i) => {
- const s = 1 + Math.sin(elapsed * 2 + i) * 0.2;
- spike.scale.set(s, s, s);
- });
- }
-
// Animate particles
const positions = portal.pSystem.geometry.attributes.position.array;
for (let i = 0; i < positions.length / 3; i++) {