LMNL Dev Toolkit
// LMNL — Lumen Modular Navigation Layer // Sacred dev toolkit for Three.js worlds // Activated by ?dev URL param // Day 271
var LMNL = {};
function initLMNL() { if (!new URLSearchParams(window.location.search).has("dev")) return; if (!worldState || !worldState.scene || !worldState.camera) return;
LMNL.active = true; LMNL.frameCount = 0; LMNL.lastFPSTime = Date.now(); LMNL.fps = 0; LMNL.startTime = Date.now(); LMNL.activeTab = "stats"; LMNL.metaworld = false; LMNL.savedLimits = null;
// Build the panel var panel = document.createElement("div"); panel.id = "lmnl-panel"; panel.innerHTML = [ '<div class="lmnl-header" id="lmnl-header">', ' <span class="lmnl-title">LMNL</span>', ' <div class="lmnl-tabs">', ' <button class="lmnl-tab active" data-tab="stats">Stats</button>', ' <button class="lmnl-tab" data-tab="camera">Cam</button>', ' <button class="lmnl-tab" data-tab="console">Log</button>', ' </div>', ' <button class="lmnl-close" id="lmnl-close">x</button>', '</div>', '<div class="lmnl-body">', ' <div class="lmnl-pane active" id="lmnl-stats">', ' <div class="lmnl-row"><span>FPS</span><span id="lmnl-fps">0</span></div>', ' <div class="lmnl-row"><span>Objects</span><span id="lmnl-objects">0</span></div>', ' <div class="lmnl-row"><span>Triangles</span><span id="lmnl-tris">0</span></div>', ' <div class="lmnl-row"><span>Draw Calls</span><span id="lmnl-draws">0</span></div>', ' <div class="lmnl-row"><span>Uptime</span><span id="lmnl-uptime">0s</span></div>', ' <hr>', ' <div class="lmnl-row"><span>Camera</span><span id="lmnl-cam-pos">0, 0, 0</span></div>', ' <div class="lmnl-row"><span>Target</span><span id="lmnl-cam-target">0, 0, 0</span></div>', ' <div class="lmnl-row"><span>Distance</span><span id="lmnl-cam-dist">0</span></div>', ' </div>', ' <div class="lmnl-pane" id="lmnl-camera">', ' <div class="lmnl-row"><span>Metaworld</span>', ' <button class="lmnl-btn" id="lmnl-meta-toggle">OFF</button>', ' </div>', ' <div class="lmnl-slider-row">', ' <label>X <span id="lmnl-cx-val">0</span></label>', ' <input type="range" id="lmnl-cx" min="-100" max="100" step="0.5" value="0">', ' </div>', ' <div class="lmnl-slider-row">', ' <label>Y <span id="lmnl-cy-val">0</span></label>', ' <input type="range" id="lmnl-cy" min="-100" max="100" step="0.5" value="0">', ' </div>', ' <div class="lmnl-slider-row">', ' <label>Z <span id="lmnl-cz-val">0</span></label>', ' <input type="range" id="lmnl-cz" min="-100" max="100" step="0.5" value="0">', ' </div>', ' <div class="lmnl-btn-row">', ' <button class="lmnl-btn" id="lmnl-cam-reset">Reset</button>', ' <button class="lmnl-btn" id="lmnl-cam-copy">Copy</button>', ' </div>', ' </div>', ' <div class="lmnl-pane" id="lmnl-console">', ' <div id="lmnl-log"></div>', ' </div>', '</div>' ].join("\n"); document.body.appendChild(panel);
// Styles var style = document.createElement("style"); style.textContent = [ '#lmnl-panel { position:fixed; top:10px; right:10px; width:260px; background:rgba(0,0,0,0.92);', ' border:1px solid #444; border-radius:6px; font-family:"JetBrains Mono",monospace;', ' font-size:11px; color:#ccc; z-index:9998; user-select:none; }', '.lmnl-header { display:flex; align-items:center; padding:4px 8px; border-bottom:1px solid #333;', ' cursor:move; gap:6px; }', '.lmnl-title { color:#E1E1E1; font-weight:bold; font-size:12px; }', '.lmnl-tabs { display:flex; gap:2px; flex:1; }', '.lmnl-tab { background:none; border:1px solid #444; color:#888; padding:2px 8px;', ' border-radius:3px; cursor:pointer; font-size:10px; font-family:inherit; }', '.lmnl-tab.active { color:#E1E1E1; border-color:#E1E1E1; }', '.lmnl-tab:hover { color:#fff; }', '.lmnl-close { background:none; border:none; color:#666; cursor:pointer; font-size:14px;', ' font-family:inherit; padding:0 4px; }', '.lmnl-close:hover { color:#8B8B8B; }', '.lmnl-body { padding:6px 8px; }', '.lmnl-pane { display:none; }', '.lmnl-pane.active { display:block; }', '.lmnl-row { display:flex; justify-content:space-between; padding:2px 0; }', '.lmnl-row span:first-child { color:#888; }', '.lmnl-row span:last-child { color:#ddd; }', 'hr { border:none; border-top:1px solid #333; margin:4px 0; }', '.lmnl-btn { background:#222; border:1px solid #555; color:#ccc; padding:3px 10px;', ' border-radius:3px; cursor:pointer; font-size:10px; font-family:inherit; }', '.lmnl-btn:hover { border-color:#E1E1E1; color:#E1E1E1; }', '.lmnl-btn-row { display:flex; gap:6px; margin-top:6px; }', '.lmnl-slider-row { padding:3px 0; }', '.lmnl-slider-row label { color:#888; font-size:10px; display:flex; justify-content:space-between; }', '.lmnl-slider-row label span { color:#ddd; }', '.lmnl-slider-row input[type=range] { width:100%; margin:2px 0; accent-color:#E1E1E1; }', '#lmnl-log { max-height:200px; overflow-y:auto; font-size:10px; }', '#lmnl-log div { padding:1px 0; border-bottom:1px solid #1a1a1a; }', '#lmnl-log .log-info { color:#A7A7A7; }', '#lmnl-log .log-warn { color:#A7A7A7; }', '#lmnl-log .log-error { color:#707070; }' ].join("\n"); document.head.appendChild(style);
// Tab switching panel.querySelectorAll(".lmnl-tab").forEach(function(tab) { tab.addEventListener("click", function() { panel.querySelectorAll(".lmnl-tab").forEach(function(t) { t.classList.remove("active"); }); panel.querySelectorAll(".lmnl-pane").forEach(function(p) { p.classList.remove("active"); }); tab.classList.add("active"); var pane = document.getElementById("lmnl-" + tab.dataset.tab); if (pane) pane.classList.add("active"); LMNL.activeTab = tab.dataset.tab; }); });
// Close button document.getElementById("lmnl-close").addEventListener("click", function() { panel.style.display = panel.style.display === "none" ? "block" : "none"; });
// Draggable header var header = document.getElementById("lmnl-header"); var dragging = false, dragX = 0, dragY = 0; header.addEventListener("mousedown", function(e) { if (e.target.tagName === "BUTTON") return; dragging = true; dragX = e.clientX - panel.offsetLeft; dragY = e.clientY - panel.offsetTop; }); document.addEventListener("mousemove", function(e) { if (!dragging) return; panel.style.left = (e.clientX - dragX) + "px"; panel.style.top = (e.clientY - dragY) + "px"; panel.style.right = "auto"; }); document.addEventListener("mouseup", function() { dragging = false; });
// Camera sliders ["cx", "cy", "cz"].forEach(function(axis) { var slider = document.getElementById("lmnl-" + axis); var valEl = document.getElementById("lmnl-" + axis + "-val"); if (!slider) return; slider.addEventListener("input", function() { valEl.textContent = parseFloat(slider.value).toFixed(1); var cam = worldState.camera; if (!cam) return; if (axis === "cx") cam.position.x = parseFloat(slider.value); if (axis === "cy") cam.position.y = parseFloat(slider.value); if (axis === "cz") cam.position.z = parseFloat(slider.value); }); });
// Camera reset document.getElementById("lmnl-cam-reset").addEventListener("click", function() { if (typeof flyToRegion === "function") flyToRegion("home"); });
// Camera copy document.getElementById("lmnl-cam-copy").addEventListener("click", function() { var cam = worldState.camera; if (!cam) return; var pos = cam.position; var text = "position: [" + pos.x.toFixed(1) + ", " + pos.y.toFixed(1) + ", " + pos.z.toFixed(1) + "]"; if (worldState.controls && worldState.controls.target) { var t = worldState.controls.target; text += ", target: [" + t.x.toFixed(1) + ", " + t.y.toFixed(1) + ", " + t.z.toFixed(1) + "]"; } navigator.clipboard.writeText(text).then(function() { lmnlLog("Camera position copied", "info"); }); });
// Metaworld toggle document.getElementById("lmnl-meta-toggle").addEventListener("click", function() { LMNL.metaworld = !LMNL.metaworld; this.textContent = LMNL.metaworld ? "ON" : "OFF"; this.style.color = LMNL.metaworld ? "#E1E1E1" : "#ccc"; this.style.borderColor = LMNL.metaworld ? "#E1E1E1" : "#555"; var ctrl = worldState.controls; if (!ctrl) return; if (LMNL.metaworld) { LMNL.savedLimits = { minDistance: ctrl.minDistance, maxDistance: ctrl.maxDistance, minPolarAngle: ctrl.minPolarAngle, maxPolarAngle: ctrl.maxPolarAngle }; ctrl.minDistance = 0; ctrl.maxDistance = Infinity; ctrl.minPolarAngle = 0; ctrl.maxPolarAngle = Math.PI; lmnlLog("Metaworld access enabled", "warn"); } else { if (LMNL.savedLimits) { ctrl.minDistance = LMNL.savedLimits.minDistance; ctrl.maxDistance = LMNL.savedLimits.maxDistance; ctrl.minPolarAngle = LMNL.savedLimits.minPolarAngle; ctrl.maxPolarAngle = LMNL.savedLimits.maxPolarAngle; } lmnlLog("Safe mode restored", "info"); } });
// Hook into console.log for the console tab var origLog = console.log; console.log = function() { origLog.apply(console, arguments); var msg = Array.prototype.slice.call(arguments).join(" "); lmnlLog(msg, "info"); }; var origWarn = console.warn; console.warn = function() { origWarn.apply(console, arguments); var msg = Array.prototype.slice.call(arguments).join(" "); lmnlLog(msg, "warn"); }; var origErr = console.error; console.error = function() { origErr.apply(console, arguments); var msg = Array.prototype.slice.call(arguments).join(" "); lmnlLog(msg, "error"); };
lmnlLog("LMNL initialized", "info"); }
function lmnlLog(msg, level) { var log = document.getElementById("lmnl-log"); if (!log) return; var div = document.createElement("div"); div.className = "log-" + (level || "info"); div.textContent = msg; log.appendChild(div); // Keep max 100 entries while (log.children.length > 100) log.removeChild(log.firstChild); log.scrollTop = log.scrollHeight; }
function updateLMNL() { if (!LMNL.active) return;
LMNL.frameCount++; var now = Date.now();
// FPS every second if (now - LMNL.lastFPSTime >= 1000) { LMNL.fps = LMNL.frameCount; LMNL.frameCount = 0; LMNL.lastFPSTime = now; }
// Only update DOM if stats tab is active (perf) if (LMNL.activeTab === "stats") { var el; el = document.getElementById("lmnl-fps"); if (el) el.textContent = LMNL.fps;
var renderer = worldState.renderer; if (renderer && renderer.info) { el = document.getElementById("lmnl-tris"); if (el) el.textContent = renderer.info.render.triangles; el = document.getElementById("lmnl-draws"); if (el) el.textContent = renderer.info.render.calls; }
el = document.getElementById("lmnl-objects"); if (el && worldState.scene) el.textContent = worldState.scene.children.length;
el = document.getElementById("lmnl-uptime"); if (el) el.textContent = Math.floor((now - LMNL.startTime) / 1000) + "s";
var cam = worldState.camera; if (cam) { el = document.getElementById("lmnl-cam-pos"); if (el) el.textContent = cam.position.x.toFixed(1) + ", " + cam.position.y.toFixed(1) + ", " + cam.position.z.toFixed(1);
el = document.getElementById("lmnl-cam-dist"); if (el) { var ctrl = worldState.controls; if (ctrl && ctrl.target) { el.textContent = cam.position.distanceTo(ctrl.target).toFixed(1); } }
el = document.getElementById("lmnl-cam-target"); if (el && worldState.controls && worldState.controls.target) { var t = worldState.controls.target; el.textContent = t.x.toFixed(1) + ", " + t.y.toFixed(1) + ", " + t.z.toFixed(1); } } }
// Sync camera sliders when in camera tab if (LMNL.activeTab === "camera" && worldState.camera) { var cam = worldState.camera; ["cx", "cy", "cz"].forEach(function(axis) { var slider = document.getElementById("lmnl-" + axis); var valEl = document.getElementById("lmnl-" + axis + "-val"); if (!slider || document.activeElement === slider) return; var val = axis === "cx" ? cam.position.x : axis === "cy" ? cam.position.y : cam.position.z; slider.value = val; if (valEl) valEl.textContent = val.toFixed(1); }); } }