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와 같은 확장 프로그램이 필요합니다.

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

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

(function () {
    'use strict';

    /**
     * =========================================================================
     * UTILITIES & HELPERS
     * =========================================================================
     */

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

    // Basic OBJ Exporter (Solves export feature without relying on external CDNs)
    const exportToOBJ = (object3D) => {
        let obj = '';
        let vertexOffset = 1;
        object3D.traverse((child) => {
            if (child.isMesh && child.geometry) {
                const geo = child.geometry;
                const positions = geo.attributes.position;
                if (!positions) return;
                
                obj += `o ${child.name || child.type || 'Mesh'}\n`;
                const matrix = child.matrixWorld;
                const vec3 = new window.THREE.Vector3();
                
                for (let i = 0; i < positions.count; i++) {
                    vec3.fromBufferAttribute(positions, i).applyMatrix4(matrix);
                    obj += `v ${vec3.x} ${vec3.y} ${vec3.z}\n`;
                }
                
                if (geo.index) {
                    const indices = geo.index.array;
                    for (let i = 0; i < indices.length; i += 3) {
                        obj += `f ${indices[i]+vertexOffset} ${indices[i+1]+vertexOffset} ${indices[i+2]+vertexOffset}\n`;
                    }
                } else {
                    for (let i = 0; i < positions.count; i += 3) {
                        obj += `f ${i+vertexOffset} ${i+1+vertexOffset} ${i+2+vertexOffset}\n`;
                    }
                }
                vertexOffset += positions.count;
            }
        });
        const blob = new Blob([obj], { type: 'text/plain' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `${object3D.name || 'Insight_Export'}.obj`;
        a.click();
        URL.revokeObjectURL(a.href);
    };

    /**
     * =========================================================================
     * UI FRAMEWORK & COMPONENTS (With Persistence)
     * =========================================================================
     */

    class UIWindow {
        constructor(ui, id, title, iconName) {
            this.ui = ui;
            this.id = id;
            this.el = document.createElement('div');
            this.el.className = 'insight-window';
            
            // Restore window state
            const state = JSON.parse(localStorage.getItem(`insight_win_${this.id}`) || 'null');
            this.el.style.left = state?.left || '100px';
            this.el.style.top = state?.top || '100px';
            this.el.style.width = state?.width || 'auto';
            this.el.style.height = state?.height || 'auto';
            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.makeDraggableAndResizable();
            this.ui.refreshIcons(this.header);
        }

        saveState() {
            localStorage.setItem(`insight_win_${this.id}`, JSON.stringify({
                left: this.el.style.left,
                top: this.el.style.top,
                width: this.el.style.width,
                height: this.el.style.height
            }));
        }

        makeDraggableAndResizable() {
            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.saveState();
            };

            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);

                // Bring to front
                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);
            });

            // Save state on resize
            new ResizeObserver(() => this.saveState()).observe(this.el);
        }

        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;
                    --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;
                }

                .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 .title { font-weight: 500; flex-grow: 1; }
                .insight-header .close-btn { cursor: pointer; color: var(--text-muted); }
                .insight-header .close-btn:hover { color: #F87171; }

                .window-content {
                    flex: 1;
                    overflow: auto;
                    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;
                }
                .btn:hover { background: var(--border); }

                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); }

                /* Two Way Data Binding Inputs */
                .live-input {
                    background: transparent;
                    border: 1px solid transparent;
                    color: var(--accent);
                    font-family: var(--font-mono);
                    font-size: 12px;
                    width: 60px;
                    outline: none;
                    text-align: right;
                    border-radius: 3px;
                }
                .live-input:focus {
                    border-color: var(--accent);
                    background: var(--bg-base);
                }

                .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; padding-top: 15vh; z-index: 10000;
                }

                .cmd-palette {
                    width: 600px; background: var(--bg-surface);
                    border: 1px solid var(--border); border-radius: 8px;
                    display: flex; flex-direction: column;
                }
                .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;
                }
                .cmd-item {
                    padding: 12px 16px; display: flex; align-items: center; cursor: pointer; color: var(--text-muted);
                }
                .cmd-item.selected { background: var(--bg-hover); color: var(--text-main); }

                .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, .sidebar-item.active { background: var(--bg-hover); color: var(--text-main); }
                .sidebar-item.active { 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(id, title, iconName) {
            return new UIWindow(this, id, 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; transition: all 0.3s;
            `;
            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);
            
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }
    }

    /**
     * =========================================================================
     * 1. DETECTOR MODULE (Intelligent Hooks + Safe Mode Fallback)
     * =========================================================================
     */

    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.lastCamera = null;
            this.lastScene = null;
            this.lastRenderer = null;
            
            this.setupHooks();
        }

        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;
            
            try {
                // High-performance Proxy hooks
                const proxySubclasses = (baseName, callback) => {
                    Object.keys(THREE).forEach(key => {
                        if (key.includes(baseName) || key === baseName) {
                            if (typeof THREE[key] === 'function') {
                                const Orig = THREE[key];
                                if (!Orig.__insightHooked) {
                                    const hooked = new Proxy(Orig, {
                                        construct(target, args) {
                                            const obj = new target(...args);
                                            callback(obj);
                                            return obj;
                                        }
                                    });
                                    hooked.__insightHooked = true;
                                    THREE[key] = hooked;
                                }
                            }
                        }
                    });
                };

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

                // Hook the renderer's loop to catch pre-existing scenes and active cameras
                if (THREE.WebGLRenderer) {
                    const origRender = THREE.WebGLRenderer.prototype.render;
                    const self = this;
                    THREE.WebGLRenderer.prototype.render = function(scene, camera) {
                        self.lastRenderer = this;
                        self.lastScene = scene;
                        self.lastCamera = 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 }); }
                        return origRender.apply(this, arguments);
                    };
                }

                THREE._insightHooked = true;
                console.log('[Insight] Intelligent WebGL hooking initialized.');

            } catch (err) {
                // Safe Mode Fallback for heavily obfuscated / frozen objects
                console.warn('[Insight] Proxy Hook failed. Falling back to Safe Mode (Interval Scanning).', err);
                setInterval(() => this.scanGlobal(), 2000);
            }
        }

        scanGlobal() {
            const traverse = (obj) => {
                if (!obj || typeof obj !== 'object') return;
                if (obj.isScene) { this.scenes.add(obj); this.lastScene = obj; }
                if (obj.isCamera) { this.cameras.add(obj); this.lastCamera = 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);
        }
    }

    /**
     * =========================================================================
     * 2. RAYCASTER PICKER TOOL (Interactive Click-to-Select)
     * =========================================================================
     */

    class PickerTool extends Module {
        init() {
            this.active = false;
            this.raycaster = new window.THREE.Raycaster();
        }

        initUI() {
            this.insight.commands.registerCommand('Toggle Raycast Picker', 'crosshair', () => this.toggle());
            document.addEventListener('click', (e) => this.onClick(e), true);
        }

        toggle() {
            this.active = !this.active;
            this.insight.ui.showToast(`Picker Tool ${this.active ? 'Activated (Click an object)' : 'Deactivated'}`);
            if (this.active) {
                document.body.style.cursor = 'crosshair';
            } else {
                document.body.style.cursor = 'default';
            }
        }

        onClick(e) {
            if (!this.active || !this.insight.modules.detector.lastCamera || !this.insight.modules.detector.lastScene) return;
            e.preventDefault();
            e.stopPropagation();
            
            const renderer = this.insight.modules.detector.lastRenderer;
            const canvas = renderer ? renderer.domElement : document.body;
            const rect = canvas.getBoundingClientRect();
            
            const mouse = new window.THREE.Vector2();
            mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
            mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;

            this.raycaster.setFromCamera(mouse, this.insight.modules.detector.lastCamera);
            const intersects = this.raycaster.intersectObjects(this.insight.modules.detector.lastScene.children, true);
            
            if (intersects.length > 0) {
                this.insight.emit('inspect-object', intersects[0].object);
                this.insight.ui.showToast(`Selected: ${intersects[0].object.name || intersects[0].object.type}`);
            }
            this.toggle(); // Auto-disable after picking
        }
    }

    /**
     * =========================================================================
     * 3. ASSET EXPLORER (Snapshots, Gallery Views, Downloads)
     * =========================================================================
     */

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

        openWindow() {
            const win = this.insight.ui.createWindow('asset_explorer', 'Asset Explorer', 'package');
            if (win.el.style.width === 'auto') {
                win.el.style.width = '750px';
                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 style="margin-top: auto; padding: 10px; border-top: 1px solid var(--border);">
                            <button id="btn-snapshot" class="btn" style="width: 100%; justify-content: center; margin-bottom: 6px;">
                                <i data-lucide="camera"></i> Snapshot
                            </button>
                            <label style="display: flex; align-items: center; gap: 6px; font-size: 11px;">
                                <input type="checkbox" id="chk-delta" disabled> Show Delta (Leaks)
                            </label>
                        </div>
                    </div>
                    <div class="main-content">
                        <div style="padding: 8px; border-bottom: 1px solid var(--border); display: flex; gap: 8px;">
                            <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: 12px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); align-content: start;">
                        </div>
                    </div>
                </div>
            `;

            let currentTab = 'textures';
            let searchQuery = '';
            let snapshot = null;
            let showDelta = false;
            
            const listEl = win.content.querySelector('#asset-list');

            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);

                // Filter by Snapshot Delta
                if (showDelta && snapshot) {
                    items = items.filter(i => !snapshot.has(i.uuid));
                }

                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; display: flex; flex-direction: column; gap: 6px;';
                    
                    let meta = '';
                    let preview = '';
                    
                    if (currentTab === 'textures') {
                        meta = `<div style="font-size: 10px;">${item.image ? item.image.width+'x'+item.image.height : 'No Image'} | ${item.format}</div>`;
                        
                        // Gallery View implementation
                        if (item.image && item.image.src) {
                            preview = `<div style="height: 100px; background: #000 url(${item.image.src}) center/contain no-repeat; border-radius: 4px; margin-bottom: 6px;"></div>`;
                            meta += `<button class="btn dl-btn" data-url="${item.image.src}" style="width: 100%; margin-top: 6px; padding: 4px;">Download Map</button>`;
                        } else if (item.image && item.image instanceof HTMLCanvasElement) {
                            preview = `<div style="height: 100px; background: #000 url(${item.image.toDataURL()}) center/contain no-repeat; border-radius: 4px; margin-bottom: 6px;"></div>`;
                            meta += `<button class="btn dl-btn" data-url="${item.image.toDataURL()}" style="width: 100%; margin-top: 6px; padding: 4px;">Download Map</button>`;
                        }
                    } else if (currentTab === 'materials') {
                        meta = `<div style="font-size: 10px;">Type: ${item.type}<br>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 style="font-size: 10px;">Verts: ${verts.toLocaleString()}<br>Tris: ${Math.floor(tris).toLocaleString()}</div>`;
                        meta += `<button class="btn exp-btn" style="width: 100%; margin-top: 6px; padding: 4px;">Export OBJ</button>`;
                    }

                    card.innerHTML = `
                        ${preview}
                        <div style="font-weight: 600; font-size: 12px; color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${name}">${name}</div>
                        <div style="color: var(--text-muted); font-family: var(--font-mono);">${meta}</div>
                    `;

                    // Actions
                    const dlBtn = card.querySelector('.dl-btn');
                    if(dlBtn) {
                        dlBtn.onclick = () => {
                            const a = document.createElement('a');
                            a.href = dlBtn.getAttribute('data-url');
                            a.download = `${name}.png`;
                            a.click();
                        };
                    }

                    const expBtn = card.querySelector('.exp-btn');
                    if(expBtn) {
                        expBtn.onclick = () => {
                            const mesh = new window.THREE.Mesh(item, new window.THREE.MeshBasicMaterial());
                            mesh.name = name;
                            exportToOBJ(mesh);
                        };
                    }

                    listEl.appendChild(card);
                });
            };

            // Memory Snapshot Logic
            const snapBtn = win.content.querySelector('#btn-snapshot');
            const chkDelta = win.content.querySelector('#chk-delta');
            
            snapBtn.onclick = () => {
                snapshot = new Set([
                    ...Array.from(this.insight.modules.detector.textures).map(t => t.uuid),
                    ...Array.from(this.insight.modules.detector.materials).map(m => m.uuid),
                    ...Array.from(this.insight.modules.detector.geometries).map(g => g.uuid)
                ]);
                chkDelta.disabled = false;
                chkDelta.checked = true;
                showDelta = true;
                this.insight.ui.showToast(`Memory Snapshot Taken. Identifying Deltas.`);
                renderItems();
            };

            chkDelta.onchange = (e) => {
                showDelta = e.target.checked;
                renderItems();
            };

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

            win.content.querySelector('#asset-search').addEventListener('input', (e) => {
                searchQuery = e.target.value;
                renderItems();
            });

            const cleanup = this.insight.modules.detector.on('asset-added', () => {
                if(!showDelta) renderItems();
            });
            win.onClose(cleanup);

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

    /**
     * =========================================================================
     * 4. ENTITY INSPECTOR (Live Editing + Two-Way Binding + Shader Viewer)
     * =========================================================================
     */

    class EntityInspector extends Module {
        initUI() {
            this.insight.commands.registerCommand('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', 'Inspector', 'sliders');
            if (win.el.style.width === 'auto') {
                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.</div>';
                return;
            }

            // Input UI Generators
            const createNum = (key) => {
                this.liveRefs[key] = Math.random().toString(36).substr(2, 9);
                return `<input id="${this.liveRefs[key]}" class="live-input" type="number" step="0.1">`;
            };
            const createBool = (key) => {
                this.liveRefs[key] = Math.random().toString(36).substr(2, 9);
                return `<input id="${this.liveRefs[key]}" type="checkbox">`;
            };
            const createColor = (key) => {
                this.liveRefs[key] = Math.random().toString(36).substr(2, 9);
                return `<input id="${this.liveRefs[key]}" type="color" style="background:transparent;border:none;cursor:pointer;width:24px;height:24px;padding:0;">`;
            };

            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;">${title}</div>
                    ${inner}
                </div>
            `;
            const row = (lbl, val) => `
                <div style="display: flex; justify-content: space-between; align-items:center; margin-bottom: 4px; font-size: 12px;">
                    <span style="color: var(--text-muted);">${lbl}</span>
                    <span style="font-family: var(--font-mono); color: var(--text-main);">${val}</span>
                </div>
            `;

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

            if (obj.position) {
                html += section('Transform', 
                    row('Position', `${createNum('px')} ${createNum('py')} ${createNum('pz')}`) +
                    row('Rotation', `${createNum('rx')} ${createNum('ry')} ${createNum('rz')}`) +
                    row('Scale', `${createNum('sx')} ${createNum('sy')} ${createNum('sz')}`)
                );
            }

            if (obj.material) {
                const mat = obj.material;
                html += section('Material', 
                    row('Type', mat.type) +
                    (mat.color ? row('Color (Live)', createColor('mCol')) : '') +
                    row('Wireframe', createBool('mWire')) +
                    (mat.isShaderMaterial || mat.vertexShader ? `<button id="btn-shader" class="btn" style="width:100%; margin-top:8px;">Inspect Shader Source</button>` : '')
                );
            }

            html += section('Properties', 
                row('Visible', createBool('visible')) + 
                `<button id="btn-export" class="btn" style="width:100%; margin-top:8px;">Export as OBJ</button>`
            );

            content.innerHTML = html;

            // Bind Event Listeners for Live Editing (Two-Way Data Binding)
            const bindEl = (key, event, handler) => {
                const el = content.querySelector('#' + this.liveRefs[key]);
                if (el) {
                    el.addEventListener(event, handler);
                    this.liveRefs[key] = el; // Store DOM element
                }
            };

            if (obj.position) {
                bindEl('px', 'input', e => obj.position.x = parseFloat(e.target.value));
                bindEl('py', 'input', e => obj.position.y = parseFloat(e.target.value));
                bindEl('pz', 'input', e => obj.position.z = parseFloat(e.target.value));
                bindEl('rx', 'input', e => obj.rotation.x = parseFloat(e.target.value));
                bindEl('ry', 'input', e => obj.rotation.y = parseFloat(e.target.value));
                bindEl('rz', 'input', e => obj.rotation.z = parseFloat(e.target.value));
                bindEl('sx', 'input', e => obj.scale.x = parseFloat(e.target.value));
                bindEl('sy', 'input', e => obj.scale.y = parseFloat(e.target.value));
                bindEl('sz', 'input', e => obj.scale.z = parseFloat(e.target.value));
            }

            bindEl('visible', 'change', e => obj.visible = e.target.checked);

            if (obj.material) {
                bindEl('mWire', 'change', e => { obj.material.wireframe = e.target.checked; obj.material.needsUpdate = true; });
                bindEl('mCol', 'input', e => { if(obj.material.color) obj.material.color.set(e.target.value); });
                
                // Shader Source Viewer
                const btnShader = content.querySelector('#btn-shader');
                if (btnShader) {
                    btnShader.onclick = () => {
                        const win = this.insight.ui.createWindow('shader_view', 'Shader Source', 'code');
                        win.el.style.width = '700px'; win.el.style.height = '500px';
                        win.content.innerHTML = `
                            <div style="display:flex; height:100%;">
                                <textarea style="flex:1; background:#111; color:#10B981; font-family:var(--font-mono); font-size:11px; padding:10px; border:none; outline:none; resize:none;" readonly>// VERTEX SHADER\n${obj.material.vertexShader}</textarea>
                                <textarea style="flex:1; background:#111; color:#3B82F6; font-family:var(--font-mono); font-size:11px; padding:10px; border:none; outline:none; resize:none; border-left:1px solid var(--border);" readonly>// FRAGMENT SHADER\n${obj.material.fragmentShader}</textarea>
                            </div>`;
                    };
                }
            }

            const btnExport = content.querySelector('#btn-export');
            if (btnExport) btnExport.onclick = () => exportToOBJ(obj);
        }

        updateLiveValues() {
            const obj = this.activeObject;
            const refs = this.liveRefs;
            
            const updateNum = (el, val) => { if (el && document.activeElement !== el) el.value = typeof val === 'number' ? val.toFixed(3) : val; };
            const updateBool = (el, val) => { if (el && document.activeElement !== el) el.checked = val; };
            const updateCol = (el, val) => { if (el && document.activeElement !== el && val) el.value = '#' + val.getHexString(); };

            if (obj.position) {
                updateNum(refs.px, obj.position.x); updateNum(refs.py, obj.position.y); updateNum(refs.pz, obj.position.z);
                updateNum(refs.rx, obj.rotation.x); updateNum(refs.ry, obj.rotation.y); updateNum(refs.rz, obj.rotation.z);
                updateNum(refs.sx, obj.scale.x); updateNum(refs.sy, obj.scale.y); updateNum(refs.sz, obj.scale.z);
            }
            updateBool(refs.visible, obj.visible);
            
            if (obj.material) {
                updateBool(refs.mWire, obj.material.wireframe);
                updateCol(refs.mCol, obj.material.color);
            }
        }
    }

    /**
     * =========================================================================
     * 5. SCENE EXPLORER (With Heavy Bottleneck Highlighting)
     * =========================================================================
     */

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

        openWindow() {
            const win = this.insight.ui.createWindow('scene_explorer', 'Hierarchy', 'layers');
            if (win.el.style.width === 'auto') {
                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()));
            const cleanup = this.insight.modules.detector.on('asset-added', (d) => {
                if (d.type === 'scene') this.renderTree(treeContainer, search.value.toLowerCase());
            });
            win.onClose(cleanup);
            this.renderTree(treeContainer, '');
        }

        renderTree(container, filterText) {
            container.innerHTML = '';
            const scenes = Array.from(this.insight.modules.detector.scenes);
            if (scenes.length === 0) 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';

                // Performance Bottleneck logic
                let isHeavy = false;
                if (object.geometry) {
                    const verts = object.geometry.attributes?.position?.count || 0;
                    if (verts > 100000) isHeavy = true;
                }

                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>`;
                const iconHtml = `<i data-lucide="${object.type === 'Mesh' ? 'box' : 'cuboid'}" style="width: 14px; margin-right: 6px; color: ${isHeavy ? '#EF4444' : 'var(--text-muted)'};"></i>`;
                
                row.innerHTML = `
                    ${chevronHtml}${iconHtml}
                    <span style="font-size: 13px; color: ${isHeavy ? '#EF4444' : (object.visible ? 'var(--text-main)' : 'var(--text-muted)')};">${object.name || object.type || 'Object3D'}</span> 
                    ${isHeavy ? '<span style="font-size:9px;color:#EF4444;margin-left:auto;font-weight:bold;">HEAVY</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"]').onclick = (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);
        }
    }

    /**
     * =========================================================================
     * 6. RUNTIME OBJECT EXPLORER (The Graph View)
     * =========================================================================
     */

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

        openWindow() {
            const win = this.insight.ui.createWindow('runtime_graph', 'Runtime Graph', 'share-2');
            if (win.el.style.width === 'auto') {
                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;
                if(transform.x === 0) {
                    transform.x = canvas.width / 2;
                    transform.y = canvas.height / 2;
                }
            };
            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');
                        
                        s.children.forEach(c => {
                            const cNode = addNode(c, c.name || c.type, 'object');
                            addEdge(sNode, cNode, 'child');
                        });
                    });
                });
            };

            buildGraph();

            // Spring Physics 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;
                
                // Layout Physics
                if (!dragNode) {
                    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;
                            }
                        }
                    }
                    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;
                    });
                    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);
        }
    }

    /**
     * =========================================================================
     * 7. NETWORK ANALYZER 
     * =========================================================================
     */

    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(); // Safety limit
            this.emit('new-request', req);
        }

        openWindow() {
            const win = this.insight.ui.createWindow('network_analyzer', 'Network', 'globe');
            if(win.el.style.width === 'auto') { 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);
        }
    }

    /**
     * =========================================================================
     * 8. FUNCTION TRACER (With Helper UI)
     * =========================================================================
     */

    class FunctionTracer extends Module {
        initUI() {
            this.insight.commands.registerCommand('Function Hooks', 'activity', () => this.openWindow());
        }
        
        trace(obj, methodName) {
            if(!obj || !obj[methodName]) return;
            const orig = obj[methodName];
            if(orig.__isHooked) return;
            
            const self = this;
            obj[methodName] = function(...args) {
                const start = performance.now();
                const res = orig.apply(this, args);
                self.emit('trace', { method: methodName, duration: performance.now() - start });
                return res;
            };
            obj[methodName].__isHooked = true;
            this.insight.ui.showToast(`Hook attached to ${methodName}`);
        }

        openWindow() {
            const win = this.insight.ui.createWindow('func_tracer', 'Function Hooks', 'activity');
            if (win.el.style.width === 'auto') win.el.style.width = '400px';
            
            const hookBtn = (label, target, method) => {
                const btn = document.createElement('button');
                btn.className = 'btn';
                btn.style.width = '100%';
                btn.style.marginBottom = '8px';
                btn.innerHTML = `<i data-lucide="zap"></i> Hook ${label}`;
                btn.onclick = () => { if(window.THREE && window.THREE[target]) this.trace(window.THREE[target].prototype, method); };
                return btn;
            };

            const container = document.createElement('div');
            container.style.padding = '16px';
            container.appendChild(hookBtn('Renderer.render', 'WebGLRenderer', 'render'));
            container.appendChild(hookBtn('Raycaster.intersectObject', 'Raycaster', 'intersectObject'));
            container.appendChild(hookBtn('Object3D.updateMatrixWorld', 'Object3D', 'updateMatrixWorld'));
            
            win.content.appendChild(container);
            this.insight.ui.refreshIcons(win.content);
        }
    }

    /**
     * =========================================================================
     * 9. PERFORMANCE MONITOR
     * =========================================================================
     */

    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('perf_mon', '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 });
            win.onClose(this.on('stats', updateUI));
        }
    }

    /**
     * =========================================================================
     * 10. DEVELOPER CONSOLE
     * =========================================================================
     */

    class DeveloperConsole extends Module {
        initUI() {
            this.insight.commands.registerCommand('Developer Console', 'terminal', () => this.openWindow());
        }
        
        openWindow() {
            const win = this.insight.ui.createWindow('dev_console', 'Console', 'terminal');
            if (win.el.style.width === 'auto') { 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; 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); 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;
                }
            });
        }
    }

    /**
     * =========================================================================
     * 11. COMMAND PALETTE & SETTINGS & PLUGINS 
     * =========================================================================
     */

    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...';

            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);

            window.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') {
                    e.preventDefault();
                    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(); });
            
            // Helpful default guide command
            this.registerCommand('Open Developer Guide', 'help-circle', () => {
                const win = this.insight.ui.createWindow('dev_guide', 'Developer Guide', 'help-circle');
                win.el.style.width = '500px';
                win.content.innerHTML = `
                    <div style="padding: 16px; line-height: 1.6; color: var(--text-muted);">
                        <h2 style="color: var(--text-main); margin-top: 0;">Scripting Insight</h2>
                        <p>Interact with applications using the <code style="color: var(--accent);">insight</code> global object via Developer Console.</p>
                        <h3 style="color: var(--text-main);">Quick Tips:</h3>
                        <ul style="padding-left: 20px;">
                            <li>Assets: <code>insight.modules.detector.textures</code></li>
                            <li>Inspect: <code>insight.emit('inspect-object', mesh)</code></li>
                            <li>Commands: <code>insight.modules.commands.registerCommand(...)</code></li>
                        </ul>
                    </div>`;
            });
        }

        registerCommand(name, icon, action) { this.commands.push({ name, icon, action }); }
        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(q) {
            this.filtered = this.commands.filter(c => c.name.toLowerCase().includes(q.toLowerCase()));
            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.onclick = () => { this.hide(); cmd.action(); };
                item.onmouseenter = () => { this.selectedIndex = idx; this.renderList(); };
                this.list.appendChild(item);
            });
            this.insight.ui.refreshIcons(this.list);
            const sel = this.list.children[this.selectedIndex];
            if (sel) sel.scrollIntoView({ block: 'nearest' });
        }
    }

    class SettingsManager extends Module {
        initUI() { this.insight.commands.registerCommand('Settings', 'settings', () => this.openWindow()); }
        openWindow() {
            const win = this.insight.ui.createWindow('settings', '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><br>
                    <button class="btn" style="margin-top: 16px;" onclick="localStorage.clear(); alert('Local storage cleared. Refresh page to reset windows.');">Reset Window Positions</button>
                </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 All Modules
            this.registerModule('detector', new ThreeDetector(this));
            this.registerModule('performance', new PerformanceMonitor(this));
            this.registerModule('commands', new CommandPalette(this));
            this.registerModule('raycaster', new PickerTool(this));
            this.registerModule('scene', new SceneExplorer(this));
            this.registerModule('inspector', new EntityInspector(this));
            this.registerModule('assets', new AssetExplorer(this));
            this.registerModule('runtimeGraph', new RuntimeObjectExplorer(this));
            this.registerModule('network', new NetworkAnalyzer(this));
            this.registerModule('console', new DeveloperConsole(this));
            this.registerModule('tracer', new FunctionTracer(this));
            this.registerModule('settings', new SettingsManager(this));
            this.registerModule('plugins', new PluginManager(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] Advanced Editor Mode 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();
    }

})();