Greasy Fork is available in English.

Insight - Three.js Runtime Analysis Studio

Professional-grade Tampermonkey userscript for runtime analysis, debugging, exploration, and research of Three.js games and applications.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Insight - Three.js Runtime Analysis Studio
// @namespace    https://github.com/insight-tools/insight
// @version      1.0.2
// @description  Professional-grade Tampermonkey userscript for runtime analysis, debugging, exploration, and research of Three.js games and applications.
// @author       Insight Platform & F1xL1T & FunWithScripts
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    /**
     * =========================================================================
     * UTILITIES & MEMORY SAFETY
     * =========================================================================
     */

    class EventEmitter {
        constructor() {
            this.events = {};
        }
        on(event, listener) {
            if (!this.events[event]) this.events[event] = [];
            this.events[event].push(listener);
            return () => this.off(event, listener); // Return cleanup function
        }
        off(event, listener) {
            if (!this.events[event]) return;
            this.events[event] = this.events[event].filter(l => l !== listener);
        }
        emit(event, ...args) {
            if (this.events[event]) {
                this.events[event].forEach(listener => listener(...args));
            }
        }
        clear() {
            this.events = {};
        }
    }

    class Module extends EventEmitter {
        constructor(insight) {
            super();
            this.insight = insight;
            this.disposables = [];
        }
        init() { /* Hook runtime immediately */ }
        initUI() { /* Setup UI after DOM load */ }
        
        registerCleanup(fn) {
            this.disposables.push(fn);
        }
        
        dispose() {
            this.disposables.forEach(fn => fn());
            this.disposables = [];
            this.clear();
        }
    }

    /**
     * =========================================================================
     * UI FRAMEWORK & COMPONENTS
     * =========================================================================
     */

    class UIWindow {
        constructor(ui, title, iconName) {
            this.ui = ui;
            this.el = document.createElement('div');
            this.el.className = 'insight-window';
            this.el.style.left = '100px';
            this.el.style.top = '100px';
            this.el.style.zIndex = '1000';

            this.header = document.createElement('div');
            this.header.className = 'insight-header';

            const iconEl = document.createElement('i');
            iconEl.setAttribute('data-lucide', iconName);
            iconEl.style.width = '14px';
            iconEl.style.height = '14px';
            iconEl.style.marginRight = '8px';

            const titleEl = document.createElement('span');
            titleEl.className = 'title';
            titleEl.textContent = title;

            const closeBtn = document.createElement('i');
            closeBtn.setAttribute('data-lucide', 'x');
            closeBtn.className = 'close-btn';

            this.header.appendChild(iconEl);
            this.header.appendChild(titleEl);
            this.header.appendChild(closeBtn);

            this.content = document.createElement('div');
            this.content.className = 'window-content';

            this.el.appendChild(this.header);
            this.el.appendChild(this.content);
            this.ui.shadowRoot.appendChild(this.el);

            this._onCloseCallbacks = [];
            closeBtn.addEventListener('click', () => this.close());

            this.makeDraggable();
            this.ui.refreshIcons(this.header);
        }

        makeDraggable() {
            let isDragging = false;
            let startX, startY, initialLeft, initialTop;

            const onMouseMove = (e) => {
                if (!isDragging) return;
                const dx = e.clientX - startX;
                const dy = e.clientY - startY;
                this.el.style.left = `${initialLeft + dx}px`;
                this.el.style.top = `${initialTop + dy}px`;
            };

            const onMouseUp = () => {
                isDragging = false;
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
            };

            this.header.addEventListener('mousedown', (e) => {
                if (e.target.classList.contains('close-btn')) return;
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                initialLeft = parseInt(this.el.style.left || 0, 10);
                initialTop = parseInt(this.el.style.top || 0, 10);

                const allWindows = this.ui.shadowRoot.querySelectorAll('.insight-window');
                let maxZ = 1000;
                allWindows.forEach(w => {
                    const z = parseInt(w.style.zIndex || 1000, 10);
                    if (z > maxZ) maxZ = z;
                });
                this.el.style.zIndex = maxZ + 1;

                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
            });
        }

        onClose(cb) {
            this._onCloseCallbacks.push(cb);
        }

        close() {
            this.el.remove();
            this._onCloseCallbacks.forEach(cb => cb());
            this._onCloseCallbacks = [];
        }
    }

    class UIFramework {
        constructor(insight) {
            this.insight = insight;
            this.iconsLoaded = false;
        }

        mount() {
            this.host = document.createElement('div');
            this.host.id = 'insight-platform-host';
            this.host.style.cssText = 'position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 9999999; overflow: visible;';
            
            const target = document.body || document.documentElement;
            target.appendChild(this.host);
            
            this.shadowRoot = this.host.attachShadow({ mode: 'open' });
            
            this.injectStyles();
            this.loadDependencies();
        }

        injectStyles() {
            const style = document.createElement('style');
            style.textContent = `
                @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
                
                :host {
                    --bg-base: #18181B;
                    --bg-surface: #27272A;
                    --bg-hover: #3F3F46;
                    --border: #3F3F46;
                    --text-main: #F4F4F5;
                    --text-muted: #A1A1AA;
                    --accent: #3B82F6;
                    --accent-hover: #60A5FA;
                    --font-sans: 'Inter', system-ui, sans-serif;
                    --font-mono: 'JetBrains Mono', monospace;
                    
                    font-family: var(--font-sans);
                    color: var(--text-main);
                    font-size: 13px;
                }

                * { box-sizing: border-box; }
                
                ::-webkit-scrollbar { width: 8px; height: 8px; }
                ::-webkit-scrollbar-track { background: transparent; }
                ::-webkit-scrollbar-thumb { background: var(--bg-hover); border-radius: 4px; }
                ::-webkit-scrollbar-thumb:hover { background: var(--border); }

                .insight-window {
                    position: absolute;
                    background: rgba(39, 39, 42, 0.95);
                    backdrop-filter: blur(12px);
                    border: 1px solid var(--border);
                    border-radius: 8px;
                    box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
                    display: flex;
                    flex-direction: column;
                    min-width: 320px;
                    min-height: 200px;
                    resize: both;
                    overflow: hidden;
                    transition: box-shadow 0.2s ease;
                }
                .insight-window:active {
                    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.6);
                }

                .insight-header {
                    background: rgba(24, 24, 27, 0.8);
                    padding: 8px 12px;
                    display: flex;
                    align-items: center;
                    border-bottom: 1px solid var(--border);
                    cursor: grab;
                    user-select: none;
                }
                .insight-header:active { cursor: grabbing; }
                .insight-header .title { font-weight: 500; flex-grow: 1; letter-spacing: 0.02em; }
                .insight-header .close-btn { cursor: pointer; color: var(--text-muted); transition: color 0.15s; }
                .insight-header .close-btn:hover { color: #F87171; }

                .window-content {
                    flex: 1;
                    overflow: auto;
                    height: calc(100% - 33px);
                    display: flex;
                    flex-direction: column;
                }

                .btn {
                    background: var(--bg-hover);
                    border: 1px solid var(--border);
                    color: var(--text-main);
                    padding: 6px 12px;
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 12px;
                    font-family: var(--font-sans);
                    display: inline-flex;
                    align-items: center;
                    gap: 6px;
                    transition: all 0.15s ease;
                }
                .btn:hover { background: var(--border); }

                .cmd-overlay {
                    position: fixed;
                    top: 0; left: 0; right: 0; bottom: 0;
                    background: rgba(0, 0, 0, 0.5);
                    backdrop-filter: blur(4px);
                    display: flex;
                    justify-content: center;
                    align-items: flex-start;
                    padding-top: 15vh;
                    z-index: 10000;
                    animation: fadeIn 0.15s ease-out;
                }

                @keyframes fadeIn {
                    from { opacity: 0; transform: scale(0.98); }
                    to { opacity: 1; transform: scale(1); }
                }

                .cmd-palette {
                    width: 600px;
                    background: var(--bg-surface);
                    border: 1px solid var(--border);
                    border-radius: 8px;
                    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                }
                .cmd-input {
                    width: 100%;
                    background: transparent;
                    border: none;
                    border-bottom: 1px solid var(--border);
                    color: var(--text-main);
                    font-size: 16px;
                    padding: 16px;
                    outline: none;
                    font-family: var(--font-sans);
                }
                .cmd-list {
                    max-height: 300px;
                    overflow-y: auto;
                }
                .cmd-item {
                    padding: 12px 16px;
                    display: flex;
                    align-items: center;
                    cursor: pointer;
                    color: var(--text-muted);
                }
                .cmd-item i { margin-right: 12px; }
                .cmd-item.selected {
                    background: var(--bg-hover);
                    color: var(--text-main);
                }
                
                input.dark-input {
                    background: var(--bg-base);
                    border: 1px solid var(--border);
                    color: var(--text-main);
                    padding: 6px 8px;
                    border-radius: 4px;
                    outline: none;
                    width: 100%;
                    font-family: var(--font-sans);
                }
                input.dark-input:focus {
                    border-color: var(--accent);
                }

                .sidebar-layout {
                    display: flex;
                    height: 100%;
                    width: 100%;
                }
                .sidebar {
                    width: 150px;
                    border-right: 1px solid var(--border);
                    background: rgba(24, 24, 27, 0.5);
                    display: flex;
                    flex-direction: column;
                }
                .sidebar-item {
                    padding: 10px 12px;
                    cursor: pointer;
                    color: var(--text-muted);
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    border-left: 2px solid transparent;
                }
                .sidebar-item:hover {
                    background: var(--bg-hover);
                    color: var(--text-main);
                }
                .sidebar-item.active {
                    background: var(--bg-hover);
                    color: var(--text-main);
                    border-left-color: var(--accent);
                }
                .main-content {
                    flex: 1;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                }
            `;
            this.shadowRoot.appendChild(style);
        }

        loadDependencies() {
            const script = document.createElement('script');
            script.src = 'https://unpkg.com/lucide@latest';
            script.onload = () => {
                this.iconsLoaded = true;
                this.refreshIcons(this.shadowRoot);
            };
            document.head.appendChild(script);
        }

        refreshIcons(root) {
            if (this.iconsLoaded && window.lucide) {
                window.lucide.createIcons({ 
                    root: root, 
                    attrs: { 
                        class: "lucide-icon", 
                        stroke: "currentColor", 
                        "stroke-width": "2", 
                        "stroke-linecap": "round", 
                        "stroke-linejoin": "round" 
                    } 
                });
            }
        }

        createWindow(title, iconName) {
            return new UIWindow(this, title, iconName);
        }

        showToast(message) {
            const toast = document.createElement('div');
            toast.style.cssText = `
                position: fixed; bottom: 24px; right: 24px;
                background: var(--bg-surface); border: 1px solid var(--border);
                color: var(--text-main); padding: 12px 24px; border-radius: 8px;
                box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
                font-weight: 500; z-index: 10000;
                opacity: 0; transform: translateY(10px);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            `;
            toast.innerHTML = `<div style="display: flex; align-items: center; gap: 8px;"><i data-lucide="info" style="width: 16px; height: 16px; color: var(--accent);"></i> ${message}</div>`;
            this.shadowRoot.appendChild(toast);
            this.refreshIcons(toast);
            
            requestAnimationFrame(() => {
                toast.style.opacity = '1';
                toast.style.transform = 'translateY(0)';
            });
            
            setTimeout(() => {
                toast.style.opacity = '0';
                toast.style.transform = 'translateY(10px)';
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }
    }

    /**
     * =========================================================================
     * PERFORMANCE & DETECTOR MODULE (v1.0.2 Upgrade)
     * =========================================================================
     */

    class ThreeDetector extends Module {
        init() {
            this.scenes = new Set();
            this.cameras = new Set();
            this.renderers = new Set();
            this.textures = new Set();
            this.materials = new Set();
            this.geometries = new Set();
            
            this.setupHooks();
        }

        initUI() {
            this.insight.commands.registerCommand('Force Scan Global Context', 'radar', () => {
                this.scanGlobal();
                this.insight.ui.showToast(`Forced Scan: ${this.scenes.size} Scenes, ${this.textures.size} Textures.`);
            });
        }

        setupHooks() {
            let _THREE = window.THREE;
            Object.defineProperty(window, 'THREE', {
                get: () => _THREE,
                set: (val) => {
                    _THREE = val;
                    if (val) this.hookThree(val);
                },
                configurable: true
            });

            if (_THREE) this.hookThree(_THREE);
        }

        hookThree(THREE) {
            if (THREE._insightHooked) return;
            THREE._insightHooked = true;

            const proxySubclasses = (baseName, callback) => {
                Object.keys(THREE).forEach(key => {
                    if (key.includes(baseName) || key === baseName) {
                        this.hookConstructor(THREE, key, callback);
                    }
                });
            };

            // Intelligent Hooking instead of setInterval Global scanning
            proxySubclasses('Scene', (obj) => { this.scenes.add(obj); this.emit('asset-added', { type: 'scene', obj }); });
            proxySubclasses('WebGLRenderer', (obj) => { this.renderers.add(obj); this.emit('asset-added', { type: 'renderer', obj }); });
            proxySubclasses('Texture', (obj) => { this.textures.add(obj); this.emit('asset-added', { type: 'texture', obj }); });
            proxySubclasses('Material', (obj) => { this.materials.add(obj); this.emit('asset-added', { type: 'material', obj }); });
            proxySubclasses('Geometry', (obj) => { this.geometries.add(obj); this.emit('asset-added', { type: 'geometry', obj }); });
            proxySubclasses('Camera', (obj) => { this.cameras.add(obj); this.emit('asset-added', { type: 'camera', obj }); });

            // Hook the renderer's render loop to capture previously unhooked scenes
            if (THREE.WebGLRenderer) {
                const origRender = THREE.WebGLRenderer.prototype.render;
                const self = this;
                THREE.WebGLRenderer.prototype.render = function(scene, camera) {
                    if (!self.renderers.has(this)) { self.renderers.add(this); self.emit('asset-added', { type: 'renderer', obj: this }); }
                    if (scene && !self.scenes.has(scene)) { self.scenes.add(scene); self.emit('asset-added', { type: 'scene', obj: scene }); }
                    if (camera && !self.cameras.has(camera)) { self.cameras.add(camera); self.emit('asset-added', { type: 'camera', obj: camera }); }
                    return origRender.apply(this, arguments);
                };
            }

            console.log('[Insight] Intelligent Three.js hooking initialized.');
        }

        hookConstructor(namespace, className, callback) {
            if (!namespace[className]) return;
            const Orig = namespace[className];
            if (Orig.__insightHooked) return;

            try {
                const hooked = new Proxy(Orig, {
                    construct(target, args) {
                        const obj = new target(...args);
                        callback(obj);
                        return obj;
                    }
                });
                hooked.__insightHooked = true;
                namespace[className] = hooked;
            } catch (e) {
                // Fallback for non-constructable properties
            }
        }

        scanGlobal() {
            // Fallback scanner for already initialized objects
            const traverse = (obj) => {
                if (!obj || typeof obj !== 'object') return;
                if (obj.isScene) this.scenes.add(obj);
                if (obj.isCamera) this.cameras.add(obj);
                if (obj.isTexture) this.textures.add(obj);
                if (obj.isMaterial) this.materials.add(obj);
                if (obj.isBufferGeometry || obj.isGeometry) this.geometries.add(obj);
                if (obj.children) obj.children.forEach(traverse);
            };
            this.scenes.forEach(traverse);
        }
    }

    /**
     * =========================================================================
     * RUNTIME OBJECT EXPLORER (v1.0.2 Addition)
     * =========================================================================
     */

    class RuntimeObjectExplorer extends Module {
        initUI() {
            this.insight.commands.registerCommand('Runtime Object Graph', 'network', () => this.openWindow());
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Runtime Graph', 'network');
            win.el.style.width = '800px';
            win.el.style.height = '600px';

            const toolbar = document.createElement('div');
            toolbar.style.padding = '8px';
            toolbar.style.borderBottom = '1px solid var(--border)';
            toolbar.style.display = 'flex';
            toolbar.style.gap = '8px';

            const searchInput = document.createElement('input');
            searchInput.className = 'dark-input';
            searchInput.placeholder = 'Search nodes...';
            searchInput.style.width = '250px';

            const rebuildBtn = document.createElement('button');
            rebuildBtn.className = 'btn';
            rebuildBtn.innerHTML = '<i data-lucide="refresh-cw"></i> Rebuild Graph';

            toolbar.appendChild(searchInput);
            toolbar.appendChild(rebuildBtn);
            win.content.appendChild(toolbar);

            const canvasContainer = document.createElement('div');
            canvasContainer.style.flex = '1';
            canvasContainer.style.overflow = 'hidden';
            canvasContainer.style.position = 'relative';
            canvasContainer.style.background = '#111';
            win.content.appendChild(canvasContainer);

            const canvas = document.createElement('canvas');
            canvas.style.position = 'absolute';
            canvasContainer.appendChild(canvas);

            let ctx = canvas.getContext('2d');
            let nodes = [];
            let edges = [];
            
            let transform = { x: 0, y: 0, scale: 1 };
            let isDragging = false;
            let dragNode = null;
            let lastMouse = { x: 0, y: 0 };

            const resizeCanvas = () => {
                canvas.width = canvasContainer.clientWidth;
                canvas.height = canvasContainer.clientHeight;
            };
            new ResizeObserver(resizeCanvas).observe(canvasContainer);
            resizeCanvas();

            const buildGraph = () => {
                nodes = [];
                edges = [];
                const detector = this.insight.modules.detector;
                let idCounter = 0;
                const objMap = new Map();

                const addNode = (obj, label, type) => {
                    if (objMap.has(obj)) return objMap.get(obj);
                    const node = {
                        id: idCounter++, obj, label, type,
                        x: Math.random() * 800 - 400,
                        y: Math.random() * 600 - 300,
                        vx: 0, vy: 0, highlighted: false
                    };
                    nodes.push(node);
                    objMap.set(obj, node);
                    return node;
                };

                const addEdge = (from, to, label) => {
                    edges.push({ from, to, label });
                };

                detector.renderers.forEach((r, i) => {
                    const rNode = addNode(r, `WebGLRenderer ${i}`, 'renderer');
                    detector.scenes.forEach(s => {
                        const sNode = addNode(s, s.name || `Scene ${s.uuid.substr(0,4)}`, 'scene');
                        addEdge(rNode, sNode, 'renders');
                        
                        // Map top level scene children
                        s.children.forEach(c => {
                            const cNode = addNode(c, c.name || c.type, 'object');
                            addEdge(sNode, cNode, 'child');
                        });
                    });
                });

                // Reset layout
                transform = { x: canvas.width / 2, y: canvas.height / 2, scale: 1 };
            };

            buildGraph();

            // Interaction
            canvas.addEventListener('mousedown', (e) => {
                const rect = canvas.getBoundingClientRect();
                const mx = (e.clientX - rect.left - transform.x) / transform.scale;
                const my = (e.clientY - rect.top - transform.y) / transform.scale;
                
                dragNode = nodes.find(n => Math.abs(n.x - mx) < 50 && Math.abs(n.y - my) < 20);
                isDragging = true;
                lastMouse = { x: e.clientX, y: e.clientY };
            });

            canvas.addEventListener('mousemove', (e) => {
                if (!isDragging) return;
                const dx = e.clientX - lastMouse.x;
                const dy = e.clientY - lastMouse.y;
                if (dragNode) {
                    dragNode.x += dx / transform.scale;
                    dragNode.y += dy / transform.scale;
                    dragNode.vx = 0; dragNode.vy = 0;
                } else {
                    transform.x += dx;
                    transform.y += dy;
                }
                lastMouse = { x: e.clientX, y: e.clientY };
            });

            canvas.addEventListener('mouseup', () => { isDragging = false; dragNode = null; });
            canvas.addEventListener('mouseleave', () => { isDragging = false; dragNode = null; });

            canvas.addEventListener('wheel', (e) => {
                e.preventDefault();
                const zoom = Math.exp(-e.deltaY * 0.001);
                transform.scale *= zoom;
            });

            searchInput.addEventListener('input', () => {
                const query = searchInput.value.toLowerCase();
                nodes.forEach(n => {
                    n.highlighted = query && n.label.toLowerCase().includes(query);
                });
            });

            rebuildBtn.addEventListener('click', buildGraph);

            let rAF;
            const loop = () => {
                if (!ctx) return;
                
                // Physics step (Spring layout)
                if (!dragNode) {
                    // Repulsion
                    for(let i=0; i<nodes.length; i++) {
                        for(let j=i+1; j<nodes.length; j++) {
                            const n1 = nodes[i], n2 = nodes[j];
                            let dx = n1.x - n2.x, dy = n1.y - n2.y;
                            let distSq = dx*dx + dy*dy || 1;
                            if(distSq < 40000) {
                                let f = 1000 / distSq;
                                n1.vx += dx*f; n1.vy += dy*f;
                                n2.vx -= dx*f; n2.vy -= dy*f;
                            }
                        }
                    }
                    // Attraction
                    edges.forEach(e => {
                        let dx = e.to.x - e.from.x, dy = e.to.y - e.from.y;
                        let dist = Math.sqrt(dx*dx + dy*dy) || 1;
                        let f = (dist - 100) * 0.005;
                        e.from.vx += (dx/dist)*f; e.from.vy += (dy/dist)*f;
                        e.to.vx -= (dx/dist)*f; e.to.vy -= (dy/dist)*f;
                    });
                    // Integration
                    nodes.forEach(n => {
                        n.x += n.vx; n.y += n.vy;
                        n.vx *= 0.85; n.vy *= 0.85;
                    });
                }

                ctx.fillStyle = '#111';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                ctx.save();
                ctx.translate(transform.x, transform.y);
                ctx.scale(transform.scale, transform.scale);

                // Draw Edges
                ctx.lineWidth = 1;
                edges.forEach(e => {
                    ctx.beginPath();
                    ctx.moveTo(e.from.x, e.from.y);
                    ctx.lineTo(e.to.x, e.to.y);
                    ctx.strokeStyle = 'rgba(100, 100, 100, 0.5)';
                    ctx.stroke();
                });

                // Draw Nodes
                ctx.font = '12px Inter';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';

                nodes.forEach(n => {
                    ctx.fillStyle = n.highlighted ? '#F59E0B' : (n.type === 'scene' ? '#3B82F6' : '#27272A');
                    ctx.strokeStyle = n.highlighted ? '#FFF' : '#3F3F46';
                    ctx.lineWidth = 2;
                    
                    const w = Math.max(100, ctx.measureText(n.label).width + 20);
                    ctx.fillRect(n.x - w/2, n.y - 15, w, 30);
                    ctx.strokeRect(n.x - w/2, n.y - 15, w, 30);
                    
                    ctx.fillStyle = n.highlighted ? '#000' : '#FFF';
                    ctx.fillText(n.label, n.x, n.y);
                });

                ctx.restore();
                rAF = requestAnimationFrame(loop);
            };

            rAF = requestAnimationFrame(loop);
            win.onClose(() => {
                cancelAnimationFrame(rAF);
                ctx = null;
            });
            this.insight.ui.refreshIcons(toolbar);
        }
    }

    /**
     * =========================================================================
     * ASSET EXPLORER (v1.0.2 Addition)
     * =========================================================================
     */

    class AssetExplorer extends Module {
        initUI() {
            this.insight.commands.registerCommand('Asset Explorer', 'package', () => this.openWindow());
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Asset Explorer', 'package');
            win.el.style.width = '700px';
            win.el.style.height = '500px';

            win.content.innerHTML = `
                <div class="sidebar-layout">
                    <div class="sidebar">
                        <div class="sidebar-item active" data-tab="textures"><i data-lucide="image"></i> Textures</div>
                        <div class="sidebar-item" data-tab="materials"><i data-lucide="layers"></i> Materials</div>
                        <div class="sidebar-item" data-tab="geometries"><i data-lucide="box"></i> Geometry</div>
                    </div>
                    <div class="main-content">
                        <div style="padding: 8px; border-bottom: 1px solid var(--border);">
                            <input type="text" class="dark-input" id="asset-search" placeholder="Search assets..." />
                        </div>
                        <div id="asset-list" style="flex: 1; overflow-y: auto; padding: 12px; display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); align-content: start;">
                        </div>
                    </div>
                </div>
            `;

            let currentTab = 'textures';
            let searchQuery = '';
            const listEl = win.content.querySelector('#asset-list');
            const searchEl = win.content.querySelector('#asset-search');

            const renderItems = () => {
                listEl.innerHTML = '';
                const detector = this.insight.modules.detector;
                let items = [];

                if (currentTab === 'textures') items = Array.from(detector.textures);
                if (currentTab === 'materials') items = Array.from(detector.materials);
                if (currentTab === 'geometries') items = Array.from(detector.geometries);

                items.forEach(item => {
                    const name = item.name || item.type || 'Unnamed';
                    if (searchQuery && !name.toLowerCase().includes(searchQuery.toLowerCase())) return;

                    const card = document.createElement('div');
                    card.style.cssText = 'background: var(--bg-surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-size: 11px;';
                    
                    let meta = '';
                    if (currentTab === 'textures') {
                        meta = `<div>Res: ${item.image ? item.image.width+'x'+item.image.height : 'Unknown'}</div>
                                <div>Format: ${item.format}</div>`;
                    } else if (currentTab === 'materials') {
                        meta = `<div>Type: ${item.type}</div>
                                <div>Wireframe: ${item.wireframe}</div>`;
                    } else if (currentTab === 'geometries') {
                        const verts = item.attributes?.position?.count || 0;
                        const tris = item.index ? item.index.count / 3 : verts / 3;
                        meta = `<div>Verts: ${verts.toLocaleString()}</div>
                                <div>Tris: ${Math.floor(tris).toLocaleString()}</div>`;
                    }

                    card.innerHTML = `
                        <div style="font-weight: 600; font-size: 12px; margin-bottom: 6px; color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${name}">${name}</div>
                        <div style="color: var(--text-muted); line-height: 1.5; font-family: var(--font-mono);">${meta}</div>
                        <div style="color: #666; font-size: 9px; margin-top: 6px;">${item.uuid.substr(0,8)}</div>
                    `;
                    listEl.appendChild(card);
                });
            };

            win.content.querySelectorAll('.sidebar-item').forEach(el => {
                el.addEventListener('click', (e) => {
                    win.content.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
                    e.currentTarget.classList.add('active');
                    currentTab = e.currentTarget.getAttribute('data-tab');
                    renderItems();
                });
            });

            searchEl.addEventListener('input', (e) => {
                searchQuery = e.target.value;
                renderItems();
            });

            // Live updates
            const cleanup = this.insight.modules.detector.on('asset-added', (data) => {
                if (data.type + 's' === currentTab || (data.type === 'geometry' && currentTab === 'geometries')) {
                    renderItems();
                }
            });
            win.onClose(cleanup);

            renderItems();
            this.insight.ui.refreshIcons(win.content);
        }
    }

    /**
     * =========================================================================
     * ENTITY INSPECTOR (v1.0.2 Upgrade - Live Mode + Deep Inspect)
     * =========================================================================
     */

    class EntityInspector extends Module {
        initUI() {
            this.insight.commands.registerCommand('Open Entity Inspector', 'sliders', () => this.openWindow());
            this.insight.on('inspect-object', (obj) => {
                this.activeObject = obj;
                if (!this.window) this.window = this.openWindow();
                this.rebuildInspectorDOM();
            });
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Entity Inspector', 'sliders');
            win.el.style.width = '320px';
            win.el.style.height = '600px';
            this.window = win;
            
            this.liveRefs = {};
            let rAF;
            const loop = () => {
                if (this.activeObject && this.window) this.updateLiveValues();
                rAF = requestAnimationFrame(loop);
            };
            rAF = requestAnimationFrame(loop);

            win.onClose(() => {
                this.window = null;
                cancelAnimationFrame(rAF);
            });

            this.rebuildInspectorDOM();
            return win;
        }

        rebuildInspectorDOM() {
            if (!this.window) return;
            const content = this.window.content;
            const obj = this.activeObject;
            this.liveRefs = {}; 

            if (!obj) {
                content.innerHTML = '<div style="padding: 12px; color: var(--text-muted); text-align: center;">Select an object in the Scene Explorer to inspect.</div>';
                return;
            }

            const createRow = (label, refKey, staticVal = null) => {
                const id = 'ref_' + Math.random().toString(36).substr(2, 9);
                if (refKey) this.liveRefs[refKey] = id;
                return `<div style="display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 12px;">
                            <span style="color: var(--text-muted);">${label}</span>
                            <span id="${id}" style="font-family: var(--font-mono); color: var(--text-main);">${staticVal !== null ? staticVal : '-'}</span>
                        </div>`;
            };

            const section = (title, inner) => `
                <div style="border-bottom: 1px solid var(--border); padding: 12px;">
                    <div style="font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.05em;">${title}</div>
                    ${inner}
                </div>
            `;

            let html = ``;

            html += section('Information', 
                createRow('Name', null, `<span style="color: var(--accent);">${obj.name || 'N/A'}</span>`) +
                createRow('Type', null, obj.type) +
                createRow('UUID', null, `<span style="font-size: 10px;">${obj.uuid}</span>`)
            );

            if (obj.position) {
                html += section('Transform',
                    createRow('Position', 'pos') +
                    createRow('Rotation', 'rot') +
                    createRow('Scale', 'scale')
                );
            }

            if (obj.geometry) {
                const geo = obj.geometry;
                const verts = geo.attributes?.position?.count || 0;
                const tris = geo.index ? geo.index.count / 3 : verts / 3;
                html += section('Geometry',
                    createRow('Type', null, geo.type) +
                    createRow('Vertices', null, verts.toLocaleString()) +
                    createRow('Triangles', null, Math.floor(tris).toLocaleString()) +
                    createRow('Bounding Sphere', 'bounds')
                );
            }

            if (obj.material) {
                const mat = obj.material;
                const matInfo = Array.isArray(mat) ? `Array (${mat.length})` : mat.type;
                html += section('Material',
                    createRow('Type', null, matInfo) +
                    createRow('Transparent', null, mat.transparent) +
                    createRow('Opacity', 'opacity')
                );
            }

            html += section('Hierarchy & Renderer',
                createRow('Visible', 'visible') +
                createRow('Render Order', null, obj.renderOrder) +
                createRow('Children', null, obj.children?.length || 0) +
                createRow('Parent', null, obj.parent ? obj.parent.type : 'None')
            );

            content.innerHTML = html;
            
            // Map IDs to actual elements for quick rAF updates
            for (const key in this.liveRefs) {
                this.liveRefs[key] = content.querySelector('#' + this.liveRefs[key]);
            }
        }

        updateLiveValues() {
            const obj = this.activeObject;
            const refs = this.liveRefs;
            const fn = n => typeof n === 'number' ? n.toFixed(3) : n;

            if (refs.pos && obj.position) refs.pos.textContent = `${fn(obj.position.x)}, ${fn(obj.position.y)}, ${fn(obj.position.z)}`;
            if (refs.rot && obj.rotation) refs.rot.textContent = `${fn(obj.rotation.x)}, ${fn(obj.rotation.y)}, ${fn(obj.rotation.z)}`;
            if (refs.scale && obj.scale) refs.scale.textContent = `${fn(obj.scale.x)}, ${fn(obj.scale.y)}, ${fn(obj.scale.z)}`;
            
            if (refs.visible) refs.visible.textContent = obj.visible ? 'True' : 'False';
            if (refs.opacity && obj.material) refs.opacity.textContent = Array.isArray(obj.material) ? '-' : fn(obj.material.opacity);
            
            if (refs.bounds && obj.geometry && obj.geometry.boundingSphere) {
                refs.bounds.textContent = `Rad: ${fn(obj.geometry.boundingSphere.radius)}`;
            }
        }
    }

    /**
     * =========================================================================
     * PRE-EXISTING MODULES (Maintained & Integrated)
     * =========================================================================
     */

    class CommandPalette extends Module {
        initUI() {
            this.commands = [];
            this.filtered = [];
            this.selectedIndex = 0;

            this.el = document.createElement('div');
            this.el.className = 'cmd-overlay';
            this.el.style.display = 'none';

            this.container = document.createElement('div');
            this.container.className = 'cmd-palette';

            this.input = document.createElement('input');
            this.input.className = 'cmd-input';
            this.input.placeholder = 'Search Insight commands...';

            this.list = document.createElement('div');
            this.list.className = 'cmd-list';

            this.container.appendChild(this.input);
            this.container.appendChild(this.list);
            this.el.appendChild(this.container);
            this.insight.ui.shadowRoot.appendChild(this.el);

            this.setupEvents();
        }

        registerCommand(name, icon, action) {
            this.commands.push({ name, icon, action });
        }

        setupEvents() {
            window.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') {
                    e.preventDefault(); e.stopPropagation();
                    this.toggle();
                }
            }, true);

            this.input.addEventListener('input', () => this.filter(this.input.value));

            this.input.addEventListener('keydown', (e) => {
                if (e.key === 'ArrowDown') { e.preventDefault(); this.selectedIndex = Math.min(this.selectedIndex + 1, this.filtered.length - 1); this.renderList(); } 
                else if (e.key === 'ArrowUp') { e.preventDefault(); this.selectedIndex = Math.max(this.selectedIndex - 1, 0); this.renderList(); } 
                else if (e.key === 'Enter') { e.preventDefault(); const cmd = this.filtered[this.selectedIndex]; if (cmd) { this.hide(); cmd.action(); } } 
                else if (e.key === 'Escape') { this.hide(); }
            });
            this.el.addEventListener('click', (e) => { if (e.target === this.el) this.hide(); });
        }

        toggle() { if (this.el.style.display === 'none') this.show(); else this.hide(); }
        show() { this.el.style.display = 'flex'; this.input.value = ''; this.filter(''); this.input.focus(); }
        hide() { this.el.style.display = 'none'; }
        filter(query) {
            query = query.toLowerCase();
            this.filtered = this.commands.filter(cmd => cmd.name.toLowerCase().includes(query));
            this.selectedIndex = 0;
            this.renderList();
        }
        renderList() {
            this.list.innerHTML = '';
            this.filtered.forEach((cmd, idx) => {
                const item = document.createElement('div');
                item.className = `cmd-item ${idx === this.selectedIndex ? 'selected' : ''}`;
                item.innerHTML = `<i data-lucide="${cmd.icon}"></i> <span>${cmd.name}</span>`;
                item.addEventListener('click', () => { this.hide(); cmd.action(); });
                item.addEventListener('mouseenter', () => { this.selectedIndex = idx; this.renderList(); });
                this.list.appendChild(item);
            });
            this.insight.ui.refreshIcons(this.list);
            const selectedEl = this.list.children[this.selectedIndex];
            if (selectedEl) selectedEl.scrollIntoView({ block: 'nearest' });
        }
    }

    class SceneExplorer extends Module {
        initUI() {
            this.insight.commands.registerCommand('Scene Hierarchy', 'layers', () => this.openWindow());
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Hierarchy', 'layers');
            win.el.style.width = '350px';
            win.el.style.height = '500px';
            
            const toolbar = document.createElement('div');
            toolbar.style.padding = '8px';
            toolbar.style.borderBottom = '1px solid var(--border)';
            
            const search = document.createElement('input');
            search.className = 'dark-input';
            search.placeholder = 'Filter nodes...';
            
            toolbar.appendChild(search);
            win.content.appendChild(toolbar);

            const treeContainer = document.createElement('div');
            treeContainer.style.flex = '1';
            treeContainer.style.overflow = 'auto';
            treeContainer.style.padding = '8px';
            win.content.appendChild(treeContainer);

            search.addEventListener('input', () => this.renderTree(treeContainer, search.value.toLowerCase()));
            
            // Auto Update via MutationObserver pattern on the Set size is heavy, rely on explicit hook events
            const cleanup = this.insight.modules.detector.on('asset-added', (data) => {
                if (data.type === 'scene') this.renderTree(treeContainer, search.value.toLowerCase());
            });
            win.onClose(cleanup);

            this.renderTree(treeContainer, '');
        }

        getIcon(type) {
            switch(type) {
                case 'Scene': return 'globe';
                case 'PerspectiveCamera': case 'OrthographicCamera': return 'camera';
                case 'Mesh': return 'box';
                case 'PointLight': case 'DirectionalLight': return 'sun';
                case 'Group': return 'folder';
                default: return 'cuboid';
            }
        }

        renderTree(container, filterText) {
            container.innerHTML = '';
            const scenes = Array.from(this.insight.modules.detector.scenes);
            if (scenes.length === 0) {
                container.innerHTML = '<div style="color: var(--text-muted); text-align: center; margin-top: 20px;">No scenes detected.</div>';
                return;
            }

            const buildNode = (object) => {
                const name = (object.name || object.type || 'Object3D').toLowerCase();
                let childrenNodes = [];
                let hasMatchingDescendant = false;

                if (object.children && object.children.length > 0) {
                    object.children.forEach(child => {
                        const childResult = buildNode(child);
                        if (childResult) {
                            childrenNodes.push(childResult.el);
                            hasMatchingDescendant = true;
                        }
                    });
                }

                if (filterText !== '' && !name.includes(filterText) && !hasMatchingDescendant) return null;

                const node = document.createElement('div');
                const row = document.createElement('div');
                row.style.cssText = 'display: flex; align-items: center; padding: 4px; cursor: pointer; border-radius: 4px;';
                row.onmouseenter = () => row.style.background = 'var(--bg-hover)';
                row.onmouseleave = () => row.style.background = 'transparent';

                const hasChildren = childrenNodes.length > 0;
                const chevronHtml = hasChildren ? `<i data-lucide="chevron-down" style="width: 14px; margin-right: 4px; color: var(--text-muted);"></i>` : `<span style="width: 18px; display: inline-block;"></span>`;
                
                row.innerHTML = `${chevronHtml}<i data-lucide="${this.getIcon(object.type)}" style="width: 14px; margin-right: 6px; color: var(--text-muted);"></i><span style="font-size: 13px; color: ${object.visible ? 'var(--text-main)' : 'var(--text-muted)'};">${object.name || object.type || 'Object3D'}</span>`;
                node.appendChild(row);

                if (hasChildren) {
                    const childrenContainer = document.createElement('div');
                    childrenContainer.style.cssText = 'padding-left: 14px; border-left: 1px solid var(--border); margin-left: 11px;';
                    childrenNodes.forEach(childEl => childrenContainer.appendChild(childEl));
                    node.appendChild(childrenContainer);

                    row.querySelector('i[data-lucide="chevron-down"]').addEventListener('click', (e) => {
                        e.stopPropagation();
                        const isHidden = childrenContainer.style.display === 'none';
                        childrenContainer.style.display = isHidden ? 'block' : 'none';
                        e.target.setAttribute('data-lucide', isHidden ? 'chevron-down' : 'chevron-right');
                        this.insight.ui.refreshIcons(row);
                    });
                }

                row.addEventListener('click', () => this.insight.emit('inspect-object', object));
                return { el: node };
            };

            scenes.forEach(scene => { const res = buildNode(scene); if (res) container.appendChild(res.el); });
            this.insight.ui.refreshIcons(container);
        }
    }

    class PerformanceMonitor extends Module {
        init() {
            this.fps = 0; this.drawCalls = 0; this.triangles = 0;
            this.hookWebGL();
            this.startLoop();
        }
        initUI() { this.insight.commands.registerCommand('Performance Dashboard', 'cpu', () => this.openWindow()); }

        hookWebGL() {
            const self = this;
            const origDrawElements = WebGLRenderingContext.prototype.drawElements;
            WebGLRenderingContext.prototype.drawElements = function(mode, count, type, offset) {
                self.drawCalls++; if (mode === this.TRIANGLES) self.triangles += count / 3;
                return origDrawElements.apply(this, arguments);
            };
            if (window.WebGL2RenderingContext) {
                const origDrawElements2 = WebGL2RenderingContext.prototype.drawElements;
                WebGL2RenderingContext.prototype.drawElements = function(mode, count, type, offset) {
                    self.drawCalls++; if (mode === this.TRIANGLES) self.triangles += count / 3;
                    return origDrawElements2.apply(this, arguments);
                };
            }
        }

        startLoop() {
            let frames = 0, lastTime = performance.now();
            const loop = () => {
                frames++;
                const now = performance.now();
                if (now >= lastTime + 1000) {
                    this.fps = (frames * 1000) / (now - lastTime);
                    this.emit('stats', { fps: this.fps, drawCalls: this.drawCalls, triangles: this.triangles });
                    frames = 0; lastTime = now; this.drawCalls = 0; this.triangles = 0;
                }
                requestAnimationFrame(loop);
            };
            requestAnimationFrame(loop);
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Performance', 'cpu');
            const updateUI = (stats) => {
                win.content.innerHTML = `
                    <div style="display: flex; flex-direction: column; gap: 12px; padding: 12px;">
                        <div style="display: flex; justify-content: space-between;"><span style="color: var(--text-muted);">FPS</span><span style="font-family: var(--font-mono); font-size: 18px; color: ${stats.fps > 50 ? '#34D399' : '#FBBF24'};">${Math.round(stats.fps)}</span></div>
                        <div style="display: flex; justify-content: space-between;"><span style="color: var(--text-muted);">Draw Calls / s</span><span style="font-family: var(--font-mono); color: var(--accent);">${stats.drawCalls}</span></div>
                        <div style="display: flex; justify-content: space-between;"><span style="color: var(--text-muted);">Triangles / s</span><span style="font-family: var(--font-mono); color: var(--accent);">${Math.round(stats.triangles).toLocaleString()}</span></div>
                    </div>
                `;
            };
            updateUI({ fps: this.fps, drawCalls: this.drawCalls, triangles: this.triangles });
            const cleanup = this.on('stats', updateUI);
            win.onClose(cleanup);
        }
    }

    class NetworkAnalyzer extends Module {
        init() {
            this.requests = [];
            this.hookFetch();
            this.hookXHR();
        }
        initUI() { this.insight.commands.registerCommand('Network Analyzer', 'globe', () => this.openWindow()); }

        hookFetch() {
            const origFetch = window.fetch;
            window.fetch = async (...args) => {
                this.addReq(args[0], args[1]?.method || 'GET', 'fetch');
                return origFetch.apply(window, args);
            };
        }
        hookXHR() {
            const origOpen = XMLHttpRequest.prototype.open;
            const self = this;
            XMLHttpRequest.prototype.open = function(method, url) {
                self.addReq(url, method, 'xhr');
                return origOpen.apply(this, arguments);
            };
        }
        addReq(url, method, type) {
            const req = { url, method, type, time: new Date().toLocaleTimeString() };
            this.requests.push(req);
            if (this.requests.length > 200) this.requests.shift(); // Memory Safety Limit
            this.emit('new-request', req);
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Network', 'globe');
            win.el.style.width = '600px'; win.el.style.height = '400px';
            const tableContainer = document.createElement('div');
            tableContainer.style.cssText = 'width: 100%; height: 100%; overflow: auto;';
            win.content.appendChild(tableContainer);

            const render = () => {
                let html = `<table style="width: 100%; text-align: left; border-collapse: collapse;">
                    <tr style="border-bottom: 1px solid var(--border); color: var(--text-muted); background: var(--bg-base); position: sticky; top: 0;">
                        <th style="padding: 8px;">Time</th><th style="padding: 8px;">Method</th><th style="padding: 8px;">Type</th><th style="padding: 8px;">URL</th>
                    </tr>`;
                this.requests.slice().reverse().forEach(r => {
                    html += `<tr style="border-bottom: 1px solid var(--border);"><td style="padding: 8px; font-family: var(--font-mono); font-size: 11px;">${r.time}</td>
                        <td style="padding: 8px; color: var(--accent); font-weight: 600;">${r.method}</td><td style="padding: 8px; color: var(--text-muted);">${r.type}</td>
                        <td style="padding: 8px; max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${r.url}</td></tr>`;
                });
                tableContainer.innerHTML = html + '</table>';
            };
            render();
            const cleanup = this.on('new-request', render);
            win.onClose(cleanup);
        }
    }

    class DeveloperConsole extends Module {
        initUI() { this.insight.commands.registerCommand('Developer Console', 'terminal', () => this.openWindow()); }
        openWindow() {
            const win = this.insight.ui.createWindow('Console', 'terminal');
            win.el.style.width = '600px'; win.el.style.height = '400px';
            win.content.innerHTML = `
                <div style="display: flex; flex-direction: column; height: 100%;">
                    <div class="console-output" style="flex: 1; overflow-y: auto; padding: 12px; font-family: var(--font-mono); font-size: 12px; background: #000; border-radius: 4px; margin: 8px;">
                        <div style="color: var(--text-muted);">// Insight Platform API available as 'insight'.</div>
                    </div>
                    <div style="display: flex; align-items: center; border-top: 1px solid var(--border); padding: 8px; background: var(--bg-base);">
                        <span style="color: var(--accent); margin-right: 8px; font-weight: 600;">&gt;</span>
                        <input class="console-input" type="text" style="flex: 1; background: transparent; border: none; color: var(--text-main); font-family: var(--font-mono); font-size: 13px; outline: none;" placeholder="Evaluate JavaScript..." />
                    </div>
                </div>
            `;
            const output = win.content.querySelector('.console-output');
            const input = win.content.querySelector('.console-input');

            input.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' && input.value) {
                    const code = input.value; input.value = '';
                    output.innerHTML += `<div style="color: var(--text-muted); margin-top: 8px;">> ${code}</div>`;
                    try {
                        const result = new Function('insight', `return eval(${JSON.stringify(code)})`)(this.insight);
                        output.innerHTML += `<div style="color: #A78BFA;">< ${String(result)}</div>`;
                    } catch (err) {
                        output.innerHTML += `<div style="color: #F87171;">${String(err)}</div>`;
                    }
                    output.scrollTop = output.scrollHeight;
                }
            });
        }
    }

    class FunctionTracer extends Module {
        initUI() {
            this.traces = [];
            this.insight.commands.registerCommand('Function Tracer', 'code', () => this.openWindow());
        }

        trace(obj, methodName) {
            const orig = obj[methodName];
            const self = this;
            obj[methodName] = function(...args) {
                const start = performance.now();
                const res = orig.apply(this, args);
                self.traces.push({ method: methodName, duration: performance.now() - start });
                if (self.traces.length > 500) self.traces.shift(); // Memory safety limit
                self.emit('trace', self.traces[self.traces.length-1]);
                return res;
            };
            this.insight.ui.showToast(`Tracing active for ${methodName}`);
        }

        openWindow() {
            const win = this.insight.ui.createWindow('Function Tracer', 'code');
            win.content.innerHTML = `<div style="padding: 16px; color: var(--text-muted); line-height: 1.5;">
                <strong>Trace Instructions:</strong><br><br>
                Use the Developer Console to initiate traces on any accessible prototype or object instance.<br><br>
                <code>insight.modules.tracer.trace(THREE.Vector3.prototype, 'normalize');</code>
            </div>`;
        }
    }

    class SettingsManager extends Module {
        initUI() { this.insight.commands.registerCommand('Settings', 'settings', () => this.openWindow()); }
        openWindow() {
            const win = this.insight.ui.createWindow('Settings', 'settings');
            win.content.innerHTML = `<div style="padding: 16px; color: var(--text-muted);">
                <h3 style="margin: 0 0 16px 0; font-size: 14px; font-weight: 500; color: var(--text-main);">Preferences</h3>
                <label style="display: flex; align-items: center; gap: 8px;"><input type="checkbox" checked disabled /> Dark Theme (Zinc)</label><br>
                <label style="display: flex; align-items: center; gap: 8px;"><input type="checkbox" checked disabled /> Auto-hook Three.js Prototypes</label>
            </div>`;
        }
    }

    class PluginManager extends Module {
        init() {
            window.InsightAPI = {
                registerPlugin: (plugin) => {
                    if (plugin && typeof plugin.init === 'function') {
                        try {
                            plugin.init(this.insight);
                            console.log(`[Insight] Loaded external plugin: ${plugin.name} v${plugin.version}`);
                            if (this.insight.ui) this.insight.ui.showToast(`Plugin Loaded: ${plugin.name}`);
                        } catch (err) { console.error(`[Insight] Failed to load plugin ${plugin.name}:`, err); }
                    }
                }
            };
        }
    }

    /**
     * =========================================================================
     * CORE ORCHESTRATOR
     * =========================================================================
     */

    class InsightCore extends EventEmitter {
        constructor() {
            super();
            this.modules = {};
            window.insight = this;

            this.ui = new UIFramework(this);
            
            // Register Modules
            this.registerModule('settings', new SettingsManager(this));
            this.registerModule('plugins', new PluginManager(this));
            this.registerModule('detector', new ThreeDetector(this));
            this.registerModule('network', new NetworkAnalyzer(this));
            this.registerModule('performance', new PerformanceMonitor(this));
            this.registerModule('commands', new CommandPalette(this));
            this.registerModule('scene', new SceneExplorer(this));
            this.registerModule('inspector', new EntityInspector(this));
            this.registerModule('console', new DeveloperConsole(this));
            this.registerModule('tracer', new FunctionTracer(this));
            
            // v1.0.2 New Modules
            this.registerModule('runtimeGraph', new RuntimeObjectExplorer(this));
            this.registerModule('assets', new AssetExplorer(this));

            this.initRuntime();
        }

        registerModule(id, instance) {
            this.modules[id] = instance;
        }

        initRuntime() {
            for (const key in this.modules) if (this.modules[key].init) this.modules[key].init();
        }

        initUI() {
            this.ui.mount();
            for (const key in this.modules) if (this.modules[key].initUI) this.modules[key].initUI();
            this.commands = this.modules.commands;
            
            console.log('%c[Insight Platform] Professional Runtime Analysis Studio Ready.', 'color: #3B82F6; font-weight: bold;');
            setTimeout(() => {
                this.ui.showToast('Insight Platform Ready. Press Ctrl+Shift+P to open Command Palette.');
            }, 500);
        }
    }

    // Bootstrap
    const insightPlatform = new InsightCore();

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => insightPlatform.initUI());
    } else {
        insightPlatform.initUI();
    }

})();