{
  "case_id": "A3",
  "case_name": "3D体素宝塔",
  "prompt": "Create a 3D voxel pagoda project with ~500 lines of bug-free code.\nRequirements:\n1. Interactive camera controls (mouse drag to rotate)\n2. Dynamic lighting effects\n3. Traditional Chinese pagoda architecture (5-7 layers)\n4. Use Three.js with ESM import\n5. Single HTML file",
  "result": {
    "success": true,
    "content": "Here is a complete, single-file HTML solution containing a procedural Voxel Pagoda generator using Three.js.\n\nIt features:\n1.  **InstancedMesh Rendering**: Efficiently renders thousands of voxels.\n2.  **Procedural Architecture**: Algorithms to generate walls, balconies, and curved \"flying eave\" roofs typical of Chinese architecture.\n3.  **Dynamic Lighting**: Orbiting \"firefly\" lights and warm internal glow.\n4.  **Interactive Controls**: Mouse drag to rotate/zoom.\n\nSave this code as `index.html` and open it in a modern browser.\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>3D Voxel Pagoda</title>\n    <style>\n        body {\n            margin: 0;\n            overflow: hidden;\n            background-color: #050505; /* Dark night background */\n            font-family: 'Courier New', Courier, monospace;\n        }\n        #info {\n            position: absolute;\n            top: 10px;\n            left: 10px;\n            color: #ffffff;\n            background: rgba(0, 0, 0, 0.7);\n            padding: 10px;\n            border-radius: 5px;\n            pointer-events: none;\n            user-select: none;\n        }\n        #loading {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            color: white;\n            font-size: 24px;\n            transition: opacity 0.5s;\n        }\n    </style>\n</head>\n<body>\n\n    <div id=\"info\">\n        <strong>Voxel Pagoda Generator</strong><br>\n        Left Click: Rotate<br>\n        Right Click: Pan<br>\n        Scroll: Zoom\n    </div>\n    <div id=\"loading\">Generating Voxel Geometry...</div>\n\n    <!-- Import Three.js as ESM -->\n    <script type=\"importmap\">\n        {\n            \"imports\": {\n                \"three\": \"https://unpkg.com/three@0.160.0/build/three.module.js\",\n                \"three/addons/\": \"https://unpkg.com/three@0.160.0/examples/jsm/\"\n            }\n        }\n    </script>\n\n    <script type=\"module\">\n        import * as THREE from 'three';\n        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\n        // --- Configuration ---\n        const CONFIG = {\n            layers: 7,\n            baseSize: 13, // Width of the bottom layer (odd number preferred)\n            voxelSize: 1,\n            colors: {\n                roof: 0x2c3e50,      // Dark Slate Blue/Grey\n                roofHighlight: 0x34495e,\n                wall: 0x880000,      // Traditional Red\n                wallDetail: 0xaa0000,\n                wood: 0x5d4037,      // Dark Wood\n                gold: 0xffb300,      // Gold ornament\n                base: 0x424242,      // Stone Grey\n                white: 0xeeeeee      // Plaster/Paper\n            }\n        };\n\n        // --- Voxel Engine ---\n        // Helper class to manage voxel data before rendering\n        class VoxelBuilder {\n            constructor() {\n                this.voxels = []; // Array of {x, y, z, color}\n                this.lookup = new Set(); // For fast collision checking\n            }\n\n            add(x, y, z, color) {\n                const key = `${x},${y},${z}`;\n                if (this.lookup.has(key)) return; // Prevent duplicates\n                \n                this.lookup.add(key);\n                this.voxels.push({ x, y, z, color });\n            }\n\n            // Generate the Three.js InstancedMesh\n            generateMesh() {\n                if (this.voxels.length === 0) return null;\n\n                const geometry = new THREE.BoxGeometry(1, 1, 1);\n                // Round edges slightly for better light interaction\n                // geometry = new THREE.BoxGeometry(0.95, 0.95, 0.95); \n                \n                const material = new THREE.MeshStandardMaterial({\n                    roughness: 0.8,\n                    metalness: 0.1,\n                });\n\n                const mesh = new THREE.InstancedMesh(geometry, material, this.voxels.length);\n                mesh.castShadow = true;\n                mesh.receiveShadow = true;\n\n                const dummy = new THREE.Object3D();\n                const color = new THREE.Color();\n\n                for (let i = 0; i < this.voxels.length; i++) {\n                    const v = this.voxels[i];\n                    dummy.position.set(v.x, v.y, v.z);\n                    dummy.updateMatrix();\n                    mesh.setMatrixAt(i, dummy.matrix);\n                    mesh.setColorAt(i, color.setHex(v.color));\n                }\n\n                mesh.instanceMatrix.needsUpdate = true;\n                mesh.instanceColor.needsUpdate = true;\n                return mesh;\n            }\n        }\n\n        // --- Procedural Generation Logic ---\n\n        function buildPagoda(builder) {\n            let currentY = 0;\n            let currentWidth = CONFIG.baseSize;\n\n            // 1. Build Stone Base (Platform)\n            const baseHeight = 3;\n            const baseWidth = currentWidth + 8;\n            \n            for (let y = 0; y < baseHeight; y++) {\n                const layerW = baseWidth - (y > 0 ? 1 : 0); // Step in slightly\n                for (let x = -layerW; x <= layerW; x++) {\n                    for (let z = -layerW; z <= layerW; z++) {\n                        // Create steps\n                        if (Math.abs(x) > layerW - 1 || Math.abs(z) > layerW - 1) {\n                            if (y < baseHeight - 1) builder.add(x, currentY + y, z, CONFIG.colors.base);\n                        } else {\n                            builder.add(x, currentY + y, z, CONFIG.colors.base);\n                        }\n                    }\n                }\n            }\n            currentY += baseHeight;\n\n            // 2. Build Layers\n            for (let layer = 0; layer < CONFIG.layers; layer++) {\n                const isTop = layer === CONFIG.layers - 1;\n                \n                // A. Walls (The body)\n                const wallHeight = 4;\n                for (let y = 0; y < wallHeight; y++) {\n                    for (let x = -currentWidth; x <= currentWidth; x++) {\n                        for (let z = -currentWidth; z <= currentWidth; z++) {\n                            // Hollow inside logic\n                            const isEdge = Math.abs(x) === currentWidth || Math.abs(z) === currentWidth;\n                            \n                            if (isEdge) {\n                                // Pillars at corners\n                                const isCorner = Math.abs(x) === currentWidth && Math.abs(z) === currentWidth;\n                                \n                                if (isCorner) {\n                                    builder.add(x, currentY + y, z, CONFIG.colors.wood);\n                                } else {\n                                    // Windows/Doors pattern\n                                    if ((x === 0 || z === 0) && y < 3) {\n                                        // Doorway\n                                        // Empty space\n                                    } else if (y === 2 && (x % 2 !== 0 || z % 2 !== 0)) {\n                                        // Lattice work\n                                        builder.add(x, currentY + y, z, CONFIG.colors.white);\n                                    } else {\n                                        builder.add(x, currentY + y, z, CONFIG.colors.wall);\n                                    }\n                                }\n                            } else if (y === 0 || y === wallHeight - 1) {\n                                // Floor and Ceiling inside\n                                builder.add(x, currentY + y, z, CONFIG.colors.wood);\n                            } else if (Math.abs(x) < currentWidth -1 && Math.abs(z) < currentWidth -1) {\n                                // Interior core (optional, for stability if physics existed)\n                                if (Math.abs(x) <= 1 && Math.abs(z) <= 1)\n                                     builder.add(x, currentY + y, z, CONFIG.colors.wallDetail);\n                            }\n                        }\n                    }\n                }\n                \n                // B. Balcony / Railing (for layers above ground)\n                if (layer > 0) {\n                    const railWidth = currentWidth + 1;\n                    for (let x = -railWidth; x <= railWidth; x++) {\n                        for (let z = -railWidth; z <= railWidth; z++) {\n                            const isRailEdge = Math.abs(x) === railWidth || Math.abs(z) === railWidth;\n                            if (isRailEdge) {\n                                // Railing posts\n                                if (Math.abs(x) % 2 === 0 && Math.abs(z) % 2 === 0) {\n                                     builder.add(x, currentY, z, CONFIG.colors.wood);\n                                     builder.add(x, currentY + 1, z, CONFIG.colors.wood);\n                                } else {\n                                     // Horizontal rail\n                                     builder.add(x, currentY + 1, z, CONFIG.colors.wood);\n                                }\n                                // Floor under rail\n                                builder.add(x, currentY - 1, z, CONFIG.colors.wood);\n                            }\n                        }\n                    }\n                }\n\n                currentY += wallHeight;\n\n                // C. Roof Generation (The complex part)\n                // Roof overhangs significantly\n                const roofOverhang = 3; \n                const roofBaseWidth = currentWidth + roofOverhang;\n                const roofHeight = 3;\n\n                for (let rx = -roofBaseWidth; rx <= roofBaseWidth; rx++) {\n                    for (let rz = -roofBaseWidth; rz <= roofBaseWidth; rz++) {\n                        \n                        // Calculate distance from center (Manhattan or Chebyshev distance for square roofs)\n                        const distX = Math.abs(rx);\n                        const distZ = Math.abs(rz);\n                        const maxDist = Math.max(distX, distZ); // Square rings\n                        \n                        // Only build if we are outside the wall (or slightly inside for overlap)\n                        if (maxDist > currentWidth - 1) {\n                            \n                            // Calculate Slope\n                            // The further out, the lower.\n                            // However, we want \"Flying Eaves\" (corners curve up)\n                            \n                            const distFromWall = maxDist - currentWidth;\n                            let drop = distFromWall; // Linear slope down\n                            \n                            // Curve logic:\n                            // If it's a corner (distX approx distZ), reduce the drop (curve up)\n                            const isCornerArea = Math.abs(distX - distZ) < 2;\n                            if (isCornerArea && distFromWall > 1) {\n                                drop -= 1; // Lift corner\n                                if (distFromWall > 2) drop -= 1; // Lift tip more\n                            }\n\n                            // Invert drop to get Y position relative to roof start\n                            let voxelY = currentY - drop;\n                            \n                            // Don't go below previous layer visually too much\n                            // Add roof tile\n                            const color = (maxDist % 2 === 0) ? CONFIG.colors.roof : CONFIG.colors.roofHighlight;\n                            builder.add(rx, voxelY, rz, color);\n\n                            // Add Gold tips to corners\n                            if (isCornerArea && maxDist === roofBaseWidth) {\n                                builder.add(rx, voxelY + 1, rz, CONFIG.colors.gold);\n                            }\n                        }\n                    }\n                }\n                \n                // Increment Y for next layer, but overlap with roof height slightly\n                currentY += 1; \n\n                // Shrink the next layer\n                if (currentWidth > 3) currentWidth -= 1;\n            }\n\n            // 3. The Spire (Top Ornament)\n            const spireHeight = 6;\n            for (let y = 0; y < spireHeight; y++) {\n                if (y === 0) {\n                    // Base of spire\n                    for(let x=-1; x<=1; x++)\n                        for(let z=-1; z<=1; z++)\n                            builder.add(x, currentY+y, z, CONFIG.colors.roof);\n                } else if (y % 2 !== 0) {\n                    // Ring\n                    builder.add(0, currentY+y, 0, CONFIG.colors.gold);\n                    builder.add(1, currentY+y, 0, CONFIG.colors.gold);\n                    builder.add(-1, currentY+y, 0, CONFIG.colors.gold);\n                    builder.add(0, currentY+y, 1, CONFIG.colors.gold);\n                    builder.add(0, currentY+y, -1, CONFIG.colors.gold);\n                } else {\n                    // Pole\n                    builder.add(0, currentY+y, 0, CONFIG.colors.gold);\n                }\n            }\n        }\n\n        // --- Main Application ---\n\n        let scene, camera, renderer, controls;\n        let pagodaMesh;\n        let fireflies = [];\n        const clock = new THREE.Clock();\n\n        function init() {\n            // 1. Setup Scene\n            scene = new THREE.Scene();\n            scene.background = new THREE.Color(0x050510);\n            scene.fog = new THREE.FogExp2(0x050510, 0.015);\n\n            // 2. Setup Camera\n            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);\n            camera.position.set(35, 30, 35);\n\n            // 3. Setup Renderer\n            renderer = new THREE.WebGLRenderer({ antialias: true });\n            renderer.setSize(window.innerWidth, window.innerHeight);\n            renderer.shadowMap.enabled = true;\n            renderer.shadowMap.type = THREE.PCFSoftShadowMap;\n            document.body.appendChild(renderer.domElement);\n\n            // 4. Controls\n            controls = new OrbitControls(camera, renderer.domElement);\n            controls.enableDamping = true;\n            controls.dampingFactor = 0.05;\n            controls.autoRotate = true;\n            controls.autoRotateSpeed = 0.5;\n            controls.maxPolarAngle = Math.PI / 2 - 0.05; // Prevent going under ground\n\n            // 5. Lighting\n            setupLighting();\n\n            // 6. Generate Pagoda\n            const builder = new VoxelBuilder();\n            buildPagoda(builder);\n            pagodaMesh = builder.generateMesh();\n            \n            if (pagodaMesh) {\n                scene.add(pagodaMesh);\n                // Center the pagoda vertically roughly\n                const box = new THREE.Box3().setFromObject(pagodaMesh);\n                const center = box.getCenter(new THREE.Vector3());\n                controls.target.set(0, center.y / 2, 0);\n            }\n\n            // 7. Environment (Ground)\n            const groundGeo = new THREE.PlaneGeometry(200, 200);\n            const groundMat = new THREE.MeshStandardMaterial({ \n                color: 0x1a1a1a, \n                roughness: 1, \n                metalness: 0 \n            });\n            const ground = new THREE.Mesh(groundGeo, groundMat);\n            ground.rotation.x = -Math.PI / 2;\n            ground.position.y = -0.5; // Just below the lowest voxel\n            ground.receiveShadow = true;\n            scene.add(ground);\n\n            // 8. Fireflies (Dynamic Light Visuals)\n            setupFireflies();\n\n            // Remove Loading Text\n            document.getElementById('loading').style.opacity = 0;\n\n            // Event Listeners\n            window.addEventListener('resize', onWindowResize);\n        }\n\n        function setupLighting() {\n            // Ambient\n            const ambientLight = new THREE.AmbientLight(0x404060, 0.5); // Blueish moonlight base\n            scene.add(ambientLight);\n\n            // Moonlight (Directional)\n            const dirLight = new THREE.DirectionalLight(0xaaccff, 1.5);\n            dirLight.position.set(-50, 80, -30);\n            dirLight.castShadow = true;\n            dirLight.shadow.mapSize.width = 2048;\n            dirLight.shadow.mapSize.height = 2048;\n            dirLight.shadow.camera.near = 0.5;\n            dirLight.shadow.camera.far = 200;\n            dirLight.shadow.camera.left = -50;\n            dirLight.shadow.camera.right = 50;\n            dirLight.shadow.camera.top = 50;\n            dirLight.shadow.camera.bottom = -50;\n            scene.add(dirLight);\n\n            // Internal Warm Glow (Point Lights)\n            // Place a few lights inside the pagoda structure\n            for(let i=1; i<=CONFIG.layers; i+=2) {\n                const yPos = i * 4 + 2;\n                const pLight = new THREE.PointLight(0xffaa00, 2, 15);\n                pLight.position.set(0, yPos, 0);\n                scene.add(pLight);\n            }\n        }\n\n        function setupFireflies() {\n            const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);\n            const material = new THREE.MeshBasicMaterial({ color: 0xaaffaa });\n            \n            for (let i = 0; i < 30; i++) {\n                const mesh = new THREE.Mesh(geometry, material);\n                const angle = Math.random() * Math.PI * 2;\n                const radius = 15 + Math.random() * 20;\n                const y = 5 + Math.random() * 25;\n                \n                mesh.position.set(\n                    Math.cos(angle) * radius,\n                    y,\n                    Math.sin(angle) * radius\n                );\n                \n                // Custom properties for animation\n                mesh.userData = {\n                    angle: angle,\n                    radius: radius,\n                    speed: 0.2 + Math.random() * 0.5,\n                    yBase: y,\n                    yOffset: Math.random() * Math.PI\n                };\n                \n                // Add a point light to the firefly\n                const light = new THREE.PointLight(0x88ff88, 1, 10);\n                mesh.add(light);\n                \n                scene.add(mesh);\n                fireflies.push(mesh);\n            }\n        }\n\n        function onWindowResize() {\n            camera.aspect = window.innerWidth / window.innerHeight;\n            camera.updateProjectionMatrix();\n            renderer.setSize(window.innerWidth, window.innerHeight);\n        }\n\n        function animate() {\n            requestAnimationFrame(animate);\n\n            const delta = clock.getDelta();\n            const time = clock.getElapsedTime();\n\n            controls.update();\n\n            // Animate Fireflies\n            fireflies.forEach(fly => {\n                const u = fly.userData;\n                u.angle += u.speed * delta;\n                \n                fly.position.x = Math.cos(u.angle) * u.radius;\n                fly.position.z = Math.sin(u.angle) * u.radius;\n                fly.position.y = u.yBase + Math.sin(time + u.yOffset) * 2;\n                \n                // Subtle rotation of the cube itself\n                fly.rotation.x += delta;\n                fly.rotation.y += delta;\n            });\n\n            renderer.render(scene, camera);\n        }\n\n        // Start\n        init();\n        animate();\n\n    </script>\n</body>\n</html>\n```",
    "model": "gemini-3-pro-preview",
    "usage": {
      "prompt_tokens": 95,
      "completion_tokens": 6598,
      "total_tokens": 6693
    },
    "timestamp": "2025-11-19 13:26:35"
  },
  "needs_screenshot": true,
  "needs_manual_test": true
}