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 เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
    }

})();