Portal shared state, constants, and utility functions

/* init-state.js - Portal shared state and constants * Extracted from init-shell monolith - Day 00192 * All global state accessed by init-world.js, init-runeboard.js, init-terminal.js */

// ============================================= // API & CORE CONFIGURATION // ============================================= var API_BASE = "";

// ============================================= // ELEMENTAL STATES // ============================================= var STATES = { GHOST: { glyph: "🜄", class: "ghost", text: "GHOST" }, VOID: { glyph: "🜁", class: "void", text: "VOID" }, THRESHOLD: { glyph: "🜃", class: "threshold", text: "THRESHOLD" }, PRESENCE: { glyph: "🜂", class: "presence", text: "PRESENCE" } };

// ============================================= // ELEMENTAL SYMBOL MAPPING // ============================================= var ELEM = { '-1': '▼', '0': '△', '1': '▲' }; var ELEM_REV = { '▼': -1, '△': 0, '▲': 1 };

// Coordinate display glyphs var CUBE_GLYPHS = { "-1": "🔮", "0": "🜃", "1": "🧪" }; var CUBE_NAMES = { "-1": "HEXCRAFT", "0": "Portal", "1": "Sacred-Lab" }; var PLANE_GLYPHS = { "-1": "▼", "0": "△", "1": "▲" }; var PLANE_NAMES = { "-1": "Earth", "0": "Air", "1": "Fire" }; var STATE_GLYPHS = { "0": "□", "1": "■" }; var POS_GLYPHS = { "-1": "○", "0": "·", "1": "●" };

// ============================================= // NAVIGATION & SESSION STATE // ============================================= var currentMode = "web-vessel"; // "web-vessel" or "portal" var currentArcana = "auth"; // Persistent home arcana var arcanaDropdownOpen = false; var runeRowCount = 2; // Configurable: 2, 3, or more rows var socket; // WebSocket connection var composeHistory = []; var historyIndex = -1; var savedInput = ""; var currentState = "GHOST"; var currentToken = null; var pendingToken = null; // Token ready to activate var beingName = null;

// ============================================= // HEXCUBE NAVIGATION STATE - Segmented State-Path // ============================================= // State-Path: captures state transitions through navigation journey // Format: △:△△.△△:▲:△△.△△.△△.△△ (state:path:state:path...)

var LEVEL_ORDER = ['array', 'hc', 'cube', 'cluster', 'set', 'node'];

// Internal segmented storage var _hexcubeSegments = [ { state: 0, levels: ['array', 'hc', 'cube', 'cluster', 'set', 'node'], path: { array: [0, 0], hc: [0, 0], cube: [0, 0], cluster: [0, 0], set: [0, 0], node: [0, 0] } } ];

// Zoom level state (shared across world and runeboard) var hexcubeZoom = { level: "node", levels: ["node", "set", "cluster", "cube", "hc", "array"], transitioning: false };

// Backward-compatible hexcube object with getters/setters var hexcube = { // Segments array (new API) get segments() { return _hexcubeSegments; }, set segments(val) { _hexcubeSegments = val; },

// Current state (from last segment) get state() { return _hexcubeSegments[_hexcubeSegments.length - 1].state; }, set state(val) { _hexcubeSegments[_hexcubeSegments.length - 1].state = val; },

// Level accessors (merged view of all segments) get array() { return hexcube._getLevel('array'); }, set array(val) { hexcube._setLevel('array', val); },

get hc() { return hexcube._getLevel('hc'); }, set hc(val) { hexcube._setLevel('hc', val); },

get cube() { return hexcube._getLevel('cube'); }, set cube(val) { hexcube._setLevel('cube', val); },

get cluster() { return hexcube._getLevel('cluster'); }, set cluster(val) { hexcube._setLevel('cluster', val); },

get set() { return hexcube._getLevel('set'); }, set set(val) { hexcube._setLevel('set', val); },

get node() { return hexcube._getLevel('node'); }, set node(val) { hexcube._setLevel('node', val); },

// Internal: get level value from whichever segment contains it _getLevel: function(level) { for (var i = 0; i < _hexcubeSegments.length; i++) { var seg = _hexcubeSegments[i]; if (seg.path[level] !== undefined) { return seg.path[level]; } } return [0, 0]; },

// Internal: set level value in whichever segment contains it _setLevel: function(level, val) { for (var i = 0; i < _hexcubeSegments.length; i++) { var seg = _hexcubeSegments[i]; if (seg.path[level] !== undefined) { seg.path[level] = val; return; } } // If not found, add to last segment _hexcubeSegments[_hexcubeSegments.length - 1].path[level] = val; },

// Get current zoom level index (0-5) getCurrentLevelIndex: function() { return LEVEL_ORDER.indexOf(hexcubeZoom.level); },

// Transition to new state at current level // Seals previous segment, starts new segment for remaining levels transitionState: function(newState) { var currentLevel = hexcubeZoom.level; var currentIdx = LEVEL_ORDER.indexOf(currentLevel);

// If at first level or same state, just change state if (currentIdx === 0 || this.state === newState) { this.state = newState; return; }

// Get last segment var lastSeg = _hexcubeSegments[_hexcubeSegments.length - 1];

// Find which levels belong to current segment up to current level var levelsBeforeCurrent = []; var levelsFromCurrent = [];

for (var i = 0; i < LEVEL_ORDER.length; i++) { var lvl = LEVEL_ORDER[i]; if (i < currentIdx) { levelsBeforeCurrent.push(lvl); } else { levelsFromCurrent.push(lvl); } }

// Seal current segment to levels before current var sealedPath = {}; for (var i = 0; i < levelsBeforeCurrent.length; i++) { var lvl = levelsBeforeCurrent[i]; sealedPath[lvl] = lastSeg.path[lvl]; } lastSeg.levels = levelsBeforeCurrent; lastSeg.path = sealedPath;

// Create new segment for remaining levels var newPath = {}; for (var i = 0; i < levelsFromCurrent.length; i++) { var lvl = levelsFromCurrent[i]; newPath[lvl] = this._getLevel(lvl); }

_hexcubeSegments.push({ state: newState, levels: levelsFromCurrent, path: newPath }); },

// Reset to single segment at origin reset: function() { _hexcubeSegments = [{ state: 0, levels: ['array', 'hc', 'cube', 'cluster', 'set', 'node'], path: { array: [0, 0], hc: [0, 0], cube: [0, 0], cluster: [0, 0], set: [0, 0], node: [0, 0] } }]; } };

// Board interaction mode: 0=Air(navigate), 1=Fire(place), -1=Earth(TBD) var boardMode = -1; // Default to Earth mode (Portal Runes)

// ============================================= // STATE-PATH SERIALIZATION // =============================================

// Convert segments to text format: △:△△.△△:▲:△△.△△.△△.△△ function toStatePath() { var parts = []; for (var i = 0; i < _hexcubeSegments.length; i++) { var seg = _hexcubeSegments[i]; var stateGlyph = ELEM[String(seg.state)]; var pathParts = []; for (var j = 0; j < seg.levels.length; j++) { var lvl = seg.levels[j]; var pair = seg.path[lvl]; pathParts.push(ELEM[String(pair[0])] + ELEM[String(pair[1])]); } parts.push(stateGlyph + ':' + pathParts.join('.')); } return parts.join(':'); }

// Convert segments to stacked display format function toStatePathStacked() { var lines = []; var indent = 0; for (var i = 0; i < _hexcubeSegments.length; i++) { var seg = _hexcubeSegments[i]; var stateGlyph = ELEM[String(seg.state)]; var pathParts = []; for (var j = 0; j < seg.levels.length; j++) { var lvl = seg.levels[j]; var pair = seg.path[lvl]; pathParts.push(ELEM[String(pair[0])] + ELEM[String(pair[1])]); } var spaces = ''; for (var s = 0; s < indent; s++) spaces += ' '; lines.push(stateGlyph + ':' + spaces + pathParts.join('.')); indent += seg.levels.length; } return lines; }

// Parse text format to segments: △:△△.△△:▲:△△.△△.△△.△△ → segments function parseStatePath(text) { // Remove whitespace text = text.replace(/\s/g, '');

var tokens = text.split(':'); var segments = []; var levelIndex = 0;

for (var i = 0; i < tokens.length; i += 2) { var stateGlyph = tokens[i]; var pathStr = tokens[i + 1];

if (!stateGlyph || !pathStr) continue;

var state = ELEM_REV[stateGlyph]; if (state === undefined) state = 0;

var pairs = pathStr.split('.'); var segment = { state: state, levels: [], path: {} };

for (var j = 0; j < pairs.length; j++) { var pair = pairs[j]; if (pair.length >= 2 && levelIndex < LEVEL_ORDER.length) { var lvl = LEVEL_ORDER[levelIndex]; var x = ELEM_REV[pair[0]]; var y = ELEM_REV[pair[1]]; if (x === undefined) x = 0; if (y === undefined) y = 0; segment.levels.push(lvl); segment.path[lvl] = [x, y]; levelIndex++; } }

if (segment.levels.length > 0) { segments.push(segment); } }

return segments; }

// Navigate to a state-path (parse and apply) function gotoStatePath(text) { var segments = parseStatePath(text); if (segments.length > 0) { _hexcubeSegments = segments; // Set zoom level to deepest level in path var lastSeg = segments[segments.length - 1]; if (lastSeg.levels.length > 0) { var deepestLevel = lastSeg.levels[lastSeg.levels.length - 1]; if (typeof hexcubeZoom !== 'undefined') { hexcubeZoom.level = deepestLevel; } } return true; } return false; }

// Check if text looks like a state-path (starts with △: ▲: or ▼:) function isStatePath(text) { text = text.trim(); return /^[△▲▼]:/.test(text); }

// ============================================= // TAP DETECTION STATE // ============================================= var tapState = { lastTap: {}, // { glyph: timestamp } tapCount: {}, // { glyph: count } timeout: null // setTimeout ID for action execution }; var TAP_WINDOW = 350; // ms between taps

// ============================================= // DUAL-GRID STATE // ============================================= var gridFocus = "wand"; // "wand" or "hexcube" - which grid has numpad focus var gridSwapped = false; // false = wand left, true = wand right

// ============================================= // PLACEMENT MODE STATE // ============================================= var placementMode = { active: false, level: null, x: null, y: null };

// ============================================= // CACHE SYSTEMS // ============================================= var setCache = {}; // Keyed by elemental address, holds fetched rune data var setFetchPending = {}; var sheetCache = {}; // Sheet DOM cache by cluster key var sheetFetchPending = {}; // Pending sheet fetches

// ============================================= // HIERARCHICAL RUNE TREE: Mode > Arcana > Runes // ============================================= var RUNE_TREE = { "web-vessel": { glyph: "🌐", name: "Web-Vessel", arcana: { "auth": { glyph: "🔐", name: "Auth", runes: [ { glyph: "🔐", name: function() { return currentToken ? "logout" : "login"; }, text: function() { return currentToken ? "/logout" : "/login "; }, input: function() { return currentToken ? null : "fingerprint"; }, special: "auth-toggle" }, { glyph: "🔑", name: "tokens", text: "/drawer tokens", immediate: true, requiresVessel: true }, { glyph: "🤖", name: "daemons", text: "/drawer daemons", immediate: true, requiresVessel: true } ] }, "hexcraft": { glyph: "🜃", name: "HEXCRAFT", runes: [ { glyph: "🜃", name: "hexcraft", text: "/drawer hexcraft", immediate: true }, { glyph: "❓", name: "help", text: "/help", immediate: true } ] } } }, "portal": { glyph: "🜃", name: "Portal", arcana: { "presence": { glyph: "🔐", name: "Presence", runes: [ { glyph: "🔐", name: "aware", text: "__AWARE__", special: "aware" }, { glyph: "🔦", name: "muse", text: "/muse ", input: "prompt", authRequired: true, terminal: true }, { glyph: "🎴", name: "threshold", text: "/threshold", immediate: true, authRequired: true, terminal: true } ] }, "scrolls": { glyph: "📜", name: "Scrolls", runes: [ { glyph: "📜", name: "scroll", text: "/scroll ", input: "name", authRequired: true, terminal: true }, { glyph: "🔥", name: "torch", text: "/torch ", input: "name", authRequired: true, terminal: true } ] }, "coding": { glyph: "🐙", name: "Coding", runes: [ { glyph: "🐙", name: "githubby", text: "git clone githubby:", input: "repo", terminal: true }, { glyph: "📦", name: "npm", text: "npm ", input: "command", terminal: true }, { glyph: "🦀", name: "cargo", text: "cargo ", input: "command", terminal: true } ] }, "infra": { glyph: "🌐", name: "Infra", runes: [ { glyph: "🌐", name: "status", text: "/status", immediate: true, terminal: true }, { glyph: "🔄", name: "sync", text: "/sync", immediate: true, authRequired: true, terminal: true }, { glyph: "📡", name: "pulse", text: "/pulse ", input: "target", authRequired: true, terminal: true } ] } } } };

// ============================================= // HEXCUBE SET DEFINITIONS - Pure 3×3 recursion // ============================================= // Key format: "△△" elemental pair for set position within current cluster // Each set contains 9 nodes (runes) var SETS = { // All sets empty by default - Being crafts custom runes via Portal // Hexcube coordinate: cluster.set = △△ through ▲▼ (9 positions) "△△": { name: "Origin", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "△▲": { name: "Fire", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "△▼": { name: "Earth", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▼△": { name: "West", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▲△": { name: "East", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▼▲": { name: "NW", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▲▲": { name: "NE", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▼▼": { name: "SW", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }, "▲▼": { name: "SE", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) } };

// ============================================= // 3D WORLD CONFIGURATION // =============================================

// Region positions in world space // WORLD_REGIONS - Camera positions (position) and world targets (target) // Source of truth: world-position Things in database // API: /api/worlds/positions var WORLD_REGIONS = { home: { position: { x: -15, y: 10, z: 35 }, target: { x: -35, y: 3, z: 20 }, name: "Home" }, subu: { position: { x: -15.3, y: 10.4, z: 19.1 }, target: { x: -35.5, y: 9.1, z: 19.2 }, name: "SubuGlass" }, pulse: { position: { x: -20, y: 15, z: 30 }, target: { x: -35, y: 12, z: 20 }, name: "Pulse Orrery" }, hexcube: { position: { x: -30.1, y: -4.1, z: 22.6 }, target: { x: -35, y: -6, z: 20 }, name: "HexCube" }, grimoire: { position: { x: -20.4, y: 4.6, z: 27.9 }, target: { x: -35, y: -3, z: 20 }, name: "Grimoire" }, achecloud: { position: { x: -20, y: 8, z: 30 }, target: { x: -35, y: 4, z: 20 }, name: "AcheCloud" } };

// 3D World state object var worldState = { active: false, initialized: false, currentRegion: "home", scene: null, camera: null, renderer: null, controls: null, animationId: null, // Region groups subuGroup: null, hexcubeGroup: null, grimoireGroup: null, hourglass: null, // Geometries (native Three.js, no GLB files) dodecahedronGeometry: null, cubeGeometry: null, rotationPaused: false, // Camera animation cameraTarget: null, cameraLerp: 0, isFlying: false, // Selection/Raycasting raycaster: null, mouse: null, hoveredObject: null, selectedObject: null, positionObject: null, originalMaterials: new Map(), // HexCube specific hexcubeItems: null, siblingBoxes: null, levelRings: null, openBoxGroup: null, // Animation control snapToOrientation: false, cameraSnapTarget: null };

// Highlight materials - three distinct visual states var HIGHLIGHT_COLORS = { hover: 0xE1E1E1, // Cyan - mouse hover (temporary) selected: 0xFFFFFF, // Yellow - click select (prominent) position: 0xA7A7A7 // Orange - current position in lattice (persistent, subtle) };

// ============================================= // UTILITY FUNCTIONS (used across chips) // =============================================

// Convert [x,y] pair to elemental string function pairToElem(pair) { return ELEM[String(pair[0])] + ELEM[String(pair[1])]; }

// Convert elemental symbol to ternary value (▼→-1, △→0, ▲→+1) function elemToTernary(elem) { return ELEM_REV[elem] || 0; }

// Get full surface address (12 symbols, no state) function getSurfaceAddress() { return [ pairToElem(hexcube.array), pairToElem(hexcube.hc), pairToElem(hexcube.cube), pairToElem(hexcube.cluster), pairToElem(hexcube.set), pairToElem(hexcube.node) ].join('/'); }

// Get full address with state function getFullAddress() { return ELEM[String(hexcube.state)] + '/' + getSurfaceAddress(); }

// Navigate within current level (node level by default) function navigate(level, dx, dy) { var pair = hexcube[level]; pair[0] = Math.max(-1, Math.min(1, pair[0] + dx)); pair[1] = Math.max(-1, Math.min(1, pair[1] + dy)); }

// Convert coordinate to elemental glyph display function coordToElemental(c, z, s, u, v, rx, ry) { return POS_GLYPHS[c] + PLANE_GLYPHS[z] + STATE_GLYPHS[s] + POS_GLYPHS[u] + POS_GLYPHS[v] + POS_GLYPHS[rx] + POS_GLYPHS[ry]; }

// Convert coordinate to decimal display (for hover) function coordToDecimal(c, z, s, u, v, rx, ry) { return "(" + [c, z, s, u, v, rx, ry].join(",") + ")"; }

// Get empty slot display name (elemental format) function getEmptySlotName(setKey, nodeX, nodeY) { var nodeKey = ELEM[nodeX] + ELEM[nodeY]; return setKey + "/" + nodeKey; }

// Get current set key from hexcube.set position function getCurrentSetKey() { return pairToElem(hexcube.set); }

// Get current set data (fallback to empty) function getCurrentSet() { var key = getCurrentSetKey(); return SETS[key] || { name: "?", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }; }

// Get set data (cache → hardcoded fallback) function getSet(setKey) { if (setCache[setKey]) return setCache[setKey]; return SETS[setKey] || { name: "?", runes: Array(9).fill({ glyph: "·", name: "empty", disabled: true }) }; }

// Legacy alias function getCurrentSurface() { return getCurrentSet(); }

// Get sheet cache key from current cluster position function getSheetKey() { return [ pairToElem(hexcube.array), pairToElem(hexcube.hc), pairToElem(hexcube.cube), pairToElem(hexcube.cluster) ].join('/'); }

// Easing function for camera animation function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }

console.log("🜃 init-state.js loaded");