DeepSeek-Flash, GLM-5.3-Flash, 小米 MiMo-V2.6-Flash & Space Bunny & MiniMax M3.1 Flash 对比

对比主流模型的物理世界理解

6 个模型 · AI拯救打工人 · 分享快照
分享与嵌入

社交平台直接发布;GitHub/README 使用 Markdown 或 Badge。

XFacebook微博LinkedIn
[![DeepSeek-Flash, GLM-5.3-Flash, 小米 MiMo-V2.6-Flash & Space Bunny & MiniMax M3.1 Flash 对比 在 TOKRACE 上的速度结果](/api/badge/share/O643gCkkMe?locale=zh-CN)](/zh-CN/r/O643gCkkMe)
Design and create a very creative, elaborate, and detailed voxel art scene of a pagoda in a beautiful garden with trees, including some cherry blossoms, add a village with people living in it. Make the scene impressive and varied and use colorful voxels. Make it really detailed use as many voxels as you want, we have a power machine here. Create a single HTML file.
复跑这次评测完整复跑会带走标题、备注、Prompt 和模型;Prompt 复跑只复用任务文本。
显示
🏁 实时竞速当前阶段速度 token/s · 快的在前思考 / 正文量 · token首响等待 · 短更好

黄色显示思考TPS,绿色显示正文输出TPS;进行中为实时估算,结束后与卡片「输出TPS」一致。速度不代表回答质量。

0500 token/s
DeepSeek-Flash
⚡331 token/s
51,502 token
🚦0.70s
MiniMax-M3.1-Flash-Preview
190 token/s
29,284 token
首响 0.76s
小米 MiMo-V2.6 Flash
102 token/s
96,122 token
首响 1.71s
智谱 GLM-5.3-flash
73 token/s
52,244 token
首响 6.36s
OpenRouter
—
0 token
首响失败
SBSpace Bunny Alpha
—
4,151 token
首响 0.88s
🏁 本轮结算
🏆 最快
DeepSeek-Flash
331 tok/s
✓ 任务效果
尚未评估
DeepSeek-Flash
deepseek-flash
完成

历史结果 · 2026/9/28 07:53:39

The user wants a single HTML file with a detailed voxel art scene. I should use Three.js. Build a pagoda, garden, trees, cherry blossoms, village with people. Use instanced meshes for performance, since we want many voxels. Let me design: - Use Three.js from CDN (importmap with unpkg/jsdelivr). - Voxel world built with a custom voxel builder that merges geometry or uses InstancedMesh per color. - Best approach: use InstancedMesh with a shared BoxGeometry, one instanced mesh per color (palette). That gives huge voxel counts efficiently. Plan: 1. Create a "VoxelWorld" class that stores voxels in a map keyed by "x,y,z" with color. Then build instanced meshes grouped by color. 2. Scene: terrain with rolling hills (voxel heightmap), a river/pond, garden paths, pagoda (multi-tier with roofs), cherry blossom trees, pine trees, village houses with roofs, people (small voxel figures), lanterns, bridges, torii gates, stone lanterns, bamboo, flowers, koi pond, etc. 3. Lighting: hemisphere + directional with shadows. Maybe use sun with shadow map. 4. Camera: OrbitControls, auto-rotate slowly. 5. Sky: gradient background, maybe stars? Daytime with soft blue sky. 6. Post: maybe fog. Scale: 1 voxel = 1 unit. World size maybe 120x120. Pagoda ~ 30 tall. Let me write helper functions: - `setVoxel(x,y,z,color)` — store in map. - `box(x0,y0,z0,x1,y1,z1,color)` fill. - `hollowBox` maybe. Colors: use a palette of hex strings. Since instanced meshes per color, number of distinct colors maybe ~80. Fine. Also could support per-voxel scale? Keep simple: uniform size 1, box geometry 1x1x1. Add slight bevel? Using BoxGeometry with slight edges maybe. For performance, plain boxes. Actually to make it look nicer, could add a tiny gap: box geometry 0.98 to give voxel-art look with visible seams. Yes, that looks good — like MagicaVoxel render with slight gaps. Hmm, gaps reveal background between voxels. At 0.98 gap is tiny. Let's do 0.96 maybe. Actually with shadows, slight gaps fine. Let's use 0.97. Terrain: generate heightmap using simple value noise (deterministic pseudo-random). Height 0-6 range. Then top layer grass color varied, below dirt/stone. Water: separate flat region at a certain level, use transparent blue voxels? Water voxels could be rendered with a separate transparent instanced mesh. Or use actual water plane. Let's make a pond/river with water voxels at y=waterLevel, using a semi-transparent material. Hmm, transparency with instanced mesh and sorting — fine, single mesh. Let's structure: ``` const palette = { grass: 0x6fbf44, ... } ``` Let me write the voxel store: ```js const voxels = new Map(); // key -> color function key(x,y,z){return x+','+y+','+z;} function set(x,y,z,c){ voxels.set(key(x,y,z), c); } ``` But for many voxels this creates strings — millions? We'll have maybe 200k voxels. String keys okay-ish but memory heavy. Use a Map with key = (x+512) + (y+512)*1024 + (z+512)*1024*1024 as number? Numbers up to 1024^3 = 1e9, fine as exact integer. Use that. Let's define: ```js const OX=512, OZ=512, OY=256; function idx(x,y,z){ return ((x+OX) + (z+OZ)*1024 + (y+OY)*1024*1024); } ``` Max = 1024*1024*512 ≈ 5.4e8, fine. Store color index instead of hex string: palette array, store index. Use Map<number, number>. Then build: iterate map, group by color index → arrays of positions. Then for each color create InstancedMesh with count. Set matrix per instance: position + identity rotation, scale 1. Use `mesh.instanceMatrix.needsUpdate`. Shadows: InstancedMesh castShadow/receiveShadow works. Number of colors ~100, instanced meshes ~100 draw calls. Fine. Water: separate set of voxels with transparency — I'll build water voxels into a separate map with a single material. Now the scene design. Let's lay out a coordinate system: world spans x from -60 to 60, z from -60 to 60. Terrain: gentle hills at edges, flat-ish in middle for the garden and village. Water: a river running through, maybe a pond near the pagoda. Let me plan layout: - Center-ish: Pagoda at around (0, ?, -10), a 5-tier pagoda on a stone platform. - Garden around pagoda: raked gravel/sand, moss, stone lanterns, paths. - Cherry blossom trees: several around, big pink canopies. - Pond with koi? maybe too detailed; just a pond with lotus and a bridge. - Village: a cluster of houses at positive x side, with villagers (small figures) walking on paths, plus lanterns, fences, farms with crop rows. - Torii gate at entrance to pagoda area. - Bamboo grove. Terrain height: get height at (x,z) with pseudo-noise, flatten near structures. Let me write a simple deterministic noise: ```js function hash(x,z){ let n = Math.sin(x*127.1 + z*311.7)*43758.5453; return n - Math.floor(n); } function valueNoise(x,z){ const xi=Math.floor(x), zi=Math.floor(z); const xf=x-xi, zf=z-zi; const u=xf*xf*(3-2*xf), v=zf*zf*(3-2*zf); const a=hash(xi,zi), b=hash(xi+1,zi), c=hash(xi,zi+1), d=hash(xi+1,zi+1); return a*(1-u)*(1-v)+b*u*(1-v)+c*(1-u)*v+d*u*v; } ``` Height = sum of octaves, scaled. Terrain design: I want a mostly flat central plaza (y=0 base) with hills rising at the edges to frame the scene. So: ``` base(x,z) = smoothstep of distance from center * hills ``` Let's define: h = 0 for r < 30, then rising. Plus noise. And water pond carved: near (-25, 20) region, height lowered to -2 with water at y=-1. Simplify: ground level 0. Water level -1. Pond area dug down to -3. Actually let's do: terrain surface y = terrainHeight(x,z). Fill from y = -6 up to surface. Let me do: ```js function terrainH(x,z){ const r = Math.hypot(x*0.9, z); let h = 0; // rolling hills away from center const n = fbm(x*0.06, z*0.06) ; // 0..1 const rise = Math.max(0, (r-38)/22); h = rise*rise*10 * (0.5 + n); // small undulation everywhere h += (fbm(x*0.09+13.3, z*0.09-7.7)-0.5)*1.6; return h; } ``` Then round to int. Then flatten in specific areas: village area and pagoda plaza. Hmm, but the pond. Let's define a pond as an ellipse at (28, 26) maybe. Actually put the pond near the pagoda's garden. Let me define regions and flatten: - Plaza (pagoda): circle center (0,-6) radius 18 → h = 0. - Village: rectangle x in [26, 58], z in [-20, 24] → h = 0 (or slight). - Pond: ellipse center (-24, 24), radii 12, 9 → dig to -3, water at -1. - River? Maybe skip river, keep pond. Or add a small stream. Keep pond. Hmm, maybe simpler: big flat area but hills at border. Let's implement blend: ```js function hAt(x,z){ let h = baseHills(x,z); // flatten zones const flat = (cx,cz,rad) => { const d=Math.hypot(x-cx,z-cz); return 1 - smoothstep(rad*0.7, rad, d); } ... } ``` Simpler approach: compute h, then for flatten zones lerp toward 0 with weight w. Let me do it directly: ```js function terrainHeight(x,z){ const n = fbm(x*0.05, z*0.05); const r = Math.sqrt(x*x + z*z); const rise = Math.max(0, (r - 42)/20); let h = rise*rise*14*(0.6+n*0.8); h += (fbm(x*0.13+5, z*0.13+9)-0.5)*2.0; h = Math.max(h, 0); // no negative except pond return h; } ``` Wait but the pond needs a depression. I'll handle the pond by digging: after computing h, if inside pond ellipse, set h = -3 (with smooth edge). Then flatten near center: multiply h by a factor that's 0 in the plaza. Let me just write: ```js const plazaC = {x:0, z:-6, r:20}; const villageC = {x:38, z:2, r:26}; const pondC = {x:-26, z:22, rx:15, rz:11}; ``` h computation: ```js let h = terrainHeight(x,z); // pond const pd = Math.hypot((x-pondC.x)/pondC.rx, (z-pondC.z)/pondC.rz); if (pd < 1.25) { const t = smoothstep(1.25, 0.55, pd); // 0 outside, 1 inside h = h*(1-t) + (-3.5)*t; } // flatten plaza const pdist = Math.hypot(x-plazaC.x, z-plazaC.z); const pt = 1 - smoothstep(plazaC.r*0.6, plazaC.r*1.15, pdist); h = h*(1-pt) + 0*pt; // village flatten const vdist = Math.hypot(x-villageC.x, z-villageC.z); const vt = 1 - smoothstep(villageC.r*0.55, villageC.r*1.1, vdist); h = h*(1-vt) + (0.5)*vt; ``` Then the actual voxel y = Math.round(h). Water level: y = -2 for the pond surface. Ground inside pond at -3.5 → rounded -3 or -4. Fill water voxels at y = -2 where terrain top < -2. Hmm but the pond at y=-3.5 with surrounding terrain at 0 means a 3.5 deep basin — visible walls of dirt. That's fine, looks like a pond. Actually water should be at like -1 so it's less deep. Let's do pond bottom at -3, water surface at -1.5 → water voxels at y=-2 and -1? Let's do water surface y=-1, and columns from terrain top+1 to -1 filled with water. Simpler: for each (x,z) in the pond, if terrainTopY < waterLevel, fill from terrainTopY+1 to waterLevel with water. Since terrain top is -3 or -4 mostly, fills 2-3 layers. Fine — transparent blue. Terrain materials: - Top voxel: grass (varied greens), unless it's sand near the pond or on paths. - Below: dirt (brown), then stone (dark gray) deeper. Also add snow? No, spring scene with cherry blossoms. Now let's build content. ### Pagoda 5 tiers. Base platform: stone slab square 22x22 at y=0..1? Actually ground is at y=0 (top of terrain). Let's say terrain surface top voxel at y=h. For flat regions h=0, so solid ground occupies y <= 0. Structures start at y=1. Wait, define: terrain voxel at level y = Math.round(h). Top voxel at y = hRound. So flat ground top voxel is at y=0. Buildings start at y=1. Pagoda: located at (0, -6). Base platform: from y=1 to y=2, a stone square 20x20 (x from -10..9? center 0). Let's center at (0,-6): x in -10..10, z in -16..4. Pagoda core: square shaft. Tier widths: 13, 11, 9, 7, 5 for 5 tiers. Each tier has a story with walls + a roof with upturned eaves. Height per story: 4 voxels. Roof: 2-3 voxels tall with overhang. Let me write a function to build a pagoda tier: ``` buildPagoda(cx, cz, baseY) ``` - Platform: stone, 20x20x1 at y=baseY (baseY=1), plus 18x18 at y=baseY... Let me get concrete. Ground top at y=0. Pagoda: - Stone platform: y=1 and y=2, square from -10..10 (21 wide) in x and z relative to center. Then steps on the -z side. - Then the pagoda body starts at y=3. Tier i (0-indexed, 5 tiers): - halfWidth w = 5 - i → sizes 11,9,7,5,3 (odd, centered on 0). - story walls from y = yStart to yStart+3 (4 tall), walls = columns of wood red/brown at edges, white plaster between, with a lattice window. - Then roof: at y = yStart+4 .. yStart+6 (3 layers), each layer wider: e.g., layer widths w+4, w+3, w+2, with corners raised. Hmm, roofs: I'll create a pyramid-ish roof: for each roof level k from 0..2, the roof slab covers x in [-(w+3-k), w+3-k] with thickness 1, and the corners get extra upturned voxels. Better: roof layer k (k=0 bottom, largest) at y = yRoof + k, half extent = w + 3 - k. Add dark tile color. Also add the eave edge with a slightly different color (darker) at the outermost ring. And at the corners, raise the corner voxels by 1 to simulate upturn. Then between roof layers, the story continues. Total height: 5 tiers * (4 story + 3 roof) = 35 plus base 2 = ~37. That's tall relative to the world but fine — pagodas are tall. Maybe scale down: story 3 tall, roof 2 layers → 5 tiers * 5 = 25. Good. Let's do story height 3, roof 2 layers. Tier widths (half): starting 5, decreasing by 1 each: 5,4,3,2,1? That's small at top. Let's do 5,4,3,2,2. Hmm. Actually typical pagoda: base wide, tapering. With half-widths 5,4,4,3,3 → widths 11,9,9,7,7. Let's do halfWidths = [5,4,4,3,3] and roof overhang = 2 for lower tiers, 2 for all. Story: from y0 to y0+2 (3 tall). Walls: perimeter ring at half-width w. Interior hollow (but nobody sees inside; keep hollow to save voxels, or fill with dark). Fill the interior with a dark color? Better hollow — but then from outside you see gap? No, walls enclose it. But the roof would float above the hollow. Fine visually. Actually for the top-down view, roof covers it. OK hollow. Wall design: perimeter columns. For each (x,z) on the perimeter ring: place a wall voxel. Use white plaster; at corners and midpoints use red wood posts. Add a small window: skip voxels in some places, replaced by dark. Roof: at y = y0+3 and y0+4. - Layer 0 (y0+3): covers half-extent w+2, but only the ring outside w and it's solid covering whole footprint? Let's make it solid across the full (2*(w+2)+1)^2 area. That's fine. Actually the roof should overhang: at layer 0, cover from -(w+2) to (w+2). At layer 1 (y0+4), cover from -(w+1) to (w+1). Then next story starts at y0+5 with half-width w-1... Hmm, then the top roof layer at each tier is smaller and the next story is even smaller, so the roof looks like a small lip. Not great. Let's do: - Layer 0 (y0+3): half extent w+3, thin ring? No — solid disc is fine, it's a roof. - Layer 1 (y0+4): half extent w+1. - Then story of next tier from y0+5. The overhang from w to w+3 gives 3 voxels of eave — good with the corner upturns. Roof colors: dark slate blue-gray tiles (#4a5568?) with a red-orange ridge? Japanese pagoda roofs are dark gray/blue tiles. Add gold finial at top. Corner upturn: at layer 0, at the four corners, place voxels at y0+4 too... Actually the classic upturned eave: corners of the roof rise. Let's add at each of 4 corners of layer 0 a small stack: at (cx±(w+3), cz±(w+3)) place at y0+4, and neighbors ±1 toward the corner get y0+4 too partially. Simplify: at corner position and the two adjacent along x and z, raise by 1 (add voxel at y0+4 above the layer-0 voxel). Actually since layer 1 at y0+4 covers ±(w+1), the corner region at ±(w+3) is free, so I can put voxels there at y0+4. Let's code the roof: ```js for (let lv=0; lv<2; lv++){ const e = (lv===0)? w+3 : w+1; const y = y0+3+lv; for x in -e..e, z in -e..e: set(cx+x, y, cz+z, ROOF) } ``` This makes a stepped pyramid roof. Then corner upturns: at layer 0 corners (cx±(w+3), cz±(w+3)) add voxels at y0+4 with ROOF_LIGHT. And also add ridge highlight along the edges at layer 0 outer ring with a slightly lighter color. Then at the top, a golden finial (sorin): a spire of gold voxels from top roof up, with rings. OK. Also add a base story: the bottom tier could have a bigger roof and the entrance with doors. Let's also add a torii gate in front of the pagoda, and stone lanterns lining the approach. ### Trees Cherry blossom tree: trunk brown, branches, canopy of pink voxels in a blobby sphere shape with varying pink shades. ```js function cherryTree(x,z,yBase,scale) ``` - Trunk: 1-2 wide, height 4-7, with slight lean/curve. - Branches: a few diagonal lines outward. - Canopy: an ellipsoid of radius r, filled with pink shades determined by noise; skip voxels with random chance to make it fluffy. Pine tree: trunk + conical layers of dark green. Also bamboo: tall thin stalks with small leaf clusters. Also normal green trees for variety. ### Village Houses: box walls (wood/plaster), pitched roof (dark or thatch), windows, doors, chimney. Sizes ~7x6x5. Place along a path, with variations in color/rotation (rotate by 90° increments). Add: - Fences around fields - Crop rows (green/brown voxels) - A well - Market stalls with colorful awnings - People: 3-4 voxel tall figures (head, body, legs, arms) with varied colors; place some walking on paths. - Lanterns: hanging red lanterns on poles. ### Garden - Raked gravel area with concentric pattern (alternate light/dark sand rings) - Stepping stones - Stone lanterns - Pond with lotus flowers, koi (orange voxels), lily pads - Wooden arched bridge over a stream/pond - Torii gate - Cherry petal piles? ### Sky/atmosphere - Gradient sky using a large sphere with vertex colors shader, or just scene.background = color + fog. Let's do a simple gradient shader on a big sphere (BackSide). - Fog for depth. - Directional light with shadows, hemisphere light for fill. - Maybe add floating cherry petals as particles? Nice touch — a Points system with pink petals drifting. Could be a nice touch but adds complexity. Let's add simple drifting petal sprites (small planes or points). Maybe skip; or use a Points with a few hundred petals animated falling. That's cheap and pretty. Let's include it. Actually with instanced voxels, adding a `Points` cloud with a circular texture and pink color drifting down is easy. Let's do it. ### Camera & controls - OrbitControls with damping, auto-rotate optional (maybe slow). Limit polar angle. - Initial camera position looking at the pagoda from a distance. Use import map: ```html <script type="importmap"> {"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js","three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/"}} </script> ``` Then `import * as THREE from 'three'; import {OrbitControls} from 'three/addons/controls/OrbitControls.js';` That's reliable. Now, performance. Voxel count could be large. Let's estimate: terrain 130x130 = 17000 columns, each with up to ~8 solid voxels but we only need the top few. Actually we should only fill 4-5 voxels below the surface for visual purposes (sides of hills). Since it's a closed surface from above, we need depth for the hills' sides. Let's fill from surface down 6 voxels. 17000*7 = 119k voxels. That's OK. Trees: cherry canopy radius 5 → ~500 voxels each * 12 trees = 6k. Fine. Total maybe 200-300k voxels. Instanced rendering handles that fine (a few draw calls with big instance counts). Shadow rendering doubles it. Should be OK on a "power machine". Optimization: cull hidden voxels? Skip — with only 6 layers deep and only top visible mostly. Actually interior voxels are hidden but still rendered; 300k instances is fine. Hmm, but shadow map rendering of 300k instances each frame... it's fine, instancing is fast. Let's also add frustum culling per instanced mesh — it uses the bounding sphere computed from geometry * instance matrices? Actually InstancedMesh computes boundingSphere from instanceMatrix if you call computeBoundingSphere. Let's just leave it. Let me now write the code. ```js // ---------- Voxel storage ---------- const voxels = new Map(); // idx -> colorIndex const palette = []; // hex colors const paletteMap = new Map(); function col(hex){ ... returns index } ``` Then `set(x,y,z,c)`. Order matters: later set overwrites. That's useful for terrain then structures. Build order: 1. Terrain 2. Water (separate map) 3. Pagoda 4. Garden elements 5. Village 6. Trees 7. People 8. Details Let me write terrain. ```js function fbm(x,z){ let v=0, a=0.5, f=1; for(let i=0;i<4;i++){ v += a*valueNoise(x*f, z*f); f*=2; a*=0.5; } return v; // 0..~0.94 } ``` Normalize roughly: divide by (0.5+0.25+0.125+0.0625)=0.9375. Now terrain generation loop over x from -64 to 64, z from -64 to 64. That's 129*129 = 16641 columns. Use radius mask to make it an island-ish shape? Maybe just a big square with hills at edges fading. Let's make the outer boundary fade to hills, so you can't see the edge easily. Or make an island with water around? That'd be pretty: a floating island? No, let's keep a large landmass with rolling hills at the edges, and fog hiding the far edge. Actually, a nice idea: make it an island surrounded by sea. Then the edge is natural. Hmm, but with fog and hills, a square edge is fine if we place hills at the edge. Let's do a circular island: radius R=62, ocean beyond. That's very pretty: island with a pagoda, garden, village, surrounded by sea. But then trees at edges... Let's do it: island shape via falloff. Terrain height: ```js function terrainHeight(x,z){ const d = Math.hypot(x,z); const island = smoothstep(64, 46, d); // 1 in center, 0 at 64 let h = -6 + island*8; // -6 at edge (underwater), rising ... } ``` Hmm, that gives a flat-ish disc. Let's add hills with noise weighted by island mask. ```js function terrainHeight(x,z){ const d = Math.hypot(x*1.0, z*1.0); const mask = smoothstep(66, 44, d); // 1 inside, 0 outside const base = -5 + mask*5; // ocean floor -5, coast 0 const hills = (fbm(x*0.045+3.1, z*0.045-2.7)) * 16 * Math.pow(mask,1.5) * smoothstep(42,18,d)... ``` Getting complicated. Let's simplify: ```js const R = 60; function terrainHeight(x,z){ const d = Math.hypot(x,z); // island mask const m = smoothstep(R+6, R-8, d); // 1 for d < R-8, 0 for d > R+6 // hills: more toward the edge, flat in the middle const rim = smoothstep(20, 44, d) * m; // rises as you go out const n = fbm(x*0.05+11, z*0.05-7); let h = base + rim*14*(0.5+n) + (fbm(x*0.14, z*0.14)-0.5)*1.8*m; // flatten zones... } ``` Where base = m*0 + (1-m)*(-6). Hmm: h = m * (hills) + (1-m)*(-6). Let me write: ```js function terrainHeight(x,z){ const d = Math.hypot(x,z); const m = smoothstep(R+8, R-10, d); // 1 inside, 0 at sea const n1 = fbm(x*0.045+11.3, z*0.045-7.7); // 0..1 const n2 = fbm(x*0.13-3.2, z*0.13+5.1); // 0..1 const rim = smoothstep(16, 42, d); // 0 in center, 1 at edge let h = rim * (5 + n1*16) + (n2-0.5)*1.5; h = h * m + (1-m)*(-7); return h; } ``` At d small: rim=0, h ≈ (n2-0.5)*1.5 ≈ ±0.75, times m=1 → slight undulation. Good. Near the pond we dig. Then flatten for the plaza and village as described, but careful: flattening should blend with `m`. Let's apply: ```js function surfaceH(x,z){ let h = terrainHeight(x,z); // Pond const pd = Math.hypot((x-PX)/PRX, (z-PZ)/PRZ); if (pd < 1.3){ const t = smoothstep(1.3, 0.5, pd); h = h*(1-t) + (-4.5)*t; } // Plaza flatten const pl = Math.hypot(x-0, z+6); const t2 = smoothstep(22, 8, pl); // 1 in center h = h*(1-t2) + 0*t2; // Village flatten const vd = Math.hypot(x-VX, z-VZ); const t3 = smoothstep(34, 14, vd); h = h*(1-t3) + 1.0*t3; return h; } ``` Careful: it should return h with the mask applied so edges drop to sea. Then y = Math.round(h). Solid fill: from y down to y-6, but never below -8. Colors: - y === topY: grass green, or sand if near pond/beach, or dirt if slope steep. - topY-1..topY-2: dirt brown - deeper: stone gray Beach: if topY <= 0 (near water), use sand color. Let's set sea level at y = -3. Ocean floor at -7. So waterside sand at y in [-3, 0]. Water: fill from topY+1 up to -3 in a separate water map for all columns where topY < -3. That includes the pond (dug to -4.5) and ocean. Hmm, the ocean around the island: the island mask drops to -7 at d>R+8. Water at y=-3 fills everything outside. That's a big water area: the whole square minus island. 129x129 minus ~island = maybe 8000 water voxels per layer * 4 layers = 32k. Acceptable but maybe wasteful. Could limit water to d < 75. Fine. Actually maybe use a simple flat translucent plane for the ocean instead of voxels — but voxel water looks more consistent. Let's use voxels but only 2 layers thick (top of water at -3, and -4). Just render the surface layer at y=-3 plus maybe one below. Since the water is transparent, we only need the top layer really. Let's do top layer at y=-3 for all water columns with topY < -3. Plus optionally a second layer. I'll do just the top 1-2 layers. Hmm, but the pond is dug into the land; from the side you'd see through the water edge. It's fine — 2 layers: y=-3 and y=-4. Actually if terrain around pond is at y=0 and pond bottom at -4, the water column is from -3 to -4 (2 voxels). Good. For the ocean, water at -3 and -4 as well. Ocean floor at -7 so there's a gap between -5,-6,-7 and water at -4. We can fill the gap with water too, but it's not visible. Let's just do water at -3 and -4 for all columns; below -4 to the floor we could fill water but skip it. Actually from a low camera angle you might see under... no, water is opaque-ish from the side. Since water is transparent, you'd see through. Let's fill water from -3 down to max(topY+1, -8). To limit voxel count, cap at 3 layers: -3,-4,-5. Ocean floor is -7, so there'd be a 1-voxel gap. Meh. Let's just make the ocean floor at -5.5 → y=-5 or -6, and fill water -3,-4,-5. Simplify: remove the deep ocean; make the sea floor at y=-6 and fill water from topY+1 to -3 for all water columns. For ocean columns, topY = -7 or so... let's compute: with m=0, h = -7. So the ocean floor is at -7. Fill water from -6 up to -3 = 4 layers. 8000 columns * 4 = 32k water voxels. Fine, that's OK. Actually to reduce, let's set the ocean floor at -5: `(1-m)*(-5)`. Then water -4,-3 = 2 layers. And the pond bottom at -4, water -3 only... but I want the pond water at the same level as the sea? Not necessarily; the pond can be at -1. Hmm, if the pond water is at the same level as the sea it's weird since the pond is inland and higher. Let's make the pond water level -1 and the sea level -3. OK let's finalize: - Sea level: y = -2 (water surface voxel at y=-2). - Sea floor: y = -6 (mask 0 → h=-6). Water from -5 to -2 = 4 layers. - Pond: surface at -3, bottom at -6. Water from -5 to -3. Hmm, that's a deep pond. Let's make the pond shallower: bottom -4, water at -3 → 1 layer. Good: pond dig to -4 (so the terrain top voxel = -4), and water voxels at -3. Wait, but I need the terrain top at -4 only inside the pond. Using the smoothstep blend, the rim will rise. OK. Actually let me reconsider: the ground level in the garden is 0. A pond 4 deep is fine, water surface at -3. That's a decent pond. Let me just make it: pond interior terrain top = -3, water voxel at -2. Sea level -2 as well. Then the pond water is at -2 and the ground at 0. Only 2 deep. Fine, whatever. Let's keep pond bottom at -3, water at -2 and -1. Two layers of water = nicer transparency. OK, enough. Let's set: - SEA_LEVEL = -2 (top water voxel y = -2) - Pond dig target = -4 (bottom), water fills -3, -2. Now the terrain fill: for each column with topY, fill voxels from topY down to topY-5 (but not below -7). Use depth-based colors. This makes hills' sides have a dirt/rock texture. Actually the deeper fill only matters where terrain slopes. Let's fill down to min(topY-5, ...). Fine. Voxel count: 16641 columns * 6 = ~100k. Plus water. Plus structures. Should be around 200k. OK. Let's reduce the terrain resolution? 129x129 is 16641 columns. It's fine. Actually, wait: the world is 129 x 129 = a big area for a camera to see. Since the island is circular with R=60, and the pagoda is at the center, the camera will be at distance ~90. That's fine. Hmm, but with 1-voxel resolution, a 120-wide scene is quite large in voxel terms (like 120 voxels across). That's a reasonable voxel diorama scale. Good. Let me now think about the detailed layout more concretely. Coordinates: x to the right, z toward the viewer (positive z = south/front). Camera looks from +z. - Pagoda center: (0, -8), footprint ~16x16 at base. Height ~30. - Plaza around it: gravel garden from radius 10 to 22 around (0,-8). - Torii gate: at (0, 14) facing... approach from the south along z. Path from (0, 30) to (0, 14) stone. - Pond: center (-30, 20), rx=14, rz=10. - Bridge over pond? A wooden arched bridge crossing the pond from (-30, 10) to (-30, 30)? That's across the z axis. Sure. - Cherry trees: scattered in a ring around the plaza at radius ~24-32, plus near the pond. - Village: center (34, 4), houses in a cluster, with a path connecting to the plaza. - Bamboo grove: near (-45, -30). - Rice fields / crop terraces: near (20, 35)? - Stone lanterns lining the path. - Koi pond? no, the pond is enough. Also add a small hill with a shrine? Maybe keep it manageable. Let's also add a second smaller pagoda or a shrine gate? Keep the main pagoda. People: ~12 villagers placed along the path and in the village, in various colors. Also a few near the pond and the pagoda. Each person ~5 voxels tall: legs (2), body (2), head (1). Plus a hat maybe. Let me write the helper `person(x, y, z, shirtColor, pantsColor, hairColor)`. Now let's think about the path network: a path from the pagoda plaza south to the torii, then continuing south to the village. Draw with a stone/gravel color replacing the top terrain voxel. I'll implement path drawing as: after terrain, for each point along a path, set the top voxel to path color and remove grass. I need to know the top Y at each column. I'll store a heightmap array `H[x][z]` for lookup. Let me store heights in a Map or a 2D array with offset. ```js const S = 129; const OFF = 64; const H = new Int16Array(S*S); function hIdx(x,z){ return (x+OFF) + (z+OFF)*S; } ``` OK. Now writing the code. ```js // ============ Setup ============ const scene = new THREE.Scene(); scene.fog = new THREE.Fog(0xbcd8ef, 120, 320); ``` Sky: gradient via ShaderMaterial on a sphere. Camera: PerspectiveCamera(50, aspect, 0.1, 2000). Position (60, 55, 95), lookAt (0, 10, 0). Renderer: antialias, shadowMap PCFSoft, toneMapping ACESFilmic, outputColorSpace SRGB. Lights: - HemisphereLight(0xbfe3ff, 0x6b8f4e, 0.9) - DirectionalLight(0xfff2d8, 1.6) at (60, 90, 40), castShadow, shadow camera ortho ±90, mapSize 4096. - Maybe a subtle fill light. Material for voxels: MeshLambertMaterial or MeshStandardMaterial with roughness 1, metalness 0. Standard gives nicer looks but slower. With 300k instances... Lambert is cheapest. Let's use MeshLambertMaterial for the voxels. Hmm, Lambert doesn't do specular; for a voxel diorama that's fine and looks flat-cute. Use MeshLambertMaterial + vertex colors? No, per-instance color could reduce draw calls to ONE! Idea: use `instanceColor` (InstancedMesh.setColorAt) with a single material — that way ONE draw call for all voxels! But then transparency/emissive per color is lost. That's a massive win: one InstancedMesh with 300k instances and per-instance colors. But then shadow and everything works with a single mesh. Let's do that! Use `new THREE.InstancedMesh(geo, mat, count)` and `setColorAt(i, color)`. Limit: instanceColor needs the material to support vertex colors — with MeshLambertMaterial, setting instanceColor works automatically (three handles it via USE_INSTANCING_COLOR). Yes, three.js supports instanceColor with standard materials. So: one InstancedMesh for opaque voxels with per-instance colors. Plus a separate one for water (transparent). Plus maybe glowing lanterns (could use emissive — but a single material means one emissive color). I could add a third instanced mesh for "glowing" voxels with emissive material. Nice: lanterns, windows at night. Let's keep daylight and just make lanterns bright red with slight emissive. Let's do: - `opaqueMesh`: MeshLambertMaterial({vertexColors:false}) + instanceColor. - `glowMesh`: MeshBasicMaterial (or Lambert with emissive) for lanterns — use MeshBasicMaterial with instanceColor, so they're fully bright. - `waterMesh`: MeshLambertMaterial({transparent:true, opacity:0.65, color}) — with instanceColor too. Great, 3 draw calls. But careful: `setColorAt` requires the color to be in linear space if renderer outputColorSpace is sRGB. Three's `Color.setHex` with default color management converts to linear working space. `setColorAt(i, color)` stores the color components as-is (linear). And the material multiplies by instanceColor. So using `new THREE.Color(hex)` (which converts sRGB→linear via ColorManagement) should give correct results. Yes, since three r152+, `new THREE.Color(0xff0000)` applies sRGB→Linear conversion when ColorManagement.enabled (default true). Good. Now geometry: BoxGeometry(1,1,1) but with a small gap. Let's use size 0.98? Hmm, with voxel art, gaps look like MagicaVoxel renders with a grid. Some like it, some don't. I think a tiny gap (0.96) looks crisp and shows the voxel structure. But it also creates dark crevices. Let's use 0.98 - subtle. Hmm, actually let's think: at 0.98, adjacent voxels have a 0.02 gap; from a distance it's invisible; up close it looks nice. I'll go 0.97. But wait: with gaps and shadows, might cause shadow acne. Use normal bias. Let's set shadow.bias = -0.0005, normalBias = 0.05. Actually gaps could let light leak. Use normalBias 0.1. Alternatively just use full 1.0 cubes. I'll use 0.98. Hmm, one more consideration: with a single InstancedMesh, frustum culling is all-or-nothing (bounding sphere covers everything). Fine. Also shadows: 300k instances rendered into a 4096 shadow map every frame. That's ~300k * 12 triangles = 3.6M triangles. Modern GPU handles it. OK. Let's reduce terrain fill depth to 4 to cut count. Now let's write everything. ```js // ---------- utilities ---------- function smoothstep(e0,e1,x){ const t = Math.min(1, Math.max(0, (x-e0)/(e1-e0))); return t*t*(3-2*t); } ``` Note smoothstep(edge0, edge1, x) with edge0 > edge1 works too since we clamp. ```js function hash2(x, y){ let n = Math.sin(x*127.1 + y*311.7) * 43758.5453123; return n - Math.floor(n); } function vnoise(x, y){ const xi = Math.floor(x), yi = Math.floor(y); const xf = x - xi, yf = y - yi; const u = xf*xf*(3-2*xf), v = yf*yf*(3-2*yf); const a = hash2(xi, yi), b = hash2(xi+1, yi), c = hash2(xi, yi+1), d = hash2(xi+1, yi+1); return a*(1-u)*(1-v) + b*u*(1-v) + c*(1-u)*v + d*u*v; } function fbm(x,y,oct=4){ let s=0, amp=0.5, f=1, norm=0; for (let i=0;i<oct;i++){ s += amp*vnoise(x*f, y*f); norm += amp; f*=2; amp*=0.5; } return s/norm; } ``` Random with seed for deterministic placement: ```js let seed = 1337; function rnd(){ seed = (seed*1664525 + 1013904223) % 4294967296; return seed/4294967296; } ``` Voxel API: ```js const VOX = new Map(); const GLO = new Map(); const WAT = new Map(); function vkey(x,y,z){ return (x+512) + (z+512)*1024 + (y+512)*1048576; } function setVox(map,x,y,z,color){ map.set(vkey(x,y,z), color); } ``` Colors as hex numbers (ints). Then at build time, convert to THREE.Color. To avoid creating a Color per voxel (300k Color objects), we can cache colors in a Map from hex → {r,g,b}. Then set instanceColor array directly. Actually `setColorAt(i, color)` takes a THREE.Color. We can reuse a temp Color object: `tmpColor.setHex(hex)` then setColorAt. That's fine, no allocation. But setHex does color management conversion each time — 300k times, fine. Better: precompute a Float32Array of RGB for each unique palette color. Let's do: ```js const colorCache = new Map(); function getRGB(hex){ let c = colorCache.get(hex); if(!c){ c = new THREE.Color(hex); colorCache.set(hex, c); } return c; } ``` Then `mesh.setColorAt(i, getRGB(hex))` — reuses Color objects. Good. Now building: ```js function buildInstanced(map, material){ const count = map.size; const mesh = new THREE.InstancedMesh(boxGeo, material, count); const m = new THREE.Matrix4(); let i = 0; for (const [k, hex] of map){ // decode const x = (k % 1024) - 512; const z = (Math.floor(k/1024) % 1024) - 512; const y = Math.floor(k/1048576) - 512; m.makeTranslation(x, y, z); // hmm, need to reuse mesh.setMatrixAt(i, m); mesh.setColorAt(i, getRGB(hex)); i++; } ... } ``` `setMatrixAt` copies the matrix into the array. Good. Note: y decode: k = (x+512) + (z+512)*1024 + (y+512)*1048576. Since z+512 < 1024 and y+512 < 1024, decoding works as long as coordinates are within [-512, 511]. Good. Wait, but for negative y, y+512 is positive. Fine. Now, use a helper for iterating. Now the scene content. Let me write it step by step. #### Palette Define colors: ```js const C = { grass1: 0x5fae3a, grass2: 0x6ec04a, grass3: 0x4e9a30, grass4: 0x7ecb58, dirt1: 0x8a5a34, dirt2: 0x74492a, dirt3: 0x9c6b41, rock1: 0x7b7f86, rock2: 0x656a71, rock3: 0x8f949c, sand1: 0xe2d2a8, sand2: 0xd4c294, water: 0x3fa9dc, waterDeep: 0x2a7fb8, stoneLight: 0xb9b2a6, stoneMid: 0x8f887d, stoneDark: 0x6c665d, woodDark: 0x5b3a24, woodMid: 0x8a5a34, woodLight: 0xb07c4a, plaster: 0xf2ece0, plasterShade: 0xdcd3c2, roof1: 0x2f4858, roof2: 0x3b5b70, roof3: 0x243a48, // dark blue tiles vermilion: 0xc0392b, vermilionDark: 0x8c2a1f, gold: 0xe8b74a, goldDark: 0xc4902a, cherry1: 0xffc0d4, cherry2: 0xffa8c7, cherry3: 0xffd6e4, cherry4: 0xf58cb4, pine1: 0x2e6b3a, pine2: 0x24512c, pine3: 0x3b8547, bamboo: 0x8fbf4a, bambooDark: 0x6d9436, leaf1: 0x4caf50, leaf2: 0x3d8b40, ... } ``` Many colors. It's fine. Actually to give variety, use a function `shade(hex, amt)` to vary colors. Or just pick randomly from arrays. Let's define palette arrays: ```js const GRASS = [0x5fae3a, 0x6cbb45, 0x54a233, 0x77c653, 0x62b23e]; const DIRT = [0x8a5a34, 0x7a4e2c, 0x96653c]; const ROCK = [0x7b7f86, 0x6b6f76, 0x868b93]; ``` `pick(arr, x, y, z)` uses hash for determinism. Ok. Let me write the terrain gen. ```js const SIZE = 129, OFF = 64; const Hmap = new Int16Array(SIZE*SIZE); function hi(x,z){ return (x+OFF) + (z+OFF)*SIZE; } const R_ISLAND = 58; function terrainHeight(x, z){ const d = Math.hypot(x, z); const m = smoothstep(R_ISLAND+10, R_ISLAND-12, d); // 1 inside, 0 outside const n1 = fbm(x*0.045+11.3, z*0.045-7.7, 4); const n2 = fbm(x*0.13-3.2, z*0.13+5.1, 3); const rim = smoothstep(14, 44, d); let h = rim*(6 + n1*18) + (n2-0.5)*1.6; h = h*m + (1-m)*(-6); return h; } ``` Hmm at d slightly less than R-12, m=1, rim=1, h = 6+n1*18 + noise → up to 24 tall hills at the edges. That's pretty tall. Good for framing. At d > R+10, m=0 → h=-6. Then apply the pond and flattening: ```js const POND = {x:-30, z:20, rx:15, rz:11}; const PLAZA = {x:0, z:-8}; const VILLAGE = {x:33, z:6}; function surfaceHeight(x,z){ let h = terrainHeight(x,z); // flatten plaza const pd = Math.hypot(x-PLAZA.x, z-PLAZA.z); const pt = smoothstep(24, 9, pd); h = h*(1-pt) + 0*pt; // flatten village const vd = Math.hypot(x-VILLAGE.x, z-VILLAGE.z); const vt = smoothstep(34, 16, vd); h = h*(1-vt) + 1.2*vt; // pond const qd = Math.hypot((x-POND.x)/POND.rx, (z-POND.z)/POND.rz); if (qd < 1.35){ const t = smoothstep(1.35, 0.45, qd); h = h*(1-t) + (-4.2)*t; } return h; } ``` Careful: the pond is at (-30,20) with rx=15 → x from -45 to -15. Distance from center (-30,20) to origin is 36. The island radius is 58 so it's fine. But the plaza flatten uses smoothstep(24, 9, pd) → at pd<9 fully flat, at pd>24 no effect. Pond at (-30,20) has distance from plaza center (0,-8) = sqrt(900+784)=41 → outside. Good. Village at (33,6): distance to plaza = sqrt(1089+196)=35.8, so village flatten doesn't affect the plaza (radius 34). Slight overlap. Fine. Hmm, but the flattening order: village flatten applies after plaza flatten, so near the overlap it'll pull toward 1.2. Whatever, it's a smooth blend. Now heights: ```js for x, z: Hmap[hi(x,z)] = Math.round(surfaceHeight(x,z)); ``` Terrain fill: ```js const y = Hmap[hi(x,z)]; const underwater = y < SEA_LEVEL; const beach = y <= SEA_LEVEL + 1.5; for (let d=0; d<=4; d++){ const yy = y - d; if (yy < -8) break; let col; if (d === 0) { // top if (beach) col = pick(SAND, x, y, z); else if (y > 8) col = pick(ROCK...)? // high hills could be rocky else col = pick(GRASS,...) } else if (d <= 1) col = pick(DIRT...) else col = pick(ROCK...) } ``` Hmm, for high mountains, use rock at the top? Let's do: if y > 12, top is rock/snow? A snowy peak would be cute. Let's do: y > 14 → light gray/white (snow). Actually with max height ~24, having snowy peaks is nice. Let's add snow color for y >= 16. And rock for y >= 10. Hmm, that could look odd on a small island. Let's make green hills with rocky tops only above 15 and snow above 20. Since max is ~24 it's a small snow cap. Nice. Actually, the rim rises from d=14 to 44 with rim*(6+n1*18). At d=44, rim=1, so h could be 6..24. Average ~15. So the outer ring is mostly 10-20. Snow above 20 would be sparse. Let's set snow at y>=18. Hmm, it might look weird — a ring of hills around the island with snow. Let's skip snow and just use grass + rock. Actually let's include it, it adds variety and color. Hmm... Spring scene with cherry blossoms, snow is off-theme but looks cool. I'll use rocky gray for peaks above 16 and no snow. Actually let's keep some light gray rock. Fine. Then grass color variation via noise for patches. Let's write: ```js for (let x=-64; x<=64; x++){ for (let z=-64; z<=64; z++){ const y = Hmap[hi(x,z)]; const d = Math.hypot(x,z); if (d > R_ISLAND + 14) continue; // skip far ocean columns? but we want water there ... } } ``` Hmm, if I skip, there's no water beyond. The island mask goes to 0 at d>68, so h=-6. Water fills. Let's limit the world to about d < 78, so we don't generate a full 129x129 ocean. Actually a square of 129 with a circular island means corners at d=91 are ocean. To save voxels, skip columns with d > 76. Since the fog hides distance, it's fine — but you might see the edge of the water. With fog at 120-320 and the camera near the center, the water edge at d=76 is at distance ~76-150 from the camera. Fog won't fully hide it. Alternative: make the ocean just a flat translucent plane (a big circle geometry) at y=-2.5 instead of voxels, extending to radius 200. That's much better: it merges with the horizon and the fog. And the island's coastline voxels meet the plane. Yes! Let's do that: a big circular plane (or a ring geometry) for the ocean at y = -2.5 (a bit below the top water voxel at -2). Hmm, but then the voxel water at -2 near the shore would be above the plane. Let's just use the plane at y=-2.2, and skip water voxels for the ocean, only using voxels for the pond. Hmm, but the plane must not extend under the island (it'd be hidden anyway since the island is solid). Actually it's fine — the plane goes under the island, hidden. But wait, the ocean floor terrain voxels at -6 under the water plane — visible through the translucent plane. That's actually nice (you see the sea floor). Or we could skip generating terrain outside the island (d > R+10) to save voxels, and just have the plane. Then through the plane you'd see the sky/background. Meh. Let's generate terrain out to d < R_ISLAND + 8 with the sea floor at -6, and put the ocean plane at y = -2.2 covering everything within radius 200. Beyond that the fog. Actually fog color = sky color so it blends. Hmm, but the plane's edge at radius 200 would be beyond the fog far plane? Fog far = 320. The horizon would still be visible. Let's set fog far ~ 220 and make the plane radius 300. Then the plane fades into the fog. Actually simpler: make the plane huge (radius 400) and fog near=140 far=340. The camera is at ~110 from center; the far edge of the plane at 400 → distance > 400, fully fogged. Good. Alright. But the pond: use voxel water. Its surface at y=-2, same as the ocean plane at -2.2? Let's make the pond surface at y=-1 (voxels) and the ocean plane at -2.2. Slight difference, fine. Hmm, actually simpler: make the pond water voxels at y=-1 and y=-2. OK. Let me reconsider the terrain extent: only generate out to d < 70. That saves many columns. 129² = 16641 columns total; within d<70 (out of max 91) → ~ π*70²/ (129²) ≈ 92%. Not much savings. Let's limit to d < 68 and skip the rest (the ocean plane covers it). Saves ~8%. Meh, whatever, just generate all 129². Actually, with ~16641 columns * 5 = 83k voxels. Fine, let's just do it all. Wait, but for the ocean floor, filling 5 layers of rock under water is wasteful. Let's fill only 2 layers for underwater columns. Optimization: `const depth = y < SEA_LEVEL ? 2 : 5;` OK. #### Structures Let me now write each builder. **Pagoda** at (PX=0, PZ=-8), ground y=0. ```js function buildPagoda(cx, cz){ const S = 'stone'; // platform: 3 layers // layer A: y=1, half=11 (23x23) // layer B: y=2, half=10 // layer C: y=3, half=9 -- steps ``` Hmm, let's do a 2-step platform: - y=1: x,z in [-11,11] → stone light, with a border ring of darker stone. - y=2: x,z in [-10,10] Then the pagoda body starts at y=3. Add stairs on the +z side (facing the viewer/south): steps from z=11 to z=14, going down from y=2 to y=0. Width 7 (x -3..3). Body tiers: ```js const tiers = 5; let y0 = 3; const halfs = [6,5,4,3,2]; // half widths of stories ``` Hmm, widths 13,11,9,7,5. With the platform half=10 (21 wide), the base tier half=6 (13 wide) leaves a 4-voxel walkway. Good. Wait, but the roof overhang for the base tier is half+3=9, which is within the platform (10). Good. Story height 3, roof 2 → each tier 5, 5 tiers = 25. Top at y=3+25=28. Then the finial ~6 more → 34. Total height 34. The camera should be far enough. Let's reduce: story 3, roof 2, 5 tiers → 25. Plus the finial 5 → 30. OK. Tier i: ```js for (let i=0;i<5;i++){ const w = halfs[i]; // 6,5,4,3,2 // story: y0 .. y0+2 for (let x=-w;x<=w;x++) for (let z=-w;z<=w;z++){ const edge = (Math.abs(x)===w || Math.abs(z)===w); if (!edge) continue; // hollow // wall color: plaster with wooden posts at corners and mid let col = C.plaster; if (Math.abs(x)===w && Math.abs(z)===w) col = C.woodDark; // corner posts else if (Math.abs(x)===w && (z % 2 === 0)) col = C.woodDark; // hmm ... } } ``` Let me simplify the wall: a plaster wall with dark wood frame. Posts at corners and every 2 voxels? Actually simpler: - Corner posts (both |x|==w and |z|==w): wood dark, full height. - Other perimeter voxels: plaster, except a band at y0 (the bottom) which is wood, and a band at y0+2 (top) which is wood. - Add a window: on the +z and -z faces at the middle, a 2x1 opening filled with dark (or leave empty). Let's put a dark voxel (interior) so it looks like a window. Then the roof: ```js // roof const ry = y0+3; for (let lv=0; lv<2; lv++){ const e = w + 3 - lv*2; // 9, 7 for w=6 const yy = ry + lv; for (let x=-e;x<=e;x++) for (let z=-e;z<=e;z++){ // ring or full? // full for the top layer, ring for the bottom? } } ``` If layer 0 is full (from -e to e) it's a big flat slab covering the story. That's fine — the story below is enclosed. Layer 0: y=ry, extent w+3 (full square). Layer 1: y=ry+1, extent w+1 (full square). Then the next story starts at ry+2 with half w-1. Since layer 1 extends to w+1 > w-1, there's a 2-voxel ledge. Good. Roof edge coloring: the outermost ring (|x|==e or |z|==e) gets a darker tile color; the inner gets the mid color. And the very edge gets a highlight. Corner upturn: at layer 1, add voxels at the 4 corners (±(w+3), ±(w+3)) and the adjacent ones along the edges to simulate the upturned eave. Let's add at layer 1: positions where (|x| == w+3 and |z| <= w+3-2) or similar... Hmm. Simpler upturn: at layer 1, add corner pieces: for each of the 4 corners, fill a small 3x3 L-shape at (±(w+3), ±(w+3)) and one step inward. Let's do: ```js for (const sx of [-1,1]) for (const sz of [-1,1]){ for (let a=0; a<=2; a++){ // along x edge set(cx + sx*(w+3-a), ry+1, cz + sz*(w+3), roofEdge) set(cx + sx*(w+3), ry+1, cz + sz*(w+3-a), roofEdge) } // plus the corner tip raised one more set(cx + sx*(w+3), ry+2, cz + sz*(w+3), roofEdge...) } ``` Hmm, but layer 1 already covers ±(w+1). So at ±(w+3) there's nothing at ry+1. Adding the corner pieces there creates the upturned eaves. Also add a ridge line: at layer 1, the outer ring of layer 1 (±(w+1)) is a ridge. Color it darker. Let's also add a "gold" trim on the roof edge. Then y0 += 5. After the 5 tiers, y0 = 3 + 25 = 28. Then the finial: ```js // sorin (spire) let fy = y0; // 28 // base disc for ring... ``` Finial: a gold pole from fy to fy+5 at the center, with discs (rings) of radius 1 and 2 at alternating heights. Let's write: ```js set(cx, fy, cz, gold) etc. ``` Actually the top roof's layer 1 is at y = ... let's compute: for the last tier i=4, w=2, ry = y0+3 where y0 is the story start. Let me restructure with a running `y`: ```js let y = 3; for (let i=0;i<5;i++){ const w = 6-i; // 6,5,4,3,2 buildStory(cx,cz,y,w,i); buildRoof(cx,cz,y+3,w); y += 5; } // y = 3+25 = 28 ``` The top roof layer 0 is at y-2 = 26 (extent w+3=5), layer 1 at 27 (extent 3). So the finial starts at y=28. Finial: ```js for (let k=0;k<7;k++){ const yy = 28+k; const r = (k%2===0)?1:0; // rings ... } ``` Let's do: a central gold column of height 7, with 4 rings (radius 1 in x/z, i.e., a plus shape) at k=1,3,5. Then a small sphere at the top. Simple: - k=0..6: set(cx, 28+k, cz, gold) - rings at k=1,3,5: set the 4 neighbors (cx±1, 28+k, cz) and (cx, 28+k, cz±1) to gold. - top: at k=7, set(cx,35,cz, goldBright) and 4 neighbors? A small orb. Good enough. Also add vermilion columns and details: hang lanterns from the roof corners. **Stone lanterns** (tōrō): 5 voxels tall. ```js function stoneLantern(x, y, z){ set(x, y, z, stoneDark) // base // pillar y+1, y+2 // platform y+3 // fire box y+4 (with glowing) // roof y+5 } ``` Let's do: - y+0: 3x3 base? Actually 1x1 is too thin. Let's do a 2x2 base... but for fine detail use 1x1 with a wider base: - y+0: base 3x3 at (x±1, z±1) stone dark - y+1: 1x1 pillar stone mid - y+2: 1x1 pillar - y+3: 3x3 platform stone mid - y+4: firebox: 4 voxels around the center (cross shape) glow color, center empty - y+5: roof: 3x3 minus corners, stone dark; plus a tip at y+6. Total 7 tall. Good. **Cherry tree**: ```js function cherryTree(x, z, y, size){ // trunk const h = 4 + size; for (let i=0;i<h;i++){ set(x, y+i, z, trunk) if (i>2 && i%3===0) set(x+1, y+i, z, trunk) // slight } // branches: 4-6 diagonal lines going out and up // canopy: ellipsoid } ``` Simplest good-looking approach: build a canopy ellipsoid centered at (x, y+h+2, z) with radii (r+2, r, r+2), and fill with a noise-based density so it looks fluffy. ```js function canopy(cx, cy, cz, rx, ry, rz, colors){ for (let i=-rx;i<=rx;i++) for (let j=-ry;j<=ry;j++) for (let k=-rz;k<=rz;k++){ const d = (i*i)/(rx*rx) + (j*j)/(ry*ry) + (k*k)/(rz*rz); if (d > 1) continue; // edge fluffiness if (d > 0.6 && hash2(cx+i*3.7, cz+k*2.1 + j*5.3) > 0.55) continue; setVox(cx+i, cy+j, cz+k, pickFrom(colors, cx+i,cy+j,cz+k)); } } ``` That works well. For the cherry tree, canopy radius rx=rz=5, ry=3.5. Center at y+h+1. Trunk: brown, maybe 2x2 for large trees. Also add a few darker pink voxels for depth. **Pine tree**: a conical stack of green rings, with a brown trunk. **Villager**: ```js function person(x, y, z, shirt, pants, skin, hair){ // legs set(x, y, z, pants); ... } ``` Let's do a 3-tall figure: - y+0: legs: 2 voxels? For a 1x1 footprint: y+0 = pants, y+1 = shirt, y+2 = head (skin), y+3 = hair (hat). Actually 4 voxels tall: legs(1), body(1), head(1), hat(1). Hmm, a person 4 voxels tall next to a 30-voxel pagoda is right (pagoda ~ 40m real). Fine. Let's do: - y+0: pants (legs) - y+1: shirt - y+2: skin (head) - y+3: hair/hat And arms: set(x±1, y+1, z, skin/shirt)? Let's add arms as shirt color at y+1 offset by 1 in x. That makes the person 3 wide. Maybe too wide. Let's keep arms at the same voxel — skip. Or add one arm only for some. I'll do simple 1x1x4 people with color variety, and some with hats (brown cone = a voxel of a different color at y+3). Let's place ~16 people. Actually to make them more visible, maybe 2 voxels wide at the body? Nah. **House**: ```js function buildHouse(cx, cy, cz, w, d, h, opts) ``` where w,d are half-widths. Walls of plaster/wood, a pitched roof of dark tiles or thatch. Pitched roof: for a house of width W (x from -w to w) and depth D, the roof is a gable running along z: ```js for (let k=0; k<=w+1; k++){ // at height k, the roof covers x in [-(w+1-k), w+1-k] for (let z=-d-1; z<=d+1; z++){ set(cx + ..., cy + h + k, cz + z, roofColor) } } ``` That gives a triangular gable. Add overhang at z=±(d+1). Alright. **Torii gate**: Two vermilion pillars 6 tall, a top beam 11 wide with a slight upward curve at the ends, a second beam below. **Bridge**: arched wooden bridge over the pond. Let's do a simple arch: for each x from -6 to 6, y = round(2 - 0.06*x²)... Actually an arch spans the pond. Let's define the bridge along the z axis crossing the pond at x=-30, from z=8 to z=32. Length 24 → too long. Let's shrink the pond: rx=13, rz=9, center (-30, 20) → z from 11 to 29. The bridge crosses from z=9 to z=31, length 22. That's long. Let's make the bridge cross the narrower dimension: from x=-45 to x=-15 at z=20, width 30. Hmm, also long. Let's just place a small bridge over the pond's narrow part. Or make the pond smaller: rx=11, rz=8. Bridge along x at z=20 from x=-43 to x=-17 (26 long). Still long. Alternative: put the bridge over a stream, not the pond. Or just make a small wooden bridge crossing the pond edge. Let's skip the full crossing and do a bridge over a narrow neck. Simplest: make a small arched bridge of length 11 crossing the pond near its edge where it's narrow. Position it at x=-30, z from 9 to 21? The pond at z=20 center, rz=8 → z from 12 to 28. So the bridge from z=10 to z=30 is 20 long. OK, let's just make the pond elongated in x (rx=14, rz=8) and put the bridge across z at x=-30, from z=8 to z=32: 24 long. Hmm. Let's just accept a longer bridge, or move the pond so that it's near the island edge... You know what, simpler: make the pond smaller (rx=9, rz=7) at (-28, 18). Bridge crosses along z from z=8 to z=28: 20 long. Still long but a 20-voxel bridge is fine actually! An arched wooden bridge with railings, 20 voxels long. That's OK. Hmm, alternatively skip the bridge and add stepping stones across the pond. Cute and simpler. Let's do a few stepping stones + lily pads. And maybe a small wooden dock/pier with a boat. I'll do: stepping stones across the pond (7 stones in a line), plus a wooden pier with a small boat on the pond's edge. Actually let's include a small bridge on the narrow side. Eh, stepping stones it is. Plus a boat. OK. **Gravel garden with raked pattern**: near the pagoda, concentric rings of alternating light/dark sand. Since the terrain is voxels, I'll re-color the top terrain voxel in the plaza area with a pattern: ```js for x,z in plaza radius: const r = dist from center const ring = Math.floor(r) % 2; color = ring ? sandLight : sandDark ``` But concentric circles around the pagoda base look nice. Restrict to r between 12 and 22 (outside the pagoda platform which is half=11). Also add moss patches and stepping stones (dark gray) leading from the pagoda to the path. **Paths**: Recursively lay a path along a polyline with a stone/gravel color, setting the top terrain voxel. ```js function drawPath(points, width, color){ for each segment, step along, and for the width, set the top voxel color at (x,z). } ``` Using the Hmap to find the top y. Let me write: ```js function paintGround(x, z, color){ const y = Hmap[hi(x,z)]; // only if not underwater if (y < 0) return; setVox(VOX, x, y, z, color); } ``` Because the voxel map is keyed by (x,y,z), overwriting the same position replaces the color. Good. But careful: the terrain fill loop already placed a voxel at (x, y, z). Painting replaces its color. **Lanterns on poles**: red paper lanterns hanging from wooden posts along the path. **Crop fields**: rows of green with dirt between, in the village area. **Fences**: wooden posts + rails. **Well**: a stone circle with a wooden roof. **Market stalls**: colorful awnings. OK, lots of content. Let's start writing. I'll write the code carefully. ```js // ===== Voxel maps ===== const VOX = new Map(); const GLOW = new Map(); const WAT = new Map(); ``` Actually, water in a separate map with its own material — but I want per-instance color for water too (deep/shallow). Fine, instanceColor works. Now writing. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Voxel Pagoda Garden</title> <style> html,body { margin:0; padding:0; overflow:hidden; background:#0b1020; font-family: ...} canvas { display:block; } #info { position:fixed; ... } </style> </head> <body> <div id="info">...</div> <script type="importmap">...</script> <script type="module"> ... </script> </body> </html>

Here's a complete, single-file HTML document that renders an elaborate voxel art scene. It features a five-tiered pagoda, blossoming cherry trees, a lively village, and a reflective pond—all built from colorful voxels in an interactive Three.js environment.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Voxel Pagoda Garden</title>
<style>
  html, body { margin:0; padding:0; height:100%; overflow:hidden; background:#0b1220; }
  canvas { display:block; }
  #hud {
    position:fixed; left:18px; bottom:16px; color:#eaf4ff; z-index:10;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size:12px; letter-spacing:.06em; line-height:1.6;
    text-shadow: 0 2px 8px rgba(0,0,0,.8);
    pointer-events:none; opacity:.85;
  }
  #hud b { color:#ffd6e6; font-weight:600; letter-spacing:.14em; }
  #title {
    position:fixed; left:18px; top:14px; color:#fff; z-index:10;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size:13px; letter-spacing:.3em; text-transform:uppercase;
    text-shadow: 0 2px 12px rgba(0,0,0,.9); opacity:.75;
    pointer-events:none;
  }
  #loader {
    position:fixed; inset:0; display:grid; place-items:center; z-index:50;
    background:#0b1220; color:#cfe6ff; transition:opacity .6s ease;
    font-family: ui-monospace, Menlo, monospace; letter-spacing:.2em; font-size:13px;
  }
  #loader.hide { opacity:0; pointer-events:none; }
</style>
</head>
<body>
<div id="title">Voxel Pagoda Garden</div>
<div id="hud">
  <b>DRAG</b> orbit &nbsp;·&nbsp; <b>WHEEL</b> zoom &nbsp;·&nbsp; <b>RIGHT-DRAG</b> pan<br>
  voxels: <span id="vcount">…</span>
</div>
<div id="loader">BUILDING VOXEL WORLD…</div>

<script type="importmap">
{
  "imports": {
    "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
    "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
  }
}
</script>

<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

/* ==========================================================================
   0.  MATH HELPERS
   ========================================================================== */
const clamp = (v,a,b)=> v<a?a:(v>b?b:v);
function smoothstep(e0,e1,x){
  const t = clamp((x-e0)/(e1-e0), 0, 1);
  return t*t*(3-2*t);
}
function hashSin(x,y){
  const n = Math.sin(x*127.1 + y*311.7) * 43758.5453123;
  return n - Math.floor(n);
}
function vnoise(x,y){
  const xi = Math.floor(x), yi = Math.floor(y);
  const xf = x-xi, yf = y-yi;
  const u = xf*xf*(3-2*xf), v = yf*yf*(3-2*yf);
  const a = hashSin(xi,yi),   b = hashSin(xi+1,yi);
  const c = hashSin(xi,yi+1), d = hashSin(xi+1,yi+1);
  return a*(1-u)*(1-v) + b*u*(1-v) + c*(1-u)*v + d*u*v;
}
function fbm(x,y,oct){
  oct = oct || 4;
  let s=0, amp=0.5, f=1, norm=0;
  for (let i=0;i<oct;i++){ s += amp*vnoise(x*f, y*f); norm += amp; f*=2; amp*=0.5; }
  return s/norm;
}
// fast deterministic integer hash -> [0,1)
function ihash(x,y,z){
  let h = Math.imul(x|0, 374761393) ^ Math.imul(y|0, 668265263) ^ Math.imul(z|0, 2147483647);
  h = Math.imul(h ^ (h >>> 13), 1274126177);
  h ^= h >>> 16;
  return (h >>> 0) / 4294967296;
}
const rngState = { s: 987654321 };
function rnd(){
  rngState.s = (Math.imul(rngState.s, 1664525) + 1013904223) >>> 0;
  return rngState.s / 4294967296;
}
function rr(a,b){ return a + rnd()*(b-a); }
function ri(a,b){ return Math.floor(rr(a,b+1)); }
function pickFrom(arr, x, y, z){
  return arr[Math.floor(ihash(x,y,z)*arr.length) % arr.length];
}

/* ==========================================================================
   1.  PALETTE
   ========================================================================== */
const C = {
  grass:   [0x5aa832, 0x66b73c, 0x4f9a2c, 0x71c247, 0x60ad36, 0x6bbe40],
  grassHi: [0x86c94f, 0x94d45c, 0x7cc047],
  dirt:    [0x7d5233, 0x8b5e3a, 0x6d4629, 0x875839],
  rock:    [0x767b82, 0x676c73, 0x828790, 0x6f747c],
  rockTop: [0x8d9299, 0x7e838a, 0x999ea6],
  sand:    [0xdfd0a5, 0xd2c294, 0xe8dcb6, 0xd8c89c],
  gravelL: 0xe6dcbf,
  gravelD: 0xc9bb98,
  snow:    [0xeef3f7, 0xdde6ee],

  woodDark:  0x53341f,
  woodMid:   0x7a4b2a,
  woodLight: 0xa97442,
  woodPale:  0xc79a63,
  woodRed:   0x8d4a2c,

  plaster:  0xf0e9da,
  plaster2: 0xdfd6c3,
  window:   0x2d2b33,

  roofTile: [0x35505f, 0x2c4453, 0x3e5c6d, 0x2a4150],
  roofEdge: 0x1f333f,
  roofRed:  [0xa8433a, 0x93382f, 0xb84d43],
  thatch:   [0xb99a5f, 0xa98b52, 0xc7a86b, 0xb0904e],

  vermilion:  0xcf3a2a,
  vermilion2: 0xa82d20,
  gold:       0xe9b93f,
  gold2:      0xc99a26,

  cherry:  [0xffc3d9, 0xffb0cc, 0xffd7e6, 0xf79dbf, 0xffe3ee, 0xffbdd6],
  cherryD: [0xe884ab, 0xd9739c, 0xef93b6],
  pine:    [0x2c5f36, 0x24512c, 0x35703f, 0x2a5a32],
  leaf:    [0x4a9b3a, 0x3f8a33, 0x57ab45, 0x46a03a],
  leafD:   [0x357a2c, 0x2c6b26],
  bamboo:  [0x87b544, 0x74a238, 0x93c24c],
  bambooL: [0x6d9436],

  stoneLight: 0xbdb6a8,
  stoneMid:   0x968e80,
  stoneDark:  0x6e675c,

  water:     0x3fa0d2,
  waterDeep: 0x2b7ba8,
  waterShal: 0x63bde6,

  paper:   0xfff0d0,
  lantern: 0xff6b4a,
  lantern2:0xffd27a,

  cloth: [0x2f7fbf, 0xd94f4f, 0xf0c04a, 0x62b36a, 0x9a6bc4, 0xe98a3a, 0xffffff],
  skin:  [0xf2c8a0, 0xe0b088, 0xd6a074, 0xf7d7b5],
  hair:  [0x2a1f18, 0x3d2a1a, 0x1a1410, 0x50351f],

  crop:    [0x7fbf3f, 0x6fae35, 0x8fcf4a],
  flower:  [0xff6f9c, 0xffd93d, 0xffffff, 0xc47fff, 0xff9a3d, 0x7fd8ff],

  koi:     [0xff8a3d, 0xff5a2d, 0xffe0c0],
  lily:    0x3f8f4a,
  lotus:   [0xff9ec4, 0xffd0e2]
};

/* ==========================================================================
   2.  VOXEL STORAGE
   ========================================================================== */
const VOX  = new Map();   // solid voxels
const WAT  = new Map();   // water voxels
const GLOW = new Map();   // emissive voxels

function voxKey(x,y,z){ return (x+512) + (z+512)*1024 + (y+512)*1048576; }
function setVox(map,x,y,z,c){ map.set(voxKey(x,y,z), c); }

const _colCache = new Map();
function colorOf(hex){
  let c = _colCache.get(hex);
  if (!c){ c = new THREE.Color(hex); _colCache.set(hex,c); }
  return c;
}

/* ==========================================================================
   3.  WORLD CONSTANTS + HEIGHTMAP
   ========================================================================== */
const SEA_LEVEL   = -2;
const ISLAND_R    = 58;
const PLAZA       = { x: 0,  z: -8 };
const VILLAGE     = { x: 33, z: 8  };
const POND        = { x: -30, z: 20, rx: 12, rz: 9 };

const SIZE = 129, OFF = 64;
const Hmap    = new Int16Array(SIZE*SIZE);
const TopType = new Uint8Array(SIZE*SIZE); // 0 = natural, 1 = painted
function hi(x,z){ return (x+OFF) + (z+OFF)*SIZE; }
function inside(x,z){ return x>=-64 && x<=64 && z>=-64 && z<=64; }

function terrainHeight(x,z){
  const d  = Math.hypot(x,z);
  const m  = smoothstep(ISLAND_R+10, ISLAND_R-14, d);
  const n1 = fbm(x*0.045+11.3,  z*0.045-7.7, 4);
  const n2 = fbm(x*0.130-3.2,   z*0.130+5.1, 3);
  const rim = smoothstep(12, 46, d);
  let h = rim*(5 + n1*17) + (n2-0.5)*1.7;
  h = h*m + (1-m)*(-6);
  return h;
}

function surfaceHeight(x,z){
  let h = terrainHeight(x,z);
  // flatten the pagoda plaza
  const pd = Math.hypot(x-PLAZA.x, z-PLAZA.z);
  const pt = smoothstep(26, 10, pd);
  h += (0 - h) * pt * 0.96;
  // flatten the village
  const vd = Math.hypot(x-VILLAGE.x, z-VILLAGE.z);
  const vt = smoothstep(30, 14, vd);
  h += (1.1 - h) * vt * 0.92;
  // dig the pond
  const qd = Math.hypot((x-POND.x)/POND.rx, (z-POND.z)/POND.rz);
  if (qd < 1.40){
    const t = smoothstep(1.40, 0.50, qd);
    h += (-4.6 - h) * t;
  }
  return h;
}

/* ---------- generate heightmap ---------- */
for (let x=-64;x<=64;x++){
  for (let z=-64;z<=64;z++){
    Hmap[hi(x,z)] = Math.round(surfaceHeight(x,z));
  }
}

/* ---------- fill terrain volume ---------- */
for (let x=-64;x<=64;x++){
  for (let z=-64;z<=64;z++){
    if (Math.hypot(x,z) > 74) continue;
    const h = Hmap[hi(x,z)];
    const deep = h < SEA_LEVEL;
    const depth = deep ? 2 : 5;
    for (let k=0;k<=depth;k++){
      const y = h - k;
      if (y < -9) break;
      let c;
      if (k === 0){
        if (h <= 0)             c = pickFrom(C.sand, x, y, z);
        else if (h >= 16)       c = pickFrom(C.rock, x, y, z);
        else if (h >= 12)       c = pickFrom(C.rockTop, x, y, z);
        else                    c = pickFrom(C.grass, x, y, z);
      } else if (k <= 2){
        c = pickFrom(C.dirt, x, y, z);
      } else {
        c = pickFrom(C.rock, x, y, z);
      }
      setVox(VOX, x, y, z, c);
    }
  }
}

/* ---------- pond water volume ---------- */
for (let x = POND.x-POND.rx-3; x <= POND.x+POND.rx+3; x++){
  for (let z = POND.z-POND.rz-3; z <= POND.z+POND.rz+3; z++){
    if (!inside(x,z)) continue;
    const qd = Math.hypot((x-POND.x)/POND.rx, (z-POND.z)/POND.rz);
    if (qd > 1.02) continue;
    const g = Hmap[hi(x,z)];
    for (let y = g+1; y <= SEA_LEVEL; y++){
      setVox(WAT, x, y, z, y <= SEA_LEVEL-1 ? C.waterDeep : C.water);
    }
  }
}

/* ---------- helper: paint the top terrain voxel ---------- */
function paintGround(x,z,c){
  if (!inside(x,z)) return;
  const y = Hmap[hi(x,z)];
  if (y < 0) return;
  setVox(VOX, x, y, z, c);
  TopType[hi(x,z)] = 1;
}

/* ==========================================================================
   4.  GARDEN GROUND : PLAZA, RAKED GRAVEL, PATHS
   ========================================================================== */
// --- raked gravel plaza ---
for (let x=-30;x<=30;x++){
  for (let z=-38;z<=22;z++){
    if (!inside(x,z)) continue;
    const r = Math.hypot(x-PLAZA.x, z-PLAZA.z);
    if (r > 11.6 && r < 25){
      const ring = Math.floor(r*0.95) % 2;
      let c = ring ? C.gravelL : C.gravelD;
      if (ihash(x,1,z) > 0.94) c = C.stoneMid;      // pebbles
      if (ihash(x,2,z) > 0.985) c = pickFrom(C.grass, x, 0, z); // moss patch
      paintGround(x,z,c);
    }
  }
}

// --- moss ring right around the pagoda platform ---
for (let x=-18;x<=18;x++){
  for (let z=-26;z<=10;z++){
    const r = Math.hypot(x-PLAZA.x, z-PLAZA.z);
    if (r >= 11.6 && r < 14.5 && ihash(x,9,z) > 0.3){
      paintGround(x,z, pickFrom(C.grassHi, x, 5, z));
    }
  }
}

// --- paths ---
function drawPath(pts, radius, colors){
  for (let i=0;i<pts.length-1;i++){
    const a = pts[i], b = pts[i+1];
    const len = Math.hypot(b[0]-a[0], b[1]-a[1]);
    const steps = Math.max(1, Math.ceil(len*2));
    for (let s=0;s<=steps;s++){
      const t = s/steps;
      const cx = a[0] + (b[0]-a[0])*t;
      const cz = a[1] + (b[1]-a[1])*t;
      for (let dx=-radius-1; dx<=radius+1; dx++){
        for (let dz=-radius-1; dz<=radius+1; dz++){
          if (dx*dx+dz*dz > radius*radius) continue;
          const x = Math.round(cx+dx), z = Math.round(cz+dz);
          if (!inside(x,z)) continue;
          if (Hmap[hi(x,z)] < 0) continue;
          paintGround(x,z, pickFrom(colors, x, 4, z));
        }
      }
    }
  }
}

const PATH_COLORS = [0xbfb49a, 0xb2a68c, 0xc9bea4, 0xa89d84, 0xd0c5ab];
const STONE_PATH  = [0x9f9788, 0x8d8577, 0xb0a898, 0x968e80];

// main approach from the south
drawPath([[0,14],[0,30],[6,38],[16,42],[26,40],[32,32],[34,20],[34,8]], 2.0, PATH_COLORS);
// branch west to the pond
drawPath([[0,20],[-10,24],[-18,26],[-22,24]], 1.4, PATH_COLORS);
// north loop around the pagoda
drawPath([[-14,-26],[-2,-32],[12,-30],[20,-20],[22,-10]], 1.4, PATH_COLORS);
// east branch to the village
drawPath([[20,-6],[28,-2],[30,4]], 1.4, PATH_COLORS);
// small trail to the bamboo grove
drawPath([[-20,-14],[-30,-24],[-40,-32]], 1.2, PATH_COLORS);

// --- stone stepping stones from the plaza to the pagoda stairs ---
for (let i=0;i<6;i++){
  const z = 12 + i*2;
  const x = ((i%2)?1:-1);
  paintGround(x, z, pickFrom(STONE_PATH, x, 6, z));
  paintGround(x+1, z, pickFrom(STONE_PATH, x+1, 6, z));
}

/* ==========================================================================
   5.  THE PAGODA
   ========================================================================== */
function buildStory(cx, cz, y0, w, tier){
  for (let x=-w;x<=w;x++){
    for (let z=-w;z<=w;z++){
      const ax = Math.abs(x), az = Math.abs(z);
      const onX = ax === w, onZ = az === w;
      if (!onX && !onZ) continue;
      for (let hh=0; hh<3; hh++){
        const yy = y0 + hh;
        const corner = onX && onZ;
        const band   = (hh === 0 || hh === 2);
        let c;
        if (corner || band){
          c = C.woodDark;
        } else {
          const winX = onX && !onZ && az <= 1;
          const winZ = onZ && !onX && ax <= 1;
          c = (winX || winZ) ? C.window : C.plaster;
        }
        setVox(VOX, cx+x, yy, cz+z, c);
      }
    }
  }
  // ground floor doorway (front = +z)
  if (tier === 0){
    for (let x=-1;x<=1;x++) for (let hh=0;hh<2;hh++)
      setVox(VOX, cx+x, y0+hh, cz+w, C.window);
  }
  // corner posts slightly darker, plus a vermilion accent on the middle of each face
  for (const s of [-1,1]){
    setVox(VOX, cx+s*w, y0, cz+w, C.woodMid);
    setVox(VOX, cx+s*w, y0, cz-w, C.woodMid);
    setVox(VOX, cx+w, y0, cz+s*w, C.woodMid);
    setVox(VOX, cx-w, y0, cz+s*w, C.woodMid);
  }
}

function buildRoof(cx, cz, ry, w, tier){
  const e0 = w + 3;
  // bottom slab
  for (let x=-e0;x<=e0;x++){
    for (let z=-e0;z<=e0;z++){
      const edge = Math.max(Math.abs(x), Math.abs(z)) === e0;
      setVox(VOX, cx+x, ry, cz+z, edge ? C.roofEdge : pickFrom(C.roofTile, x, ry, z));
    }
  }
  // upper slab
  const e1 = w + 1;
  for (let x=-e1;x<=e1;x++){
    for (let z=-e1;z<=e1;z++){
      const edge = Math.max(Math.abs(x), Math.abs(z)) === e1;
      setVox(VOX, cx+x, ry+1, cz+z, edge ? C.roofEdge : pickFrom(C.roofTile, x, ry+3, z));
    }
  }
  // upturned corner eaves
  for (const sx of [-1,1]){
    for (const sz of [-1,1]){
      for (let a=0;a<=3;a++){
        setVox(VOX, cx+sx*(e0-a), ry+1, cz+sz*e0, C.roofEdge);
        setVox(VOX, cx+sx*e0, ry+1, cz+sz*(e0-a), C.roofEdge);
      }
      setVox(VOX, cx+sx*e0, ry+2, cz+sz*e0, C.gold);
      setVox(VOX, cx+sx*(e0-1), ry+2, cz+sz*e0, C.gold2);
      setVox(VOX, cx+sx*e0, ry+2, cz+sz*(e0-1), C.gold2);
    }
  }
  // hanging lanterns on the front corners of the lower tiers
  if (tier < 2){
    for (const sx of [-1,1]){
      const lx = cx + sx*e1;
      const lz = cz + e1;
      setVox(GLOW, lx, ry, lz, C.lantern);
      setVox(GLOW, lx, ry-1, lz, C.lantern2);
    }
  }
}

function buildPagoda(cx, cz){
  const gy = 1;
  // --- stone platform ---
  for (let x=-11;x<=11;x++){
    for (let z=-11;z<=11;z++){
      const edge = Math.max(Math.abs(x), Math.abs(z));
      let c = (edge >= 11) ? C.stoneDark : C.stoneMid;
      if (edge < 11 && ihash(x,3,z) > 0.9) c = C.stoneLight;
      setVox(VOX, cx+x, gy, cz+z, c);
    }
  }
  for (let x=-9;x<=9;x++){
    for (let z=-9;z<=9;z++){
      const edge = Math.max(Math.abs(x), Math.abs(z));
      let c = (edge >= 9) ? C.stoneMid : C.stoneLight;
      if (ihash(x,5,z) > 0.92) c = C.stoneMid;
      setVox(VOX, cx+x, gy+1, cz+z, c);
    }
  }
  // --- front stairs ---
  for (let s=0;s<2;s++){
    const y = 2 - s;
    const zz = 10 + s;
    for (let x=-4;x<=4;x++){
      setVox(VOX, cx+x, y, cz+zz, C.stoneLight);
      for (let yy=y-1; yy>=-0; yy--) setVox(VOX, cx+x, yy, cz+zz, C.stoneMid);
    }
    // side rails
    setVox(VOX, cx-5, y, cz+zz, C.stoneDark);
    setVox(VOX, cx+5, y, cz+zz, C.stoneDark);
  }

  // --- five tiers ---
  const halfs = [6,5,4,3,2];
  let y = 3;
  for (let t=0;t<5;t++){
    buildStory(cx, cz, y, halfs[t], t);
    buildRoof(cx, cz, y+3, halfs[t], t);
    y += 5;
  }

  // --- golden finial (sorin) ---
  const fy = y; // 28
  for (let k=0;k<8;k++) setVox(VOX, cx, fy+k, cz, C.gold);
  for (const k of [1,3,5]){
    setVox(VOX, cx-1, fy+k, cz, C.gold2);
    setVox(VOX, cx+1, fy+k, cz, C.gold2);
    setVox(VOX, cx, fy+k, cz-1, C.gold2);
    setVox(VOX, cx, fy+k, cz+1, C.gold2);
  }
  setVox(VOX, cx, fy+8, cz, C.gold);
  setVox(VOX, cx, fy+9, cz, C.gold);
  setVox(GLOW, cx, fy+10, cz, C.gold);
}

buildPagoda(PLAZA.x, PLAZA.z);

/* ==========================================================================
   6.  STONE LANTERNS, TORII, SHRINE BITS
   ========================================================================== */
function stoneLantern(x, y, z){
  for (let dx=-1;dx<=1;dx++) for (let dz=-1;dz<=1;dz++){
    if (Math.abs(dx)+Math.abs(dz) === 2) continue;
    setVox(VOX, x+dx, y,   z+dz, C.stoneDark);
  }
  setVox(VOX, x, y+1, z, C.stoneMid);
  setVox(VOX, x, y+2, z, C.stoneMid);
  for (let dx=-1;dx<=1;dx++) for (let dz=-1;dz<=1;dz++){
    if (Math.abs(dx)+Math.abs(dz) === 2) continue;
    setVox(VOX, x+dx, y+3, z+dz, C.stoneMid);
  }
  setVox(GLOW, x, y+4, z, C.lantern2);
  setVox(GLOW, x-1, y+4, z, C.lantern);
  setVox(GLOW, x+1, y+4, z, C.lantern);
  setVox(GLOW, x, y+4, z-1, C.lantern);
  setVox(GLOW, x, y+4, z+1, C.lantern);
  for (let dx=-1;dx<=1;dx++) for (let dz=-1;dz<=1;dz++){
    if (Math.abs(dx)===1 && Math.abs(dz)===1) continue;
    setVox(VOX, x+dx, y+5, z+dz, C.stoneDark);
  }
  setVox(VOX, x, y+6, z, C.stoneDark);
  setVox(VOX, x, y+7, z, C.stoneLight);
}
function placeLanternOnGround(x,z){
  if (!inside(x,z)) return;
  const y = Hmap[hi(x,z)] + 1;
  stoneLantern(x, y, z);
}

// line the approach
for (let i=0;i<6;i++){
  const z = 16 + i*5;
  placeLanternOnGround(-4, z);
  placeLanternOnGround( 4, z);
}
// around the plaza
for (let a=0;a<10;a++){
  const ang = a/10*Math.PI*2;
  const x = Math.round(PLAZA.x + Math.cos(ang)*17);
  const z = Math.round(PLAZA.z + Math.sin(ang)*17);
  placeLanternOnGround(x,z);
}
// pond side
placeLanternOnGround(-18, 24);
placeLanternOnGround(-40, 16);
placeLanternOnGround(-24, 33);

/* ---------- torii gate ---------- */
function torii(x, z, big){
  const gy = Hmap[hi(x,z)] + 1;
  const H  = big ? 9 : 7;
  const halfSpan = big ? 6 : 5;
  // pillars
  for (const s of [-1,1]){
    for (let y=0;y<H;y++){
      setVox(VOX, x + s*halfSpan, gy+y, z, C.vermilion);
      if (y < H-2) setVox(VOX, x + s*halfSpan, gy+y, z+ (s>0?1:-1)*0 , C.vermilion);
    }
    // base stones
    setVox(VOX, x+s*halfSpan, gy-1, z, C.stoneDark);
    setVox(VOX, x+s*halfSpan, gy-1, z+1, C.stoneDark);
    setVox(VOX, x+s*halfSpan, gy-1, z-1, C.stoneDark);
  }
  // top beam (kasagi) with upward-curving ends
  for (let dx=-halfSpan-2; dx<=halfSpan+2; dx++){
    const lift = (Math.abs(dx) >= halfSpan) ? 2 : ((Math.abs(dx) >= halfSpan-1) ? 1 : 0);
    setVox(VOX, x+dx, gy+H+lift, z, C.vermilion2);
    setVox(VOX, x+dx, gy+H+lift-1, z, C.vermilion);
    if (Math.abs(dx) <= halfSpan) setVox(VOX, x+dx, gy+H-2, z, C.vermilion);
  }
  // second beam (nuki)
  for (let dx=-halfSpan-1; dx<=halfSpan+1; dx++){
    setVox(VOX, x+dx, gy+H-4, z, C.vermilion);
  }
  // small plaque
  setVox(VOX, x, gy+H-3, z, C.gold);
  setVox(VOX, x-1, gy+H-3, z, C.gold2);
  setVox(VOX, x+1, gy+H-3, z, C.gold2);
}
torii(0, 14, true);
torii(-24, 31, false);
torii(34, 20, false);

/* ==========================================================================
   7.  TREES
   ========================================================================== */
function trunk(x, y, z, h, thick, colors){
  for (let i=0;i<h;i++){
    for (let dx=0; dx<thick; dx++){
      for (let dz=0; dz<thick; dz++){
        setVox(VOX, x+dx, y+i, z+dz, pickFrom(colors, x+dx, y+i, z+dz));
      }
    }
  }
}

function blob(cx, cy, cz, rx, ry, rz, colors, density){
  for (let i=-rx;i<=rx;i++){
    for (let j=-ry;j<=ry;j++){
      for (let k=-rz;k<=rz;k++){
        const d = (i*i)/(rx*rx) + (j*j)/(ry*ry) + (k*k)/(rz*rz);
        if (d > 1.0) continue;
        if (d > 0.45 && ihash(cx+i*7+1, cy+j*13+3, cz+k*5+7) > density) continue;
        setVox(VOX, cx+i, cy+j, cz+k, pickFrom(colors, cx+i, cy+j, cz+k));
      }
    }
  }
}

function cherryTree(x, z, size){
  const y = Hmap[hi(x,z)] + 1;
  const h = 5 + Math.round(size*1.6);
  const thick = size >= 2 ? 2 : 1;
  trunk(x, y, z, h, thick, [C.woodDark, C.woodMid, 0x4a2e1b]);

  // branches reaching outward
  const branches = 5 + Math.floor(size);
  for (let b=0;b<branches;b++){
    const ang = (b/branches)*Math.PI*2 + size*0.7;
    const len = 2 + Math.round(size*1.3);
    let bx = x + (thick>1?0:0), bz = z, by = y + h - 2;
    const dirx = Math.cos(ang), dirz = Math.sin(ang);
    for (let s=1;s<=len;s++){
      bx = Math.round(x + dirx*s*1.1);
      bz = Math.round(z + dirz*s*1.1);
      by = y + h - 2 + Math.round(s*0.55);
      setVox(VOX, bx, by, bz, C.woodMid);
      setVox(VOX, bx, by+1, bz, C.woodDark);
    }
    // canopy at branch tip
    blob(bx, by+2, bz, 3+Math.round(size*0.5), 2+Math.round(size*0.4), 3+Math.round(size*0.5),
         ihash(b,1,2) > 0.5 ? C.cherry : C.cherryD, 0.78);
  }
  // main canopy
  blob(x, y+h+1, z, 4+Math.round(size*1.2), 3+Math.round(size*0.7), 4+Math.round(size*1.2),
       C.cherry, 0.8);
  // deep shading underneath
  blob(x, y+h-1, z, 3+Math.round(size), 1, 3+Math.round(size), C.cherryD, 0.55);
}

function pineTree(x, z, size){
  const y = Hmap[hi(x,z)] + 1;
  const h = 4 + Math.round(size*2);
  trunk(x, y, z, h, 1, [C.woodDark, 0x4a2e1b]);
  const layers = 4 + Math.floor(size);
  for (let l=0;l<layers;l++){
    const t = l/(layers-1);
    const r = Math.max(1, Math.round((1-t)*(2.6+size*1.5)));
    const ly = y + 2 + Math.round(t*(h-1));
    for (let i=-r;i<=r;i++){
      for (let k=-r;k<=r;k++){
        const d = Math.hypot(i,k);
        if (d > r + 0.35) continue;
        if (d > r - 0.4 && ihash(x+i, ly, z+k) > 0.7) continue;
        setVox(VOX, x+i, ly, z+k, pickFrom(C.pine, x+i, ly, z+k));
      }
    }
  }
  setVox(VOX, x, y+h+1, z, C.pine[2]);
  setVox(VOX, x, y+h+2, z, C.pine[1]);
}

function leafTree(x, z, size){
  const y = Hmap[hi(x,z)] + 1;
  const h = 3 + Math.round(size*1.8);
  trunk(x, y, z, h, size>=2?2:1, [C.woodDark, C.woodMid]);
  blob(x, y+h+1, z, 3+Math.round(size*1.1), 3+Math.round(size*0.8), 3+Math.round(size*1.1),
       C.leaf, 0.75);
  blob(x, y+h, z, 2+Math.round(size*0.8), 1, 2+Math.round(size*0.8), C.leafD, 0.6);
}

function bambooStalk(x, z, h){
  const y = Hmap[hi(x,z)] + 1;
  for (let i=0;i<h;i++){
    const c = (i % 4 === 3) ? C.bambooL : pickFrom(C.bamboo, x, i, z);
    setVox(VOX, x, y+i, z, c);
  }
  const top = y + h;
  for (let a=0;a<5;a++){
    const ang = a*1.3 + x*0.4;
    const dx = Math.round(Math.cos(ang)*2);
    const dz = Math.round(Math.sin(ang)*2);
    setVox(VOX, x+dx, top-1, z+dz, C.bamboo[1]);
    setVox(VOX, x+dx, top,   z+dz, C.bamboo[0]);
    setVox(VOX, x+Math.round(dx*1.6), top, z+Math.round(dz*1.6), C.bamboo[2]);
  }
}

/* --- placement --- */
function freeSpot(x, z, pad){
  pad = pad || 0;
  if (!inside(x,z)) return false;
  if (Math.hypot(x-PLAZA.x, z-PLAZA.z) < 25 + pad) return false;
  if (Math.hypot(x-VILLAGE.x, z-VILLAGE.z) < 17 + pad) return false;
  const qd = Math.hypot((x-POND.x)/POND.rx, (z-POND.z)/POND.rz);
  if (qd < 1.28) return false;
  const h = Hmap[hi(x,z)];
  if (h < 1 || h > 15) return false;
  return true;
}

const CHERRY_SPOTS = [
  [-26,-28,2], [24,-30,2], [-32,-8,1.5], [30,-12,1.5],
  [-12,26,2], [12,26,1.5], [-44,2,2], [40,-14,1.5],
  [-42,-34,2], [34,-36,2], [-6,38,2], [52,20,1.5],
  [6,-44,2], [-52,18,1.5], [16,-38,1], [-36,28,1.5],
  [46,30,1.5], [18,34,1], [-8,-18,1.2], [8,-20,1.2]
];
for (const s of CHERRY_SPOTS){
  if (freeSpot(s[0], s[1], 1)) cherryTree(s[0], s[1], s[2]);
}

// scattered pines on the hills
for (let i=0;i<90;i++){
  const ang = rnd()*Math.PI*2;
  const rad = 40 + rnd()*18;
  const x = Math.round(Math.cos(ang)*rad);
  const z = Math.round(Math.sin(ang)*rad);
  if (!freeSpot(x, z, 1)) continue;
  const h = Hmap[hi(x,z)];
  if (h < 4) continue;
  if (rnd() > 0.55) pineTree(x, z, rr(0.6, 1.6));
  else leafTree(x, z, rr(0.6, 1.4));
}
// mid-ring decorative trees
for (let i=0;i<90;i++){
  const ang = rnd()*Math.PI*2;
  const rad = 26 + rnd()*16;
  const x = Math.round(Math.cos(ang)*rad);
  const z = Math.round(Math.sin(ang)*rad);
  if (!freeSpot(x, z, 2)) continue;
  const r = rnd();
  if (r < 0.35) cherryTree(x, z, rr(0.8, 1.6));
  else if (r < 0.62) leafTree(x, z, rr(0.7, 1.4));
  else if (r < 0.82) pineTree(x, z, rr(0.7, 1.3));
  else { /* leave gaps */ }
}

// bamboo grove
for (let i=0;i<70;i++){
  const x = Math.round(-46 + rr(-7, 7));
  const z = Math.round(-34 + rr(-7, 7));
  if (!inside(x,z)) continue;
  const h = Hmap[hi(x,z)];
  if (h < 1 || h > 14) continue;
  bambooStalk(x, z, ri(8, 16));
}

/* ==========================================================================
   8.  THE POND DETAIL (lilies, koi, boat, stones)
   ========================================================================== */
for (let i=0;i<70;i++){
  const ang = rnd()*Math.PI*2;
  const rad = Math.sqrt(rnd())*0.85;
  const x = Math.round(POND.x + Math.cos(ang)*rad*POND.rx);
  const z = Math.round(POND.z + Math.sin(ang)*rad*POND.rz);
  if (!inside(x,z)) continue;
  if (Hmap[hi(x,z)] > SEA_LEVEL-1) continue;
  // lily pad
  setVox(VOX, x, SEA_LEVEL+1, z, C.lily);
  if (ihash(x,1,z) > 0.75){
    setVox(VOX, x, SEA_LEVEL+2, z, pickFrom(C.lotus, x, 2, z));
    setVox(GLOW, x, SEA_LEVEL+3, z, C.lotus[0]);
  }
}
// koi fish just under the surface
for (let i=0;i<26;i++){
  const ang = rnd()*Math.PI*2;
  const rad = Math.sqrt(rnd())*0.7;
  const x = Math.round(POND.x + Math.cos(ang)*rad*POND.rx);
  const z = Math.round(POND.z + Math.sin(ang)*rad*POND.rz);
  if (!inside(x,z)) continue;
  const c = pickFrom(C.koi, x, 3, z);
  setVox(VOX, x, SEA_LEVEL, z, c);
  setVox(VOX, x+1, SEA_LEVEL, z, c);
}

// stepping stones across the pond
for (let i=0;i<9;i++){
  const t = i/8;
  const x = Math.round(POND.x - POND.rx*0.95 + t*POND.rx*1.9);
  const z = Math.round(POND.z + Math.sin(t*Math.PI)*3.0 - 1.5);
  if (!inside(x,z)) continue;
  setVox(VOX, x, SEA_LEVEL+1, z, C.stoneMid);
  setVox(VOX, x+1, SEA_LEVEL+1, z, C.stoneLight);
}

// little wooden boat
(function boat(){
  const bx = POND.x + 5, bz = POND.z - 5;
  for (let i=-3;i<=3;i++){
    setVox(VOX, bx+i, SEA_LEVEL+1, bz-1, C.woodMid);
    setVox(VOX, bx+i, SEA_LEVEL+1, bz+1, C.woodMid);
  }
  setVox(VOX, bx-3, SEA_LEVEL+2, bz-1, C.woodLight);
  setVox(VOX, bx-3, SEA_LEVEL+2, bz+1, C.woodLight);
  setVox(VOX, bx+3, SEA_LEVEL+2, bz-1, C.woodLight);
  setVox(VOX, bx+3, SEA_LEVEL+2, bz+1, C.woodLight);
  setVox(VOX, bx, SEA_LEVEL+1, bz, C.woodDark);
  // mast + lantern
  setVox(VOX, bx, SEA_LEVEL+2, bz, C.woodDark);
  setVox(VOX, bx, SEA_LEVEL+3, bz, C.woodDark);
  setVox(GLOW, bx, SEA_LEVEL+4, bz, C.lantern);
})();

/* ==========================================================================
   9.  VILLAGE
   ========================================================================== */
function house(cx, cz, w, d, hh, roofColors, wallColor, rot){
  rot = rot || 0;
  const gy = Hmap[hi(cx,cz)] + 1;
  const T = (x,z)=>{
    if (rot === 1) return [ z, -x];
    if (rot === 2) return [-x, -z];
    if (rot === 3) return [-z,  x];
    return [x, z];
  };
  // floor
  for (let x=-w;x<=w;x++) for (let z=-d;z<=d;z++){
    const p = T(x,z);
    setVox(VOX, cx+p[0], gy-0, cz+p[1], C.woodMid);
  }
  // walls
  for (let x=-w;x<=w;x++){
    for (let z=-d;z<=d;z++){
      const ax = Math.abs(x), az = Math.abs(z);
      const onX = ax === w, onZ = az === d;
      if (!onX && !onZ) continue;
      for (let y=0;y<hh;y++){
        const yy = gy + y;
        const corner = onX && onZ;
        let c;
        if (corner) c = C.woodDark;
        else if (y === 0) c = C.stoneMid;
        else {
          const win = (y === 1) && ((onZ && !onX && ax <= 1) || (onX && !onZ && az <= 1));
          if (win) c = (ihash(x,y,z) > 0.5) ? C.paper : C.window;
          else c = (ihash(x, y*3, z) > 0.88) ? C.plaster2 : wallColor;
        }
        // door on +z face
        if (onZ && az === d && ax <= 0 && y < 2) c = C.window;
        const p = T(x,z);
        setVox(VOX, cx+p[0], yy, cz+p[1], c);
      }
    }
  }
  // gable roof
  const rh = gy + hh;
  const levels = w + 2;
  for (let k=0;k<levels;k++){
    const e = (w+1) - k;
    if (e < 0) break;
    for (let x=-e;x<=e;x++){
      for (let z=-d-1;z<=d+1;z++){
        let c;
        if (x === 0) c = C.roofEdge;
        else if (Math.abs(x) === e) c = C.roofEdge;
        else c = pickFrom(roofColors, x, rh+k, z);
        const p = T(x,z);
        setVox(VOX, cx+p[0], rh+k, cz+p[1], c);
      }
    }
  }
  // chimney
  if (ihash(cx, 17, cz) > 0.5){
    const p = T(w-1, -d+1);
    for (let y=0;y<2;y++) setVox(VOX, cx+p[0], rh+levels+y, cz+p[1], C.stoneDark);
    setVox(VOX, cx+p[0], rh+levels+2, cz+p[1], C.stoneMid);
  }
}

const houseDefs = [
  [21,  2, 4, 3, 3, C.roofTile, C.plaster, 0],
  [33, -2, 5, 4, 4, C.thatch,   C.plaster, 0],
  [44,  4, 4, 3, 3, C.roofTile, C.plaster2, 1],
  [20, 14, 5, 3, 3, C.thatch,   C.plaster, 0],
  [44, 16, 4, 4, 4, C.roofTile, C.plaster, 2],
  [33, 18, 3, 3, 2, C.thatch,   C.plaster2, 0],
  [24, -12, 4, 3, 3, C.roofRed, C.plaster, 1],
  [43, -8, 3, 3, 2, C.thatch,   C.plaster, 0]
];
for (const h of houseDefs) house(h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);

/* ---------- village square: well, market stalls, fences ---------- */
(function well(){
  const x = VILLAGE.x, z = VILLAGE.z;
  const gy = Hmap[hi(x,z)] + 1;
  for (let dx=-2;dx<=2;dx++) for (let dz=-2;dz<=2;dz++){
    const d = Math.hypot(dx,dz);
    if (d > 2.2) continue;
    if (d < 1.1) { setVox(WAT, x+dx, gy, z+dz, C.waterDeep); continue; }
    setVox(VOX, x+dx, gy, z+dz, (Math.abs(dx)===2||Math.abs(dz)===2) ? C.stoneDark : C.stoneMid);
    setVox(VOX, x+dx, gy+1, z+dz, C.stoneMid);
  }
  for (const s of [[-2,-2],[2,-2],[-2,2],[2,2]]){
    for (let y=2;y<5;y++) setVox(VOX, x+s[0], gy+y, z+s[1], C.woodMid);
  }
  for (let dx=-2;dx<=2;dx++) for (let dz=-2;dz<=2;dz++){
    if (Math.abs(dx)===2 || Math.abs(dz)===2 || (Math.abs(dx)<=1 && Math.abs(dz)<=1))
      setVox(VOX, x+dx, gy+5, z+dz, C.thatch[0]);
  }
  setVox(VOX, x, gy+6, z, C.woodDark);
})();

function marketStall(x, z, rot){
  const gy = Hmap[hi(x,z)] + 1;
  const T = (a,b)=> rot ? [b,-a] : [a,b];
  // posts
  for (const p of [[-2,-1],[2,-1],[-2,1],[2,1]]){
    const q = T(p[0],p[1]);
    for (let y=0;y<4;y++) setVox(VOX, x+q[0], gy+y, z+q[1], C.woodMid);
  }
  // counter
  for (let a=-2;a<=2;a++){
    const q = T(a,1);
    setVox(VOX, x+q[0], gy+1, z+q[1], C.woodLight);
  }
  // awning
  const col = pickFrom(C.cloth, x, 5, z);
  for (let a=-2;a<=2;a++){
    for (let b=-1;b<=1;b++){
      const q = T(a,b);
      setVox(VOX, x+q[0], gy+4, z+q[1], (a+b)%2===0 ? col : 0xffffff);
    }
  }
  // goods
  setVox(VOX, x + (rot?1:0), gy+2, z + (rot?0:1), C.lantern);
  setVox(VOX, x + (rot?-1:0), gy+2, z + (rot?0:-1), C.gold);
}
marketStall(27, 4, false);
marketStall(39, 13, true);
marketStall(30, 14, false);

/* ---------- fences around crop fields ---------- */
function fenceLine(x0,z0,x1,z1){
  const len = Math.max(Math.abs(x1-x0), Math.abs(z1-z0));
  for (let i=0;i<=len;i++){
    const t = len ? i/len : 0;
    const x = Math.round(x0 + (x1-x0)*t);
    const z = Math.round(z0 + (z1-z0)*t);
    if (!inside(x,z)) continue;
    const gy = Hmap[hi(x,z)] + 1;
    setVox(VOX, x, gy, z, C.woodDark);
    setVox(VOX, x, gy+1, z, C.woodMid);
    if (i % 2 === 0) setVox(VOX, x, gy+2, z, C.woodPale);
  }
}
// crop field south-east of the village
const FX = 40, FZ = 30;
for (let x=-6;x<=6;x++){
  for (let z=-5;z<=5;z++){
    const gy = Hmap[hi(FX+x, FZ+z)];
    const row = Math.abs(z) % 3 === 0;
    const c = row ? C.dirt[0] : pickFrom(C.crop, FX+x, gy, FZ+z);
    paintGround(FX+x, FZ+z, c);
    if (!row && ihash(FX+x, 1, FZ+z) > 0.6){
      setVox(VOX, FX+x, gy+1, FZ+z, pickFrom(C.crop, FX+x, 2, FZ+z));
      if (ihash(FX+x, 3, FZ+z) > 0.85)
        setVox(GLOW, FX+x, gy+2, FZ+z, pickFrom(C.flower, FX+x, 4, FZ+z));
    }
  }
}
fenceLine(FX-7, FZ-6, FX+7, FZ-6);
fenceLine(FX-7, FZ+6, FX+7, FZ+6);
fenceLine(FX-7, FZ-6, FX-7, FZ+6);
fenceLine(FX+7, FZ-6, FX+7, FZ+6);

/* ---------- red paper lanterns on posts along the village path ---------- */
function lanternPost(x, z){
  const gy = Hmap[hi(x,z)] + 1;
  setVox(VOX, x, gy, z, C.woodDark);
  setVox(VOX, x, gy+1, z, C.woodDark);
  setVox(VOX, x, gy+2, z, C.woodDark);
  setVox(VOX, x, gy+3, z, C.woodMid);
  setVox(GLOW, x+1, gy+3, z, C.lantern);
  setVox(GLOW, x-1, gy+3, z, C.lantern);
  setVox(GLOW, x, gy+3, z+1, C.lantern);
  setVox(GLOW, x, gy+4, z, C.lantern2);
}
for (let i=0;i<7;i++){
  const t = i/6;
  const x = Math.round(6 + (34-6)*t);
  const z = Math.round(34 + (18-34)*t + Math.sin(t*3)*3);
  lanternPost(x+4, z);
  lanternPost(x-4, z+2);
}

/* ==========================================================================
   10. PEOPLE
   ========================================================================== */
function person(x, y, z, shirt, pants, skin, hair, hat){
  setVox(VOX, x, y,   z, pants);
  setVox(VOX, x, y+1, z, shirt);
  setVox(VOX, x, y+2, z, skin);
  setVox(VOX, x, y+3, z, hat ? hat : hair);
  if (ihash(x,1,z) > 0.55){
    setVox(VOX, x+1, y+1, z, shirt);
    setVox(VOX, x+1, y+2, z, skin);
  } else {
    setVox(VOX, x-1, y+1, z, shirt);
    setVox(VOX, x-1, y+2, z, skin);
  }
}
function personAt(x, z, opts){
  if (!inside(x,z)) return;
  const gy = Hmap[hi(x,z)] + 1;
  const shirt = opts && opts.shirt ? opts.shirt : pickFrom(C.cloth, x, 1, z);
  const pants = opts && opts.pants ? opts.pants : [0x3a4a6b, 0x4a3a2a, 0x2f2f38, 0x6b4a3a][Math.floor(ihash(x,2,z)*4)%4];
  const skin  = pickFrom(C.skin, x, 3, z);
  const hair  = pickFrom(C.hair, x, 4, z);
  const hat   = (ihash(x,5,z) > 0.55) ? C.woodPale : null;
  person(x, gy, z, shirt, pants, skin, hair, hat);
}

// villagers along the main path and in the village
const peopleSpots = [
  [2,22],[ -1,26],[4,34],[9,38],[15,41],[21,41],[27,37],[31,30],[33,24],
  [26,6],[30,10],[36,6],[38,12],[29,16],[35,2],[42,8],[23,10],[46,12],
  [-10,22],[-16,25],[-21,23],[-27,26],
  [-3,-24],[7,-27],[14,-24],[20,-16],[22,-8],
  [40,30],[44,32],[38,34]
];
for (const p of peopleSpots) personAt(p[0], p[1]);

/* ==========================================================================
   11.  FLOWERS SCATTERED ON GRASS
   ========================================================================== */
for (let x=-64;x<=64;x++){
  for (let z=-64;z<=64;z++){
    if (!inside(x,z)) continue;
    const id = hi(x,z);
    if (TopType[id] !== 0) continue;
    const h = Hmap[id];
    if (h < 1 || h > 13) continue;
    if (ihash(x, 77, z) > 0.9915){
      setVox(VOX, x, h+1, z, pickFrom(C.flower, x, 9, z));
    } else if (ihash(x, 91, z) > 0.988){
      setVox(VOX, x, h+1, z, pickFrom(C.grassHi, x, 11, z));
    }
  }
}

/* ==========================================================================
   12.  THREE.JS SCENE
   ========================================================================== */
const renderer = new THREE.WebGLRenderer({ antialias:true, powerPreference:'high-performance' });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.06;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xcfe3f4, 190, 470);

const camera = new THREE.PerspectiveCamera(46, window.innerWidth/window.innerHeight, 0.5, 4000);
camera.position.set(96, 78, 122);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 12, -2);
controls.enableDamping = true;
controls.dampingFactor = 0.055;
controls.minDistance = 32;
controls.maxDistance = 300;
controls.maxPolarAngle = Math.PI * 0.487;
controls.minPolarAngle = 0.10;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.28;
controls.update();

/* ---------- sky dome ---------- */
(function sky(){
  const geo = new THREE.SphereGeometry(1400, 40, 26);
  const pos = geo.attributes.position;
  const cols = new Float32Array(pos.count*3);
  const cTop = new THREE.Color(0x2b6cb8);
  const cMid = new THREE.Color(0x8fc4ea);
  const cBot = new THREE.Color(0xe6f2fb);
  const tmp = new THREE.Color();
  for (let i=0;i<pos.count;i++){
    const t = clamp(pos.getY(i)/1400 * 0.5 + 0.5, 0, 1);
    if (t < 0.5) tmp.copy(cBot).lerp(cMid, t*2);
    else         tmp.copy(cMid).lerp(cTop, (t-0.5)*2);
    cols[i*3] = tmp.r; cols[i*3+1] = tmp.g; cols[i*3+2] = tmp.b;
  }
  geo.setAttribute('color', new THREE.BufferAttribute(cols, 3));
  const mat = new THREE.MeshBasicMaterial({ vertexColors:true, side:THREE.BackSide, fog:false, depthWrite:false });
  const m = new THREE.Mesh(geo, mat);
  m.renderOrder = -1000;
  scene.add(m);
})();

/* ---------- lights ---------- */
scene.add(new THREE.HemisphereLight(0xcfe6ff, 0x5a7a44, 1.05));
scene.add(new THREE.AmbientLight(0xffffff, 0.18));

const sun = new THREE.DirectionalLight(0xfff0cf, 2.35);
sun.position.set(102, 152, 74);
sun.castShadow = true;
sun.shadow.mapSize.set(4096, 4096);
sun.shadow.camera.left   = -92;
sun.shadow.camera.right  =  92;
sun.shadow.camera.top    =  92;
sun.shadow.camera.bottom = -92;
sun.shadow.camera.near   = 40;
sun.shadow.camera.far    = 340;
sun.shadow.bias = -0.0004;
sun.shadow.normalBias = 0.09;
scene.add(sun);
scene.add(sun.target);
sun.target.position.set(0, 0, 0);

// soft bounce light from the water
const bounce = new THREE.DirectionalLight(0x9fd0ff, 0.32);
bounce.position.set(-80, 30, -60);
scene.add(bounce);

/* ---------- ocean plane ---------- */
(function ocean(){
  const geo = new THREE.RingGeometry(48, 900, 128, 4);
  geo.rotateX(-Math.PI/2);
  const mat = new THREE.MeshStandardMaterial({
    color: 0x2b87bd,
    transparent: true,
    opacity: 0.88,
    roughness: 0.12,
    metalness: 0.15,
    side: THREE.DoubleSide
  });
  const m = new THREE.Mesh(geo, mat);
  m.position.y = SEA_LEVEL - 0.42;
  m.receiveShadow = false;
  scene.add(m);
})();

/* ==========================================================================
   13.  BUILD INSTANCED MESHES
   ========================================================================== */
const boxGeo = new THREE.BoxGeometry(0.985, 0.985, 0.985);

function buildInstanced(map, material, castShadow, receiveShadow){
  const count = map.size;
  if (!count) return null;
  const mesh = new THREE.InstancedMesh(boxGeo, material, count);
  const mat4 = new THREE.Matrix4();
  let i = 0;
  for (const [key, hex] of map){
    const x = (key % 1024) - 512;
    const z = (Math.floor(key / 1024) % 1024) - 512;
    const y = Math.floor(key / 1048576) - 512;
    mat4.makeTranslation(x, y, z);
    mesh.setMatrixAt(i, mat4);
    mesh.setColorAt(i, colorOf(hex));
    i++;
  }
  mesh.instanceMatrix.needsUpdate = true;
  if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
  mesh.castShadow = castShadow;
  mesh.receiveShadow = receiveShadow;
  mesh.frustumCulled = false;
  scene.add(mesh);
  return mesh;
}

const solidMat = new THREE.MeshLambertMaterial({ color: 0xffffff });
buildInstanced(VOX, solidMat, true, true);

const glowMat = new THREE.MeshBasicMaterial({ color: 0xffffff, toneMapped: false });
buildInstanced(GLOW, glowMat, false, false);

const waterMat = new THREE.MeshStandardMaterial({
  color: 0xffffff, transparent: true, opacity: 0.74,
  roughness: 0.08, metalness: 0.05, depthWrite: false
});
buildInstanced(WAT, waterMat, false, false);

const totalVoxels = VOX.size + GLOW.size + WAT.size;
document.getElementById('vcount').textContent = totalVoxels.toLocaleString() + '  (solid ' +
  VOX.size.toLocaleString() + ' / water ' + WAT.size.toLocaleString() + ' / glow ' + GLOW.size.toLocaleString() + ')';

/* ==========================================================================
   14.  FALLING CHERRY PETALS
   ========================================================================== */
const PETALS = 1100;
const petalPos   = new Float32Array(PETALS*3);
const petalSpd   = new Float32Array(PETALS);
const petalPhase = new Float32Array(PETALS);
for (let i=0;i<PETALS;i++){
  const a = Math.random()*Math.PI*2;
  const r = Math.sqrt(Math.random())*62;
  petalPos[i*3]   = Math.cos(a)*r;
  petalPos[i*3+1] = Math.random()*48;
  petalPos[i*3+2] = Math.sin(a)*r;
  petalSpd[i]     = 0.4 + Math.random()*1.1;
  petalPhase[i]   = Math.random()*Math.PI*2;
}
const petalGeo = new THREE.BufferGeometry();
petalGeo.setAttribute('position', new THREE.BufferAttribute(petalPos, 3));
const petalTex = (function(){
  const c = document.createElement('canvas');
  c.width = c.height = 32;
  const ctx = c.getContext('2d');
  const g = ctx.createRadialGradient(16,16,0,16,16,16);
  g.addColorStop(0.0, 'rgba(255,235,244,1)');
  g.addColorStop(0.45,'rgba(255,190,215,0.95)');
  g.addColorStop(1.0, 'rgba(255,170,205,0)');
  ctx.fillStyle = g;
  ctx.fillRect(0,0,32,32);
  const t = new THREE.CanvasTexture(c);
  t.colorSpace = THREE.SRGBColorSpace;
  return t;
})();
const petalMat = new THREE.PointsMaterial({
  size: 1.15, map: petalTex, transparent: true, depthWrite: false,
  blending: THREE.NormalBlending, sizeAttenuation: true, color: 0xffc6dc, opacity: 0.95
});
const petals = new THREE.Points(petalGeo, petalMat);
petals.frustumCulled = false;
scene.add(petals);

/* ==========================================================================
   15.  ANIMATE
   ========================================================================== */
const clock = new THREE.Clock();
let tAcc = 0;

function animate(){
  requestAnimationFrame(animate);
  const dt = Math.min(clock.getDelta(), 0.05);
  tAcc += dt;

  // petals drift
  const arr = petalGeo.attributes.position.array;
  for (let i=0;i<PETALS;i++){
    const i3 = i*3;
    arr[i3+1] -= (0.9 + petalSpd[i]) * dt * 2.6;
    arr[i3]   += Math.sin(tAcc*0.85 + petalPhase[i]) * dt * 2.2;
    arr[i3+2] += Math.cos(tAcc*0.65 + petalPhase[i]*1.3) * dt * 2.2;
    if (arr[i3+1] < 0.5){
      const a = Math.random()*Math.PI*2;
      const r = Math.sqrt(Math.random())*60;
      arr[i3]   = Math.cos(a)*r;
      arr[i3+1] = 40 + Math.random()*14;
      arr[i3+2] = Math.sin(a)*r;
    }
  }
  petalGeo.attributes.position.needsUpdate = true;

  // gentle water shimmer
  waterMat.opacity = 0.70 + Math.sin(tAcc*0.9)*0.05;

  controls.update();
  renderer.render(scene, camera);
}

/* ==========================================================================
   16.  RESIZE + BOOT
   ========================================================================== */
window.addEventListener('resize', ()=>{
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
renderer.domElement.addEventListener('pointerdown', ()=>{ controls.autoRotate = false; });

// warm up shaders then reveal
requestAnimationFrame(()=>{
  renderer.compile(scene, camera);
  animate();
  setTimeout(()=>{
    const l = document.getElementById('loader');
    l.classList.add('hide');
    setTimeout(()=> l.remove(), 700);
  }, 220);
});
</script>
</body>
</html>

Scene Composition & Interactive Exploration

The scene is built around three layered zones—a central pagoda plaza, a village, and a pond—each with distinct voxel details and interactive behavior.

  • Pagoda & Stone Lanterns: A five-tier pagoda with golden finial anchors the garden. Stone lanterns with glowing interiors line the main path, and torii gates mark key entry points.
  • Nature & Cherry Blossoms: Cherry trees, pine trees, and bamboo groves populate the terrain. Falling cherry petals drift continuously, with randomized speeds and paths, adding gentle movement.
  • Village Life: Houses with varied roofs, a well, market stalls, and fenced crop fields create a lived-in village. Dozens of small voxel people with different clothing colors are placed along paths and communal spaces.
  • Interactive Controls: Drag to orbit the camera, use the mouse wheel to zoom, and right-drag to pan. An auto-rotate mode slowly circles the scene until you click, giving a sweeping overview of the layout.

The HUD in the bottom-left shows the total voxel count, and a loading overlay hides the scene until shaders are ready.

Optimization Tip: You can easily tune the world by adjusting the PETALS constant at the top of section 14 to change the number of cherry petals, or modify the holes array in section 9 to reposition buildings. The autoRotateSpeed and controls.maxDistance in section 12 let you control camera behavior.

0.70s
首Token
实际等待 0.95s
231
思考TPS
149s · 34,442 tok
331
输出TPS
51.6s · 17,060 tok
51,502
总Tokens(官方)
输入 108
计时详情
实际等待(客户端)
0.95s
客户端请求准备
0.01s
本站代理准备
0.00s
上游响应头到达
0.38s
上游首字节到达
0.38s
上游首 Token 到达
0.70s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-28T07:30:37.286Z",
  "transport": "railway",
  "ttftSource": "proxy",
  "clientTtftMs": 952.5,
  "completionId": "dfd3f1c4-e4be-4fde-b6ef-9072c86ffb40",
  "streamResumes": 4,
  "proxyPrepareMs": 1,
  "proxyRequestId": "goODPRCzQous3syAWUN5dQ",
  "upstreamStatus": 200,
  "upstreamTtftMs": 698,
  "clientHeadersMs": 296.2999997138977,
  "clientPrepareMs": 12.699999809265137,
  "upstreamHeadersMs": 383,
  "upstreamFirstByteMs": 384,
  "clientFirstContentMs": 150376.2999997139
}
总用时 202s · 平均 256 tok/s · 峰值 419 tok/s
智谱 GLM-5.3-flash
glm-5.3-flash
完成

历史结果 · 2026/9/28 07:53:39

The user wants an elaborate voxel art scene: a pagoda in a beautiful garden with cherry blossom trees, plus a village with people, colorful, detailed, impressive, single HTML file. This is a Three.js job with instanced voxel rendering for performance. Let me plan this seriously — this needs to be genuinely impressive, not a flat plane with a couple of boxes. **Core architecture:** - Three.js from CDN (r128 or newer via unpkg). I'll use InstancedMesh for voxels grouped by color — actually better: one InstancedMesh with per-instance colors (`instanceColor`). That way hundreds of thousands of voxels render in a few draw calls. - Voxels stored as a map keyed by `x,y,z` to avoid duplicates (voxel merging), then build instanced mesh. - OrbitControls for drag/rotate/zoom (interactive 3D is essential). - Small idle animations: falling cherry blossom petals (points or small instanced boxes drifting), maybe swaying via time... petals as a separate particle system of tiny instanced cubes spiraling down. That's the "alive" feel. - Maybe a day sky gradient, soft fog, directional light with shadows (shadow map on instanced mesh works). - Water pond with animated shimmer? Could do a pond with semi-transparent plane and animated... Keep it voxel: water voxels with slight color variance and maybe gentle sinusoidal bobbing on a separate instanced mesh of water surface voxels — animate instance matrices per frame for a subset. Could be expensive but with a modest count fine. - People: tiny voxel figures (2-3 voxels wide) placed around the village, some on paths. A few animated to walk along paths? Animating instances in a separate instanced mesh — yes, do a few villagers that patrol paths. That's a signature moment: living village. - Lanterns that glow at... keep it day scene, but glowing lanterns on the pagoda look nice with emissive-ish colors (just bright colors + point light maybe). **Scene contents (detailed plan):** Terrain: 160x160 or larger. Height map using layered value noise (implement simple hash-based value noise). Compose: - Base grass green with variation. - A river/stream running through, with a bridge. - Stone paths winding. - Pond near pagoda. - Hills at edges rising, maybe rocks. Pagoda: 5 tiers, red columns, white/cream walls, dark teal/blue-ish tiered roofs with upturned edges (voxel stepped roofs overhang), golden spire. Interior floors, railings, stairs. On a stone platform hill. Maybe ~40-50 voxels wide, height ~70. Garden: cherry blossom trees (pink canopy clusters, some lighter pink, dark trunks), green maple-ish trees, pine trees (layered cones), bushes, flowers (red/yellow/white single voxels on grass), stone lanterns (tōrō), torii gate in red, a small zen sand garden patch? A fence around garden. Village: 8-12 small houses with varied roof colors (blue-gray, terracotta, green), walls white/wood brown, windows, doors, chimneys with smoke (animated voxel puffs!). Smoke as a separate small instanced mesh rising and dissipating — nice life detail. A well, market stalls with striped awnings, crates, barrels, fences, clotheslines? People: ~20 villagers: body color varied, head skin tone, hair black. Some static in poses, 4-6 walking along path segments. Extras: ducks on the pond (small white/yellow), koi (orange voxels under water?), birds flying? Keep scope manageable but rich. **Rendering approach:** - `const voxels = new Map(); key = x+"|"+y+"|"+z`; value = color hex. - Helper functions: `set(x,y,z,c)`, `box(x1,y1,z1,x2,y2,z2,c, variance)`, `cylinder`, `sphere` for canopies, etc. - After generation, create InstancedMesh(BoxGeometry(1,1,1), MeshLambertMaterial with vertexColors... for instanceColor use material with `color: white` and set `instanceColor`). r128 supports `instancedMesh.instanceColor`? Actually setColorAt exists since r117ish. Yes, `mesh.setColorAt(i, color)` works in r125+. I'll use a recent version, e.g., 0.160.0 via unpkg module? OrbitControls via module import map. Simpler: use three@0.128.0 with global THREE and the examples/js OrbitControls from same version. That's reliable. r128 supports setColorAt and instanceColor. Yes. Color variation: per-voxel slight random darken/brighten for organic feel — crucial for voxel art quality. Voxel count estimate: terrain 160x160 = 25k surface voxels (only top few layers to save count — fill depth 2-3 below surface). Trees: ~40 trees × 300 = 12k. Pagoda ~15k. Village ~15k. Total maybe 80-120k voxels. InstancedMesh handles that easily. Shadows: 100k shadow-casting instances is fine with one directional light, 2048 map. Actually to reduce instances: only render voxels that have at least one exposed face? With a Map we can check neighbors — skip fully occluded voxels. Terrain columns: fill from surface down 3, but neighbors hide... Simply do culling pass at the end: keep voxel if any of 6 neighbors is empty. That cuts huge amounts. Good — "we have a power machine" but efficiency lets me add more detail. **Animation systems:** 1. Petals: InstancedMesh of small boxes (0.35 size), ~600, each with fall speed, sway, rotation; reset at top when hitting ground. Spawn near cherry trees + wind drift. 2. Smoke: from chimneys + pagoda? ~150 puffs cycling upward with growth & fade (fade via color darkening toward sky? Instance opacity isn't per-instance easily — could scale to 0 and color lerp to sky). Use scale up then set color toward fog color. 3. Walking villagers: small InstancedMesh with legs? Simplest: each villager is 4-5 voxels in one instanced mesh, but animating limb swing means per-instance matrix per voxel... Alternative: each walker is a Group of boxes (head, body, 2 legs) — only ~6 walkers so real meshes fine, with waddle bobbing and leg swing. 4. Water: pond surface as separate instanced mesh; per-frame subtle y scale/y-position sine per instance based on position — maybe 1500 water voxels, updating matrices per frame is OK. Or just do static water with two-tone animated color? Keep static with animated specular? Simpler: animate a few koi swimming under transparent water + gentle sine bob on water instanced mesh. I'll do the sine bob on water voxels (store base positions). Could be 2-3k instances — updating 3k matrices per frame is fine. 5. Birds: a few dark voxel birds flying in circles — small Groups with flapping wings (two boxes rotating). 5 birds. 6. Lanterns glow: emissive material color; day scene so keep subtle. Maybe add windows glowing warm. Also: subtle camera intro animation (orbit-in), then user control. Auto-rotate option toggle in a small UI. UI: title plate with scene name in a nice serif font, stats (voxel count), toggle buttons: auto-rotate, petals on/off maybe. Keep minimal, elegant, Japanese-influenced typography. Font: use Google Fonts — "Zen Old Mincho" or "Shippori Mincho" — fits theme, not overused. Loading: generation is synchronous, fast enough (<1s). Show a brief overlay then fade. **Lighting:** - Warm directional sun with shadows (position angled), ambient/hemisphere light (sky blue / ground warm). - Sky: gradient via large sphere with shader or just scene.background color + fog. Better: canvas gradient texture as background. I'll make a gradient sky dome via ShaderMaterial (simple) or a big canvas texture on backside sphere. Do canvas gradient texture — easy. - Clouds: voxel clouds floating! A few cloud clusters (white/light voxels) drifting slowly — separate instanced mesh, move x, wrap around. Lovely detail. **Layout plan (coordinates, world ~200×200, center 0,0):** - Ground y=0 base; heights 0..20. - Pagoda hill: center-north, say at (-25, -, -35), a raised stone terrace ~ radius 22. - Pond: south-east of pagoda, at (15, -, 0)-ish, radius ~14, connecting stream flowing east then south past village. - Village: south flat area z in [35, 80], x in [-60, 50]. - Torii gate at path entrance to garden, around (0, -, 12). - Paths: main path from torii to pagoda stairs; village paths connecting houses; bridge over stream. - Cherry trees: arou

Here's my take on this: a hand-built voxel diorama I call 「花守の里」— The Blossom Keeper's Village. A five-tier pagoda on a stone terrace, a cherry-blossom garden with a torii gate and an arched bridge, a pond with koi and ducks, and a living village with walking villagers, smoking chimneys, market stalls and rice paddies — every voxel placed by procedural builders, rendered as a single instanced mesh.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>花守の里 — The Blossom Keeper's Village</title>
<link href="https://fonts.googleapis.com/css2?family=Shippori+Mincho:wght@500;700;800&family=Zen+Kaku+Gothic+New:wght@400;500&display=swap" rel="stylesheet">
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  html,body { width:100%; height:100%; overflow:hidden; background:#14100e; }
  canvas { display:block; }
  .vignette { position:fixed; inset:0; pointer-events:none; box-shadow: inset 0 0 180px rgba(15,10,8,.38); z-index:5; }
  #ui { position:fixed; inset:0; pointer-events:none; z-index:10; font-family:'Zen Kaku Gothic New',sans-serif; color:#f2e9d6; }
  .panel { position:absolute; pointer-events:auto; background:rgba(22,17,14,.78); border:1px solid rgba(224,168,90,.32); backdrop-filter: blur(6px); }
  #title { top:22px; left:24px; padding:16px 22px 14px; }
  #title h1 { font-family:'Shippori Mincho',serif; font-weight:800; font-size:30px; letter-spacing:.14em; color:#f6ecd7; line-height:1.1; }
  #title h1 span { color:#e0a85a; }
  #title .en { font-family:'Shippori Mincho',serif; font-size:12px; letter-spacing:.24em; color:#c9b691; margin-top:5px; text-transform:uppercase; }
  #title .sub { font-size:11px; color:#9a8c74; margin-top:7px; letter-spacing:.05em; }
  #stats { bottom:22px; left:24px; padding:9px 16px; font-size:11.5px; letter-spacing:.08em; color:#c9b691; }
  #stats b { color:#f2e9d6; font-weight:500; }
  #hint { bottom:22px; right:24px; padding:9px 16px; font-size:11px; letter-spacing:.06em; color:#9a8c74; }
  #btns { top:22px; right:24px; display:flex; gap:8px; }
  .btn { pointer-events:auto; display:flex; align-items:center; gap:7px; cursor:pointer; user-select:none;
         background:rgba(22,17,14,.78); border:1px solid rgba(224,168,90,.32); color:#d8c8a4;
         font-family:'Zen Kaku Gothic New',sans-serif; font-size:11px; letter-spacing:.12em; padding:9px 14px;
         transition:all .18s ease; backdrop-filter: blur(6px); }
  .btn:hover { border-color:rgba(224,168,90,.7); color:#f6ecd7; }
  .btn.on { color:#f0b95e; border-color:rgba(224,168,90,.75); }
  .btn svg { width:13px; height:13px; stroke:currentColor; fill:none; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
  #loader { position:fixed; inset:0; z-index:50; background:#14100e; display:flex; flex-direction:column;
            align-items:center; justify-content:center; gap:18px; transition:opacity 1.1s ease; }
  #loader.hide { opacity:0; pointer-events:none; }
  #loader .kanji { font-family:'Shippori Mincho',serif; font-size:52px; font-weight:700; letter-spacing:.3em; color:#e8d9b8; }
  #loader .line { width:120px; height:1px; background:rgba(224,168,90,.5); }
  #loader .msg { font-size:11px; letter-spacing:.3em; color:#8d7f66; text-transform:uppercase; }
</style>
</head>
<body>
<div id="loader">
  <div class="kanji">花守の里</div>
  <div class="line"></div>
  <div class="msg">placing voxels</div>
</div>
<div class="vignette"></div>
<div id="ui">
  <div class="panel" id="title">
    <h1>花守の<span>里</span></h1>
    <div class="en">The Blossom Keeper's Village</div>
    <div class="sub">a voxel diorama — pagoda · gardens · village life</div>
  </div>
  <div class="panel" id="stats"></div>
  <div id="btns">
    <div class="btn on" id="bOrbit"><svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v5h-5"/></svg>ORBIT</div>
    <div class="btn on" id="bPetal"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="2.4"/><circle cx="12" cy="5.5" r="3"/><circle cx="18" cy="10" r="3"/><circle cx="16" cy="17" r="3"/><circle cx="8" cy="17" r="3"/><circle cx="6" cy="10" r="3"/></svg>PETALS</div>
    <div class="btn" id="bReset"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/></svg>RESET VIEW</div>
  </div>
  <div class="panel" id="hint">drag&nbsp;·&nbsp;orbit&nbsp;&nbsp;&nbsp;scroll&nbsp;·&nbsp;zoom&nbsp;&nbsp;&nbsp;right-drag&nbsp;·&nbsp;pan</div>
</div>

<script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
/* =========================================================================
   花守の里 — The Blossom Keeper's Village
   A fully procedural voxel diorama. Every structure is built from code.
   ========================================================================= */

// ------------------------- renderer / scene ------------------------------
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.02;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xdcebee, 170, 420);

// sky — painted vertical gradient
(function(){
  const cv = document.createElement('canvas'); cv.width = 2; cv.height = 512;
  const g = cv.getContext('2d');
  const gr = g.createLinearGradient(0,0,0,512);
  gr.addColorStop(0.0, '#6fb1e8');
  gr.addColorStop(0.45,'#a8d2ef');
  gr.addColorStop(0.78,'#e3eff1');
  gr.addColorStop(0.92,'#f6e7d4');
  gr.addColorStop(1.0, '#f0d9c2');
  g.fillStyle = gr; g.fillRect(0,0,2,512);
  const tex = new THREE.CanvasTexture(cv);
  scene.background = tex;
})();

const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.5, 900);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; controls.dampingFactor = 0.06;
controls.minDistance = 18; controls.maxDistance = 340;
controls.maxPolarAngle = Math.PI * 0.495;
controls.autoRotate = true; controls.autoRotateSpeed = 0.35;
controls.target.set(-6, 14, -8);

const CAM_START = new THREE.Vector3(165, 105, 185);
const CAM_END   = new THREE.Vector3(72, 46, 96);
camera.position.copy(CAM_START);

// lights
const hemi = new THREE.HemisphereLight(0xcfe2ff, 0x9c8a66, 0.62);
scene.add(hemi);
const sun = new THREE.DirectionalLight(0xffeecf, 1.25);
sun.position.set(95, 140, 60);
sun.castShadow = true;
sun.shadow.mapSize.set(4096, 4096);
sun.shadow.camera.left = -135; sun.shadow.camera.right = 135;
sun.shadow.camera.top = 135;   sun.shadow.camera.bottom = -135;
sun.shadow.camera.near = 20;   sun.shadow.camera.far = 460;
sun.shadow.bias = -0.0004; sun.shadow.normalBias = 0.6;
scene.add(sun);
const fill = new THREE.DirectionalLight(0xa8c0dd, 0.28);
fill.position.set(-80, 45, -70);
scene.add(fill);

// ------------------------- utility ---------------------------------------
const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
const lerp  = (a,b,t)=>a+(b-a)*t;
const smooth= t=>{ t=clamp(t,0,1); return t*t*(3-2*t); };
function hash2(ix,iz){
  let n = (Math.imul(ix,374761393) + Math.imul(iz,668265263))|0;
  n = Math.imul(n ^ (n>>>13), 1274126177); n ^= n>>>16;
  return (n>>>0)/4294967296;
}
function vnoise(x,z){
  const ix=Math.floor(x), iz=Math.floor(z), fx=x-ix, fz=z-iz;
  const a=hash2(ix,iz), b=hash2(ix+1,iz), c=hash2(ix,iz+1), d=hash2(ix+1,iz+1);
  const u=fx*fx*(3-2*fx), v=fz*fz*(3-2*fz);
  return a+(b-a)*u+(c-a)*v+(a-b-c+d)*u*v;
}
const fbm = (x,z)=> vnoise(x,z)*0.55 + vnoise(x*2.13+13.7, z*2.13+7.1)*0.28 + vnoise(x*4.31+31.4, z*4.31+17.9)*0.17;
function distSeg(px,pz, ax,az, bx,bz){
  const dx=bx-ax, dz=bz-az, l2=dx*dx+dz*dz;
  let t = l2? ((px-ax)*dx+(pz-az)*dz)/l2 : 0; t = clamp(t,0,1);
  const x=ax+dx*t, z=az+dz*t;
  return Math.hypot(px-x, pz-z);
}

// ------------------------- terrain height field ---------------------------
const OFF=100, N=201;
const H = new Int16Array(N*N);          // top solid voxel y per column
const pathM = new Uint8Array(N*N);      // path mask
const idx = (x,z)=>(x+OFF)+(z+OFF)*N;
const inB = (x,z)=> x>=-OFF && x<=OFF && z>=-OFF && z<=OFF;
const gAt = (x,z)=>{ x=clamp(Math.round(x),-OFF,OFF); z=clamp(Math.round(z),-OFF,OFF); return H[idx(x,z)]; };

// stream centreline (starts inside the pond, exits south)
const STREAM = [[26,4],[22,10],[14,17],[5,24],[0,30],[-4,42],[-6,58],[-6,76],[-8,100]];
function streamDist(x,z){
  let m=1e9;
  for(let i=0;i<STREAM.length-1;i++)
    m = Math.min(m, distSeg(x,z, STREAM[i][0],STREAM[i][1], STREAM[i+1][0],STREAM[i+1][1]));
  return m;
}
const POND = {x:30, z:2};
const TER  = {x:-18, z:-34};            // pagoda terrace centre

for(let z=-OFF; z<=OFF; z++) for(let x=-OFF; x<=OFF; x++){
  let h = 3.5 + fbm(x*0.024, z*0.024)*6;                       // rolling base
  const dEdge = Math.hypot(x,z);
  h += smooth((dEdge-55)/38)*16;                                // rim mountains
  // village plateau (flat building ground)
  const dxv = Math.max(-55-x, x-45, 0), dzv = Math.max(34-z, z-82, 0);
  h = lerp(h, 4, smooth(1 - Math.max(dxv,dzv)/9));
  // pond bowl
  const dP = Math.hypot(x-POND.x, z-POND.z);
  const tP = 1 - smooth((dP-8.5)/4.5);
  if(tP>0) h = Math.min(h, lerp(h, 2, tP));
  // stream carve
  const dS = streamDist(x,z);
  if(dS<4.5){
    const tS = dS<2.2 ? 1 : 1-(dS-2.2)/2.3;
    h = Math.min(h, lerp(h, 2, tS));
  }
  // pagoda terrace hill + plateau
  const dT = Math.hypot(x-TER.x, z-TER.z);
  const tT = 1 - smooth((dT-16)/8);
  if(tT>0) h = Math.max(h, lerp(h, 10, smooth(tT)));
  H[idx(x,z)] = Math.round(h);
}
// stepped stairway carved into the terrace skirt
for(let z=-14; z<=-19; z++){
  const top = z>=-18 ? 10 : (z===-17||z===-16 ? 9 : 8);
  for(let x=-22; x<=-15; x++) H[idx(x,z)] = top;
}
// rice paddy terraces (channels flooded, mud ridges between)
const PADDY = {x0:28, x1:44, z0:62, z1:78};
for(let z=PADDY.z0; z<=PADDY.z1; z++) for(let x=PADDY.x0; x<=PADDY.x1; x++){
  const edge = x===PADDY.x0||x===PADDY.x1||z===PADDY.z0||z===PADDY.z1;
  const chan = !edge && ((z-PADDY.z0)%4===1 || (z-PADDY.z0)%4===2);
  H[idx(x,z)] = edge ? 4 : (chan ? 2 : 3);
}

// path network
const PATHS = [
  [[0,84],[0,40]],[[0,40],[0,24]],                       // main street
  [[0,12],[0,0]],[[0,0],[-8,-8]],[[-8,-8],[-16,-14]],[[-16,-14],[-18,-17]], // to garden
  [[-18,-17],[-18,-24]],                                  // across terrace
  [[-42,52],[32,52]],                                     // cross street
  [[12,44],[12,58]],[[-20,46],[-16,50]],                  // lanes
  [[16,2],[24,-8],[36,-8],[42,2],[38,10],[24,10],[16,2]], // pond loop
  [[6,40],[20,40]],[[0,66],[24,66]],                      // east lanes
];
for(const pts of PATHS) for(let i=0;i<pts.length-1;i++){
  const [ax,az]=pts[i], [bx,bz]=pts[i+1];
  const steps = Math.ceil(Math.max(Math.abs(bx-ax),Math.abs(bz-az)))*2;
  for(let s=0;s<=steps;s++){
    const px = Math.round(lerp(ax,bx,s/steps)), pz = Math.round(lerp(az,bz,s/steps));
    for(let dx=-1; dx<=1; dx++) for(let dz=-1; dz<=1; dz++){
      const x=px+dx, z=pz+dz;
      if(!inB(x,z)) continue;
      if(Math.abs(dx)+Math.abs(dz)===2 && hash2(x*3,z*3)<0.5) continue;
      if(gAt(x,z)>=3) pathM[idx(x,z)] = 1;                // never stamp open water
    }
  }
}

// ------------------------- voxel store ------------------------------------
const V = new Map();
const KEY = (x,y,z)=>((x+128)*256+(z+128))*256+(y+64);
function jitC(hex, amt){
  let r=hex>>16&255, g=hex>>8&255, b=hex&255;
  const d=(Math.random()-0.5)*2*amt;
  r+=d; g+=d; b+=d;
  r+=(Math.random()-0.5)*5; g+=(Math.random()-0.5)*5; b+=(Math.random()-0.5)*5;
  return (clamp(r|0,0,255)<<16)|(clamp(g|0,0,255)<<8)|clamp(b|0,0,255);
}
function set(x,y,z,c,j=4){ if(y<0||y>120||!inB(x,z)) return; V.set(KEY(x,y,z), jitC(c,j)); }
function box(x0,y0,z0,x1,y1,z1,c,j){
  for(let y=y0;y<=y1;y++) for(let z=z0;z<=z1;z++) for(let x=x0;x<=x1;x++) set(x,y,z,c,j);
}
function ring(cx,cz,y,h,c,j){ for(let i=-h;i<=h;i++){ set(cx+i,y,cz-h,c,j); set(cx+i,y,cz+h,c,j); set(cx-h,y,cz+i,c,j); set(cx+h,y,cz+i,c,j); } }
function sq(cx,cz,y,h,c,j){ for(let z=-h;z<=h;z++) for(let x=-h;x<=h;x++) set(cx+x,y,cz+z,c,j); }
function disc(cx,cz,y,r,c,j){ for(let z=-r;z<=r;z++) for(let x=-r;x<=r;x++) if(x*x+z*z<=r*r+r*0.5) set(cx+x,y,cz+z,c,j); }
function blob(cx,cy,cz,r,c,j){
  const R=Math.ceil(r);
  for(let dy=-R;dy<=R;dy++) for(let dz=-R;dz<=R;dz++) for(let dx=-R;dx<=R;dx++){
    const d2 = dx*dx+dy*dy+dz*dz;
    if(d2 <= r*r*(0.82+hash2(cx+dx*7+dy*3, cz+dz*5+dy)*0.34)) set(cx+dx, cy+dy, cz+dz, c, j);
  }
}

// ------------------------- palette ----------------------------------------
const C = {
  grass1:0x5fae4a, grass2:0x7ac25a, grassDry:0x9db35a, dirt:0x7c5433, stone:0x8b8d93,
  sand:0xd6c493, pebble:0x9d9176, gravel:0xccc09b, gravel2:0xa89a80,
  water:0x2f8fd6,
  red:0xc8402f, cream:0xf2e7cf, roofI:0x3b556e, roofEdge:0x57809e, gold:0xe8b23a,
  woodD:0x6e4527, wood:0x8a6440, woodL:0xa87b4e,
  pink1:0xf7a8c4, pink2:0xf39ac0, pink3:0xfbc9dc, pinkW:0xfadfe8,
  pine1:0x2e6e46, pine2:0x3c8a55, leaf1:0x4c9e4c, leaf2:0x6fbd52, maple:0xd96a2b, maple2:0xe08a3a,
  glow:0xffd489, win:0x46352c, door:0x4a2f1d,
};

// ------------------------- terrain voxels ---------------------------------
const waterCells = [];
for(let z=-OFF; z<=OFF; z++) for(let x=-OFF; x<=OFF; x++){
  const h = H[idx(x,z)];
  for(let y=0; y<=h; y++){
    let c;
    if(y===0) c = 0x54545b;
    else if(y===h){
      const onTerrace = Math.abs(x-TER.x)<=15 && Math.abs(z-TER.z)<=15;
      if(onTerrace){                                             // stone plaza, checker slabs
        c = ((x+z)&1) ? 0x9aa1a8 : 0x8a9198;
        if(Math.random()<0.05) c = 0x6f9459;
      }
      else if(h<=2) c = Math.random()<0.5 ? C.sand : C.pebble;   // pond / stream bed
      else if(pathM[idx(x,z)]) c = Math.random()<0.13 ? C.gravel2 : C.gravel;
      else if(h>=20) c = 0xedf1f4;                               // snow caps
      else if(h>=14) c = Math.random()<0.32 ? 0x7fae62 : 0x8f939a; // rocky slopes
      else if(h<=3)  c = C.sand;                                 // shorelines
      else if(h>=11) c = Math.random()<0.25 ? C.grassDry : 0x4f9440;
      else {
        const n = fbm(x*0.15+40, z*0.15+9);
        c = n>0.55 ? C.grass2 : (n<0.42 ? C.grass1 : 0x6cb850);
      }
      set(x,y,z,c, pathM[idx(x,z)]?7:10);
    }
    else if(y > h-3) set(x,y,z,C.dirt,9);
    else set(x,y,z,C.stone,8);
  }
  if(h<=2){ waterCells.push(x,3,z); set(x,h,z,C.sand,5); }       // register water + sandy bed
}
// rice sprouts standing in the paddies
for(let z=PADDY.z0; z<=PADDY.z1; z++) for(let x=PADDY.x0; x<=PADDY.x1; x++){
  if(gAt(x,z)===2 && x%2===0) set(x,4,z,0x569b3f,10);
}

// ------------------------- the pagoda -------------------------------------
const PX=TER.x, PZ=TER.z;
sq(PX,PZ,11,12,C.woodD,4);                       // timber floor of tier 1
const HB=[11,9,8,6,5];                           // body half-widths per tier
function pagodaRoof(cx,cz,y,hb){
  const e=hb+3;
  ring(cx,cz,y,   e,  C.roofEdge,5); ring(cx,cz,y, e-1,C.roofI,5);      // eaves
  sq (cx,cz,y+1, e-1, C.roofI,5);  ring(cx,cz,y+1,e-1,C.roofEdge,5);
  sq (cx,cz,y+2, e-2, C.roofI,5);  ring(cx,cz,y+2,e-2,C.roofEdge,5);
  sq (cx,cz,y+3, hb,  C.roofI,5);  ring(cx,cz,y+3,hb, C.roofEdge,5);
  for(const sx of [-1,1]) for(const sz of [-1,1]){                       // upswept corner tips
    set(cx+sx*e,       y,   cz+sz*(e+1), C.roofEdge,4);
    set(cx+sx*(e+1),   y,   cz+sz*e,     C.roofEdge,4);
    set(cx+sx*(e+1),   y+1, cz+sz*(e+1), C.gold,4);                      // golden tip
    set(cx+sx*(e+1),   y+2, cz+sz*(e+1), C.red,4);                       // bell
  }
}
function pagodaBody(cx,cz,y0,hb,hgt,tier){
  for(let y=y0;y<y0+hgt;y++){
    for(let i=-hb;i<=hb;i++){
      for(const [wx,wz] of [[cx+i,cz-hb],[cx+i,cz+hb],[cx-hb,cz+i],[cx+hb,cz+i]]){
        const onX = (wz===cz-hb||wz===cz+hb);
        const a = onX ? wx-cx : wz-cz;
        let c = C.cream, j = 5;
        if(Math.abs(a)===hb){ c=C.red; j=6; }                            // corner posts
        else if(y===y0+hgt-1){ c=C.woodD; j=3; }                         // head beam
        else if(tier===1 && !onX && Math.abs(a)<=1 && y<y0+4){ c=0x6b1d12; j=3; } // door
        else if(y>y0+1 && y<y0+5 && Math.abs(a)<=1){
          c = (a===0 && y===y0+3) ? C.glow : C.win; j=3;                 // windows
        }
        set(wx,y,wz,c,j);
      }
    }
  }
  // balcony just outside the walls, on the roof slab below
  ring(cx,cz,y0-1,hb+1,C.woodD,4);
  for(let i=-hb-1;i<=hb+1;i+=2){
    set(cx+i, y0, cz-hb-1, C.woodD,3); set(cx+i, y0, cz+hb+1, C.woodD,3);
    set(cx-hb-1, y0, cz+i, C.woodD,3); set(cx+hb-0+1, y0, cz+i, C.woodD,3);
  }
}
let y0 = 12;
for(let t=0; t<5; t++){
  pagodaBody(PX,PZ,y0,HB[t],6,t+1);
  pagodaRoof(PX,PZ,y0+6,HB[t]);
  y0 += 10;
}
// sacred spire
for(let y=62;y<=70;y++) set(PX,y,PZ,C.gold,6);
for(const ry of [63,65,67]) sq(PX,PZ,ry,1,C.gold,4);
blob(PX,71,PZ,1.6,0xf5cf4f,5);
// terrace parapet with a gap at the stairway
for(let i=-15;i<=15;i++){
  for(const [px2,pz2] of [[PX+i,PZ-15],[PX+i,PZ+15],[PX-15,PZ+i],[PX+15,PZ+i]]){
    if(pz2===PZ+15 && Math.abs(px2-PX)<=4) continue;
    if(i%2===0) set(px2,11,pz2,0x9aa1a8,5);
  }
}
// sand garden with raked lines, on a corner of the plaza
for(let z=-45;z<=-39;z++) for(let x=-30;x<=-24;x++)
  set(x,10,z, z%2===0 ? 0xe8e6df : 0xdcd9d0, 3);
box(-29,11,-44,-29,11,-44,0x777c83,4); box(-28,11,-44,-28,11,-44,0x8a8f96,4);
box(-26,11,-42,-26,12,-42,0x6e737a,4);

// ------------------------- torii gate -------------------------------------
(function(){
  const zx=6, zc=10, g=gAt(-5,zc);
  for(const sx of [-1,1]){
    const px2 = sx*5;
    box(px2,g+1,zc,px2+sx,g+9,zc,C.red,6);                    // pillars (2 wide)
    set(px2,g,zc,0x2b2320,3); set(px2+sx,g,zc,0x2b2320,3);    // stone bases
  }
  box(-7,g+6,zc,7,g+6,zc,C.red,5);                            // nuki beam
  box(-9,g+9,zc,9,g+9,zc,C.red,5);                            // kasagi
  box(-9,g+10,zc,-7,g+10,zc,0x2b2320,3); box(7,g+10,zc,9,g+10,zc,0x2b2320,3);
  box(0,g+7,zc,0,g+8,zc,0x2b2320,3); set(0,g+8,zc,C.gold,4);  // central plaque
})();

// ------------------------- stone lanterns & benches -----------------------
function lantern(x,z){
  const g=gAt(x,z);
  box(x,g+1,z,x+1,g+1,z+1,0x9aa0a6,5);
  set(x,g+2,z,0x8a9096,4); set(x,g+3,z,0x8a9096,4);
  sq(x,g+4,z,1,0x9aa0a6,4);
  sq(x,g+5,z,1,0x55575c,3); set(x,g+5,z,C.glow,3);
  sq(x,g+6,z,1,0x55575c,3); set(x,g+7,z,0x55575c,3);
}
[[4,4],[-4,4],[4,-8],[-4,-8],[22,2],[38,6],[PX-15,PZ+13],[PX+15,PZ+13],[4,56],[4,48]]
  .forEach(p=>lantern(p[0],p[1]));
function bench(x,z){
  const g=gAt(x,z);
  set(x,g+1,z,C.wood,4); set(x+1,g+1,z,C.wood,4);
  set(x,g+2,z,C.woodD,3); set(x+1,g+2,z,C.woodD,3);
}
bench(18,6); bench(3,38); bench(-20,4);

// ------------------------- bridges ----------------------------------------
// arched bridge over the stream on the garden approach
for(let z=24; z<=35; z++){
  const t=(z-24)/11, y = 5 + Math.round(Math.sin(Math.PI*t)*2.6);
  for(let x=-2; x<=2; x++){
    set(x,y,z,C.woodL,6);
    const g=gAt(x,z);
    for(let yy=g+1; yy<y; yy++) set(x,yy,z,0x77726c,6);      // stone footings
    if(x===-2||x===2){
      set(x,y+1,z,C.woodD,4);
      if(z%3===0) set(x,y+2,z,C.woodD,4);
    }
  }
}
// flat plank bridge on the village cross street
for(let x=-8; x<=-4; x++) for(let z=51; z<=53; z++) set(x,4,z,C.woodL,6);
[[-8,51],[-8,53],[-4,51],[-4,53]].forEach(p=>set(p[0],5,p[1],C.woodD,4));

// ------------------------- fences ------------------------------------------
function fence(x0,z0,x1,z1){
  const dx=Math.sign(x1-x0), dz=Math.sign(z1-z0), len=Math.max(Math.abs(x1-x0),Math.abs(z1-z0));
  for(let i=0;i<=len;i++){
    const x=x0+dx*i, z=z0+dz*i, g=gAt(x,z);
    set(x,g+2,z,C.woodD,4);
    if(i%3===0) set(x,g+1,z,C.woodD,3);
  }
}
fence(6,-2,6,-14); fence(-27,-13,-23,-13); fence(-14,-13,-9,-13);
fence(18,38,26,38); fence(-24,64,-24,72); fence(8,80,18,80);

// ------------------------- village houses ----------------------------------
const reserved = [];
const chimneys = [];
function house(cx,cz,w,d,wall,roofC,opts={}){
  const hw=w>>1, hd=d>>1;
  const g=gAt(cx,cz), y0h=g+1, wh=opts.wh||4;
  const x0=cx-hw, x1=cx+hw, z0=cz-hd, z1=cz+hd;
  reserved.push([x0-2,z0-2,x1+2,z1+2]);
  for(let y=y0h-2; y<y0h+wh; y++){
    const top = y===y0h+wh-1;
    for(let z=z0; z<=z1; z++) for(let x=x0; x<=x1; x++){
      if(x!==x0 && x!==x1 && z!==z0 && z!==z1) continue;
      const corner = (x===x0||x===x1)&&(z===z0||z===z1);
      let c = wall, j=5;
      if(corner || (x-x0)%4===0&&(z===z0||z===z1) || (z-z0)%4===0&&(x===x0||x===x1)){ c=C.woodD; j=4; }
      if(top && !corner){ c=C.woodD; j=3; }
      set(x,y,z,c,j);
    }
  }
  // gabled roof, ridge along the longer axis, one-voxel eaves overhang
  const yR=y0h+wh, alongX = w>=d;
  let ridgeEnds;
  if(alongX){
    let i=0;
    for(let zh=hd+1; zh>=1; zh--,i++){
      for(let x=x0-1; x<=x1+1; x++) for(let z=cz-zh; z<=cz+zh; z++)
        set(x,yR+i,z, (z===cz-zh||z===cz+zh)? C.roofEdge : roofC, 5);
    }
    for(let x=x0; x<=x1; x++){ set(x,yR+hd+1,cz,0x3f2e20,4); }
    ridgeEnds=[[x0-1,yR-1,z0],[x1+1,yR-1,z1]];
  } else {
    let i=0;
    for(let xh=hw+1; xh>=1; xh--,i++){
      for(let z=z0-1; z<=z1+1; z++) for(let x=cx-xh; x<=cx+xh; x++)
        set(x,yR+i,z, (x===cx-xh||x===cx+xh)? C.roofEdge : roofC, 5);
    }
    for(let z=z0; z<=z1; z++){ set(cx,yR+hw+1,z,0x3f2e20,4); }
  }
  // door + windows (door faces the main street by default)
  const side = opts.door||'W';
  const dz2 = side==='N'?z0 : side==='S'?z1 : 0;
  const dx2 = side==='W'?x0 : side==='E'?x1 : 0;
  if(side==='N'||side==='S'){
    set(cx,y0h,dz2,C.door,3); set(cx,y0h+1,dz2,C.door,3);
    set(cx-1,y0h+2,dz2,0xd94430,5); set(cx+1,y0h+2,dz2,0xd94430,5);  // hanging lanterns
    for(const off of [-3,3]) if(cx+off>x0&&cx+off<x1){
      set(cx+off,y0h+1,dz2,C.glow,4); set(cx+off,y0h+2,dz2,C.win,3);
    }
  } else {
    set(dx2,y0h,cz,C.door,3); set(dx2,y0h+1,cz,C.door,3);
    set(dx2,y0h+2,cz-1,0xd94430,5); set(dx2,y0h+2,cz+1,0xd94430,5);
    for(const off of [-3,3]) if(cz+off>z0&&cz+off<z1){
      set(dx2,y0h+1,cz+off,C.glow,4); set(dx2,y0h+2,cz+off,C.win,3);
    }
  }
  for(const zz of [z0,z1]) for(const off of [-3,3]) if(cx+off>x0&&cx+off<x1){
    set(cx+off,y0h+1,zz,C.glow,4);
  }
  if(opts.wh===7){  // two-storey band + upper windows
    for(let z=z0;z<=z1;z++) for(let x=x0;x<=x1;x++)
      if(x===x0||x===x1||z===z0||z===z1) set(x,y0h+3,z,C.woodD,3);
    for(const zz of [z0,z1]) for(const off of [-3,3]) set(cx+off,y0h+5,zz,C.glow,4);
  }
  if(opts.chimney){
    const chx = x1-1;
    for(let y=yR; y<=yR+hd+2; y++) set(chx,y,cz,0x8b8f96,5);
    set(chx,yR+hd+2,cz,0x55575c,3);
    chimneys.push([chx, yR+hd+3, cz]);
  }
  // dirt path stub from the door toward the nearest street
  let tx=0, tz=0;
  if(side==='W'){ tx=-1; } else if(side==='E'){ tx=1; } else { tz = side==='N'?-1:1; }
  let px3 = dx2||cx, pz3 = dz2||cz;
  for(let i=0;i<6;i++){
    px3+=tx*2; pz3+=tz*2;
    if(!inB(px3,pz3) || gAt(px3,pz3)<3) break;
    pathM[idx(px3,pz3)]=1; pathM[idx(px3+ (tz?1:0), pz3+(tx?1:0))]=1;
  }
}
house(-11,41, 8,7, 0xf1e8d2, 0xb3592f, {door:'E', chimney:true});
house( 11,43, 9,8, 0xece4cf, 0x64707d, {door:'W'});
house(-16,58, 8,8, 0xf1e8d2, 0x5e7d54, {door:'E'});
house( 14,62,10,9, 0xe7dcc0, 0xb3592f, {door:'W', wh:7, chimney:true});   // the inn
house(-32,46, 8,7, 0xece4cf, 0x64707d, {door:'E'});
house( 30,44, 9,7, 0xf1e8d2, 0x5e7d54, {door:'W', chimney:true});
house(-34,68, 9,8, 0xece4cf, 0xb3592f, {door:'E', chimney:true});
house( 20,74, 8,7, 0xf1e8d2, 0x64707d, {door:'W'});
house(-14,74, 8,7, 0xf1e8d2, 0x64707d, {door:'E'});
house( 36,56, 7,6, 0x8a6440, 0x7a5230, {door:'W'});                        // farmer's hut

// ------------------------- well, stalls, clutter ---------------------------
(function well(x,z){
  const g=gAt(x,z);
  for(let dz=-1;dz<=1;dz++) for(let dx=-1;dx<=1;dx++){
    if(dx||dz){ set(x+dx,g+1,z+dz,0x8a9096,5); set(x+dx,g+2,z+dz,0x8a9096,5); }
    else set(x,g+1,z,0x2f8fd6,4);
  }
  set(x,g+3,z-1,C.woodD,3); set(x,g+3,z+1,C.woodD,3);
  set(x,g+4,z-1,C.woodD,3); set(x,g+4,z+1,C.woodD,3);
  for(let dz=-2;dz<=2;dz++) for(let dx=-1;dx<=1;dx++)
    set(x+dx,g+5,z+dz,C.woodD,4);
  set(x,g+4,z,0x9a8c74,4); set(x,g+3,z,0x6e4527,4);   // rope & bucket
})(5,55);

function stall(cx,cz,c1,c2){
  const g=gAt(cx,cz);
  for(const [dx,dz] of [[-2,-1],[2,-1],[-2,1],[2,1]])
    for(let y=g+1;y<=g+3;y++) set(cx+dx,y,cz+dz,C.woodD,3);
  for(let dx=-3;dx<=3;dx++) for(let dz=-2;dz<=2;dz++)
    set(cx+dx,g+4,cz+dz, (dx+9)%2? c1:c2, 4);
  for(let dx=-3;dx<=3;dx++) for(let dz=-1;dz<=1;dz++) set(cx+dx,g+2,cz+dz,C.woodL,4);
  const goods=[0xff8c2e,0xe84a4a,0xf5d442];
  for(let i=0;i<3;i++){
    const gx=cx-2+i*2, gz=cz+(i%2?1:-1);
    set(gx,g+3,gz,goods[i],6); set(gx+ (i%2?-1:1),g+3,gz,goods[i],6);
  }
}
stall(-1,47, 0xd94430, 0xf2e7cf);
stall( 6,58, 0x2e6e46, 0xf2e7cf);

// crates, barrels, clothesline
box(9,5,53,9,5,53,0xb08a4a,6); box(10,5,53,10,6,53,0xa37c3f,6); box(9,5,54,9,5,54,0xb08a4a,6);
set(-3,5,62,0x8a5a30,5); set(-3,6,62,0x6e4527,4); set(-2,5,62,0x8a5a30,5);
set(24,5,58,0x8a5a30,5); set(25,5,58,0x8a5a30,5); set(25,6,58,0x6e4527,4);
(function clothes(x0,z,x1){
  const g=gAt(x0,z);
  set(x0,g+1,z,C.woodD,3); set(x0,g+2,z,C.woodD,3); set(x0,g+3,z,C.woodD,3);
  set(x1,g+1,z,C.woodD,3); set(x1,g+2,z,C.woodD,3); set(x1,g+3,z,C.woodD,3);
  const cloth=[0xe8b4c8,0x9ec4e0,0xf2ede0,0xc9a3d6];
  for(let x=x0+1;x<x1;x++){
    set(x,g+3,z,0x8a8378,4);
    if(x%2===0){ set(x,g+2,z,cloth[(x>>1)%4],6); }
  }
})(8,37,14);

// ------------------------- trees -------------------------------------------
const treeSpots = [];
function farEnough(x,z,d){ for(const p of treeSpots) if(Math.hypot(p[0]-x,p[1]-z)<d) return false; return true; }
function nearPath(x,z){ for(let dz=-2;dz<=2;dz++) for(let dx=-2;dx<=2;dx++){ const ax=x+dx,az=z+dz; if(inB(ax,az)&&pathM[idx(ax,az)]) return true; } return false; }
function isReserved(x,z){ for(const r of reserved) if(x>=r[0]&&x<=r[2]&&z>=r[1]&&z<=r[3]) return true; return false; }

function cherry(x,z,white){
  const g=gAt(x,z); if(g<4) return;
  const th=4+Math.floor(Math.random()*3);
  for(let y=g+1;y<=g+th;y++) set(x,y,z,0x6b4a34,7);
  const p1=white?C.pinkW:C.pink1, p2=white?C.pink3:C.pink2, p3=C.pink3;
  blob(x,g+th+2,z, 3.1+Math.random(), p1, 9);
  blob(x+2,g+th+1,z+1, 2.1, p2, 9);
  blob(x-2,g+th+2,z-1, 2.2, p3, 9);
  if(Math.random()<0.6) blob(x+1,g+th+3,z-2, 1.8, p2, 9);
  for(let i=0;i<14;i++){                                 // fallen petals on the grass
    const px4=x+Math.round((Math.random()-0.5)*8), pz4=z+Math.round((Math.random()-0.5)*8);
    if(inB(px4,pz4)&&gAt(px4,pz4)>=3&&!pathM[idx(px4,pz4)]) set(px4,gAt(px4,pz4)+1,pz4, C.pink3, 8);
  }
  treeSpots.push([x,z]);
}
function greenTree(x,z){
  const g=gAt(x,z); if(g<4) return;
  const th=3+Math.floor(Math.random()*2);
  for(let y=g+1;y<=g+th;y++) set(x,y,z,0x6b4a34,7);
  blob(x,g+th+2,z, 3.0, C.leaf1, 12);
  blob(x+2,g+th+1,z+2, 2.0, C.leaf2, 12);
  blob(x-2,g+th+3,z-1, 2.0, C.leaf2, 12);
  if(Math.random()<0.35) blob(x-1,g+th+1,z+2, 1.5, 0xe14b4b, 8);   // fruit
  treeSpots.push([x,z]);
}
function pine(x,z){
  const g=gAt(x,z); if(g<4) return;
  const th=3+Math.floor(Math.random()*3);
  for(let y=g+1;y<=g+th;y++) set(x,y,z,0x5c3f2b,6);
  let y=g+th+1;
  for(const r of [3,3,2,2,1,1,0]){
    if(r===0){ set(x,y,z,C.pine1,8); break; }
    disc(x,z,y,r, Math.random()<0.5?C.pine1:C.pine2, 10);
    y++;
  }
  treeSpots.push([x,z]);
}
function maple(x,z){
  const g=gAt(x,z); if(g<4) return;
  const th=3+Math.floor(Math.random()*2);
  for(let y=g+1;y<=g+th;y++) set(x,y,z,0x5c3f2b,6);
  blob(x,g+th+2,z, 2.8, C.maple, 12);
  blob(x+2,g+th+3,z, 1.8, C.maple2, 12);
  treeSpots.push([x,z]);
}
function bush(x,z){
  const g=gAt(x,z); if(g<3) return;
  blob(x,g+1,z, 1.6, C.leaf1, 12);
  if(Math.random()<0.5) set(x+1,g+2,z, 0xf5d442, 8);
}
function flowerPatch(x,z){
  const cols=[0xff5b5b,0xffd93d,0xffffff,0xff9ff3,0xf39c3d];
  for(let i=0;i<10;i++){
    const fx=x+Math.round((Math.random()-0.5)*6), fz=z+Math.round((Math.random()-0.5)*6);
    if(!inB(fx,fz)) continue;
    const g=gAt(fx,fz);
    if(g>=4 && !pathM[idx(fx,fz)]){ set(fx,g+1,fz,cols[Math.floor(Math.random()*5)],10); set(fx,g,fz+0,0,0); set(fx,g,fz, undefined); }
  }
}
// hand-placed cherries: avenue along the approach + pond ring
for(const zz of [16,22,28]){ cherry(-6,zz); cherry(6,zz); }
for(const zz of [-2,-7,-12]){ cherry(7,zz); cherry(-7,zz,false); }
[[16,-8],[42,4],[38,-8],[22,-10],[45,-2]].forEach(p=>cherry(p[0],p[1], Math.random()<0.3));
cherry(-24,0); cherry(-12,4,true); cherry(-2,-16);
maple(-30,-22); maple(-6,-22); maple(46,14); maple(-40,20);
// scattered woodland
let tries=0;
while(treeSpots.length<115 && tries<5000){
  tries++;
  const x = -97+Math.floor(Math.random()*194), z = -97+Math.floor(Math.random()*194);
  const h = gAt(x,z);
  if(h<4||h>13) continue;
  if(Math.hypot(x-TER.x,z-TER.z)<26) continue;
  if(Math.hypot(x-POND.x,z-POND.z)<15) continue;
  if(isReserved(x,z) || nearPath(x,z) || !farEnough(x,z,5.5)) continue;
  if(Math.abs(x)<5 && z>32 && z<86) continue;
  if(Math.abs(z-52)<5 && x>-44 && x<34) continue;
  const dP=Math.hypot(x-POND.x,z-POND.z);
  const r=Math.random();
  if(dP<30) r<0.6?cherry(x,z):r<0.85?greenTree(x,z):bush(x,z);
  else if(h>=10) r<0.75?pine(x,z):greenTree(x,z);
  else if(z>-30&&z<30&&Math.abs(x)<45) r<0.4?cherry(x,z):r<0.75?greenTree(x,z):r<0.9?bush(x,z):maple(x,z);
  else r<0.55?greenTree(x,z):r<0.85?pine(x,z):bush(x,z);
}
// flower patches + rocks
for(const p of [[4,-2],[-10,2],[20,12],[34,-6],[2,34],[-8,44],[16,68],[34,50]])
  flowerPatch(p[0],p[1]);
for(const p of [[20,-2],[40,8],[24,12],[-45,-20],[50,30],[-52,10]]){
  const g=gAt(p[0],p[1]); if(g<3) continue;
  blob(p[0],g,p[1],1.4+Math.random(), 0x8a8f96, 8);
}

// ------------------------- villagers (static, in the voxel map) ------------
const SKIN=[0xf2c99b,0xdda577,0xb57a4a], HAIR=[0x2b2b2b,0x191410,0x4a3220,0x66503a];
const SHIRT=[0xd94f3d,0x3d7dd9,0xe9a13b,0x7a4fd9,0x2fa38a,0x40495a,0xc25b8a];
const PANT=[0x4a4038,0x39465c,0x6e5233];
function pick(a){ return a[Math.floor(Math.random()*a.length)]; }
function person(x,z,pose){
  const g=gAt(x,z); if(g<3) return;
  const sk=pick(SKIN), sh=pick(SHIRT), pa=pick(PANT), hr=pick(HAIR);
  if(pose==='sit'){
    set(x,g+1,z,pa,5); set(x,g+2,z,pa,5);
    set(x,g+3,z,sh,5); set(x,g+4,z,sk,5); set(x,g+5,z,hr,5);
  } else {
    set(x,g+1,z,pa,5); set(x,g+2,z,sh,5); set(x,g+3,z,sh,5);
    set(x,g+4,z,sk,5); set(x,g+5,z,hr,5);
    if(pose==='wave'){ set(x+1,g+3,z,sh,5); set(x+1,g+4,z,sk,5); }
    if(Math.random()<0.3) set(x,g+5,z,0xd9b45a,6);   // straw hat over hair
  }
}
person(6,54,'stand'); person(3,52,'sit'); person(-4,49,'wave');
person(8,45,'stand'); person(-12,45,'wave'); person(-6,-2,'stand'); person(-9,-5,'stand');
person(5,2,'stand'); person(-24,-22,'stand'); person(-10,-22,'stand');
person(22,12,'sit'); person(26,64,'stand'); person(36,70,'wave'); person(0,60,'stand');
person(-30,60,'sit'); person(12,52,'stand');

// ------------------------- build the main instanced mesh -------------------
const solid = (x,y,z)=> y<0 ? true : (inB(x,z)&&y<=120 ? V.has(KEY(x,y,z)) : false);
const keepKeys = [];
for(const [k] of V){
  const y = (k%256)-64, rest=(k-y-64)/256;
  const z = (rest%256)-128, x = (rest-z-128)/256;
  if(z===128) continue;
  if(!solid(x+1,y,z)||!solid(x-1,y,z)||!solid(x,y,z+1)||!solid(x,y,z-1)||!solid(x,y+1,z)||!solid(x,y-1,z))
    keepKeys.push(k);
}
const _col = new THREE.Color();
const voxelGeo = new THREE.BoxGeometry(1,1,1);
const voxelMat = new THREE.MeshLambertMaterial({ color:0xffffff });
const world = new THREE.InstancedMesh(voxelGeo, voxelMat, keepKeys.length);
world.castShadow = world.receiveShadow = true;
world.frustumCulled = false;
const _m = new THREE.Matrix4();
for(let i=0;i<keepKeys.length;i++){
  const k=keepKeys[i];
  const y=(k%256)-64, rest=(k-y-64)/256;
  const z=(rest%256)-128, x=(rest-z-128)/256;
  _m.makeTranslation(x,y,z);
  world.setMatrixAt(i,_m);
  world.setColorAt(i, _col.setHex(V.get(k)).convertSRGBToLinear());
}
scene.add(world);

// ------------------------- water (animated, transparent) -------------------
const waterGeo = new THREE.BoxGeometry(1,0.9,1);
const waterMat = new THREE.MeshLambertMaterial({ color:0xffffff, transparent:true, opacity:0.62 });
const water = new THREE.InstancedMesh(waterGeo, waterMat, waterCells.length/3);
water.receiveShadow = true; water.frustumCulled = false;
water.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
const wBase = new Float32Array(waterCells.length);
for(let i=0;i<waterCells.length;i+=3){
  const x=waterCells[i], y=waterCells[i+1], z=waterCells[i+2];
  wBase[i]=x; wBase[i+1]=y; wBase[i+2]=z;
  water.setColorAt(i/3, _col.setHex(jitC(C.water,12)).convertSRGBToLinear());
}
scene.add(water);

// ------------------------- petals ------------------------------------------
const PET_N = 650;
const petGeo = new THREE.BoxGeometry(0.42,0.1,0.42);
const petMat = new THREE.MeshLambertMaterial({ color:0xffffff });
const petals = new THREE.InstancedMesh(petGeo, petMat, PET_N);
petals.frustumCulled = false;
petals.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
const pet = { x:new Float32Array(PET_N), y:new Float32Array(PET_N), z:new Float32Array(PET_N),
              s:new Float32Array(PET_N), ph:new Float32Array(PET_N), r1:new Float32Array(PET_N), r2:new Float32Array(PET_N) };
const PINKS=[0xf7a8c4,0xf39ac0,0xfbc9dc,0xfadfe8];
function petalSpawn(i, top){
  pet.x[i] = -45+Math.random()*100; pet.z[i] = -35+Math.random()*62;
  pet.y[i] = top ? 26+Math.random()*16 : pet.y[i];
  pet.s[i] = 0.9+Math.random()*1.6; pet.ph[i] = Math.random()*Math.PI*2;
  pet.r1[i]= (Math.random()-0.5)*3; pet.r2[i]= (Math.random()-0.5)*3;
}
for(let i=0;i<PET_N;i++){ petalSpawn(i,true); pet.y[i]=Math.random()*40+2;
  petals.setColorAt(i,_col.setHex(PINKS[i%4]).convertSRGBToLinear()); }
scene.add(petals);

// ------------------------- chimney smoke -----------------------------------
const SM_N = chimneys.length*5;
const smokeGeo = new THREE.BoxGeometry(1,1,1);
const smokeMat = new THREE.MeshLambertMaterial({ color:0xffffff });
const smoke = new THREE.InstancedMesh(smokeGeo, smokeMat, Math.max(SM_N,1));
smoke.frustumCulled = false;
smoke.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
for(let i=0;i<SM_N;i++) smoke.setColorAt(i,_col.setHex(0xf2f2f2).convertSRGBToLinear());
scene.add(smoke);
const smokeA = new THREE.Color(0xf2f2f2).convertSRGBToLinear();
const smokeB = new THREE.Color(0xdcebee).convertSRGBToLinear();

// ------------------------- clouds ------------------------------------------
const clouds = [];
for(let c=0;c<5;c++){
  const arr=[];
  const cx=-120+c*60+Math.random()*20, cy=58+Math.random()*16, cz=-90+Math.random()*160;
  const nb=2+Math.floor(Math.random()*2);
  for(let b=0;b<nb;b++){
    const bx=cx+(Math.random()-0.5)*22, bz=cz+(Math.random()-0.5)*14, r=4+Math.random()*4;
    const R=Math.ceil(r);
    for(let dy=-3;dy<=3;dy++) for(let dz=-R;dz<=R;dz++) for(let dx=-R;dx<=R;dx++)
      if(dx*dx+(dy/0.42)*(dy/0.42)+dz*dz <= r*r) arr.push([Math.round(bx+dx), Math.round(cy+dy*0.5), Math.round(bz+dz)]);
  }
  const m = new THREE.InstancedMesh(voxelGeo, new THREE.MeshLambertMaterial({color:0xfbfdfg||0xfbfdfe}), arr.length);
  m.castShadow = true; m.frustumCulled = false;
  arr.forEach((p,i)=>{ _m.makeTranslation(p[0],p[1],p[2]); m.setMatrixAt(i,_m);
    m.setColorAt(i,_col.setHex(jitC(0xfbfdfe,4)).convertSRGBToLinear()); });
  m.position.set(0,0,0);
  scene.add(m);
  clouds.push({ mesh:m, base:0, vx:1.1+Math.random()*0.9, y:cy });
}

// ------------------------- actors: walkers, birds, koi, ducks --------------
function lam(hex){ return new THREE.MeshLambertMaterial({ color:new THREE.Color(hex).convertSRGBToLinear() }); }
function mkBox(w,h,d,mat,x,y,z,parent){
  const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat);
  m.position.set(x,y,z); m.castShadow=true; parent.add(m); return m;
}
const walkers=[];
function makeWalker(path, loop, hat){
  const g=new THREE.Group();
  const sh=pick(SHIRT), pa=pick(PANT), sk=pick(SKIN), hr=pick(HAIR);
  const mSh=lam(sh), mPa=lam(pa), mSk=lam(sk), mHr=lam(hr);
  const legG=new THREE.BoxGeometry(0.32,0.8,0.32); legG.translate(0,-0.4,0);
  const legL=new THREE.Mesh(legG,mPa); legL.position.set(-0.18,0.8,0); legL.castShadow=true; g.add(legL);
  const legR=new THREE.Mesh(legG,mPa); legR.position.set(0.18,0.8,0); legR.castShadow=true; g.add(legR);
  mkBox(0.78,0.85,0.44,mSh, 0,1.22,0, g);
  const armG=new THREE.BoxGeometry(0.16,0.66,0.16); armG.translate(0,-0.28,0);
  const armL=new THREE.Mesh(armG,mSh); armL.position.set(-0.47,1.6,0); g.add(armL);
  const armR=new THREE.Mesh(armG,mSh); armR.position.set(0.47,1.6,0); g.add(armR);
  mkBox(0.6,0.58,0.56,mSk, 0,1.95,0, g);
  mkBox(0.64,0.16,0.6,mHr, 0,2.3,0, g);
  mkBox(0.64,0.28,0.14,mHr, 0,2.06,-0.26, g);
  mkBox(0.09,0.12,0.05,lam(0x241c16), -0.15,1.98,0.29, g);
  mkBox(0.09,0.12,0.05,lam(0x241c16), 0.15,1.98,0.29, g);
  if(hat){ mkBox(0.72,0.08,0.72,lam(0xd9b45a),0,2.42,0,g); mkBox(0.44,0.16,0.44,lam(0xd9b45a),0,2.5,0,g); }
  scene.add(g);
  walkers.push({ g, path, loop, i:0, dir:1, ph:Math.random()*9,
                 sp:1.4+Math.random()*0.7,
                 x:path[0][0], z:path[0][1] });
}
makeWalker([[2,78],[2,44],[-2,44],[-2,78]], true, false);
makeWalker([[2,8],[2,-2],[-6,-8],[-14,-13]], false, true);
makeWalker([[-3,49],[8,49],[8,56],[-3,56]], true, false);
makeWalker([[18,0],[28,-12],[40,-2],[36,10],[22,10]], true, true);
makeWalker([[16,58],[24,58],[24,74],[16,74]], true, false);

const birds=[];
for(let i=0;i<4;i++){
  const g=new THREE.Group();
  mkBox(0.36,0.26,0.85, lam(0x33302b), 0,0,0, g);
  mkBox(0.26,0.24,0.3, lam(0x33302b), 0,0.12,0.5, g);
  const wgl=new THREE.BoxGeometry(1.25,0.06,0.42); wgl.translate(-0.66,0,0);
  const wgr=new THREE.BoxGeometry(1.25,0.06,0.42); wgr.translate(0.66,0,0);
  const wl=new THREE.Mesh(wgl,lam(0x44403a)); wl.castShadow=true; g.add(wl);
  const wr=new THREE.Mesh(wgr,lam(0x44403a)); wr.castShadow=true; g.add(wr);
  scene.add(g);
  birds.push({ g, wl, wr, cx: i<2?TER.x:(i<3?10:20), cz: i<2?TER.z:(i<3?40:55),
               r: 20+i*6, y: 52+i*5, a: Math.random()*7, sp: 0.25+Math.random()*0.2 });
}
const koi=[];
for(let i=0;i<4;i++){
  const g=new THREE.Group();
  mkBox(0.5,0.26,1.0, lam(i%2?0xff7a26:0xf5f2ea), 0,0,0, g);
  mkBox(0.32,0.18,0.4, lam(i%2?0xf5f2ea:0xff7a26), 0,0.02,-0.65, g);
  scene.add(g);
  koi.push({ g, a:Math.random()*7, r:3.5+i*1.6, sp:0.4+Math.random()*0.3, ph:Math.random()*7 });
}
const ducks=[];
for(let i=0;i<2;i++){
  const g=new THREE.Group();
  mkBox(0.55,0.3,0.75, lam(0xf5f2ea), 0,0,0, g);
  mkBox(0.3,0.3,0.3, lam(0xf5f2ea), 0,0.28,0.42, g);
  mkBox(0.12,0.08,0.18, lam(0xe8973a), 0,0.26,0.62, g);
  scene.add(g);
  ducks.push({ g, a:Math.random()*7, ph:Math.random()*7 });
}

// ------------------------- UI wiring ---------------------------------------
const bOrbit=document.getElementById('bOrbit'), bPetal=document.getElementById('bPetal'), bReset=document.getElementById('bReset');
bOrbit.onclick = ()=>{ controls.autoRotate=!controls.autoRotate; bOrbit.classList.toggle('on',controls.autoRotate); };
bPetal.onclick = ()=>{ petals.visible=!petals.visible; bPetal.classList.toggle('on',petals.visible); };
bReset.onclick = ()=>{ camera.position.copy(CAM_END); controls.target.set(-6,14,-8); };
document.getElementById('stats').innerHTML =
  `<b>${keepKeys.length.toLocaleString()}</b> voxels &nbsp;·&nbsp; <b>${waterCells.length/3}</b> water &nbsp;·&nbsp; <b>${PET_N}</b> petals`;

// ------------------------- animation loop ----------------------------------
const _q=new THREE.Quaternion(), _e=new THREE.Euler(), _s=new THREE.Vector3(1,1,1);
let intro = 0, introDone = false;
renderer.domElement.addEventListener('pointerdown', ()=>{ if(!introDone){ intro=1; } });
const clock = new THREE.Clock();

function animate(){
  requestAnimationFrame(animate);
  const dt = Math.min(clock.getDelta(), 0.05);
  const t = clock.elapsedTime;

  // camera fly-in
  if(!introDone){
    intro = Math.min(1, intro + dt/4.2);
    const e = 1-Math.pow(1-intro,3);
    camera.position.lerpVectors(CAM_START, CAM_END, e);
    camera.lookAt(controls.target);
    if(intro>=1) introDone=true;
  } else controls.update();

  // water shimmer
  for(let i=0;i<waterCells.length;i+=3){
    const x=wBase[i], z=wBase[i+2];
    _m.makeTranslation(x, wBase[i+1] + Math.sin(t*1.7 + x*0.35 + z*0.27)*0.1, z);
    water.setMatrixAt(i/3,_m);
  }
  water.instanceMatrix.needsUpdate = true;

  // petals
  if(petals.visible){
    for(let i=0;i<PET_N;i++){
      pet.y[i] -= pet.s[i]*dt*2.1;
      pet.x[i] += (Math.sin(t*1.3+pet.ph[i])*0.5 + 0.85)*dt*1.6;
      pet.z[i] += Math.cos(t*1.1+pet.ph[i])*dt*0.7;
      const gy = gAt(pet.x[i], pet.z[i]) + 0.6;
      if(pet.y[i] < gy){ petalSpawn(i,true); pet.y[i] = 26+Math.random()*16; }
      _p.set(pet.x[i],pet.y[i],pet.z[i]);
      _e.set(t*pet.r1[i]+pet.ph[i], t*pet.r2[i], pet.ph[i]);
      _q.setFromEuler(_e);
      _m.compose(_p,_q,_s.set(1,1,1));
      petals.setMatrixAt(i,_m);
    }
    petals.instanceMatrix.needsUpdate = true;
  }

  // chimney smoke
  for(let i=0;i<SM_N;i++){
    const ch = chimneys[(i/5)|0];
    const ph = (t*0.14 + (i%5)*0.2) % 1;
    const x = ch[0] + Math.sin(ph*5+i)*0.6 + ph*1.4, y = ch[1] + ph*9, z = ch[2] + Math.cos(ph*4+i)*0.4;
    const sc = 0.55 + ph*2.3;
    _p.set(x,y,z); _q.setFromEuler(_e.set(0,ph*3,0)); _s.set(sc,sc,sc);
    _m.compose(_p,_q,_s);
    smoke.setMatrixAt(i,_m);
    smoke.setColorAt(i, _c2.copy(smokeA).lerp(smokeB, ph*ph));
  }
  smoke.instanceMatrix.needsUpdate = true;
  if(smoke.instanceColor) smoke.instanceColor.needsUpdate = true;

  // clouds
  for(const c of clouds){
    c.base += c.vx*dt;
    if(c.base > 240) c.base = -240;
    c.mesh.position.set(c.base, 0, 0);
  }

  // walkers
  for(const w of walkers){
    const tgt = w.path[w.i];
    const dx = tgt[0]-w.x, dz = tgt[1]-w.z, d = Math.hypot(dx,dz);
    if(d<0.4){
      if(w.loop) w.i = (w.i+1)%w.path.length;
      else { if(w.i+ w.dir >= w.path.length || w.i+w.dir < 0) w.dir*=-1; w.i += w.dir; }
    } else {
      w.x += dx/d*w.sp*dt; w.z += dz/d*w.sp*dt;
      w.g.rotation.y = Math.atan2(dx,dz);
    }
    w.ph += dt*9;
    const gy = gAt(Math.round(w.x), Math.round(w.z)) + 0.5;
    w.g.position.set(w.x, gy + Math.abs(Math.sin(w.ph))*0.05, w.z);
    w.g.rotation.z = Math.sin(w.ph)*0.045;
    w.g.children[0].rotation.x =  Math.sin(w.ph)*0.6;
    w.g.children[1].rotation.x = -Math.sin(w.ph)*0.6;
    w.g.children[3].rotation.x = -Math.sin(w.ph)*0.45;
    w.g.children[4].rotation.x =  Math.sin(w.ph)*0.45;
  }

  // birds
  for(const b of birds){
    b.a += b.sp*dt;
    const x=b.cx+Math.cos(b.a)*b.r, z=b.cz+Math.sin(b.a)*b.r;
    b.g.position.set(x, b.y + Math.sin(t*2+b.r)*1.2, z);
    b.g.rotation.y = -b.a;
    b.g.rotation.z = 0.25;
    const f = Math.sin(t*9+b.r);
    b.wl.rotation.z = f*0.55; b.wr.rotation.z = -f*0.55;
  }

  // koi gliding beneath the pond surface
  for(const k of koi){
    k.a += k.sp*dt;
    k.g.position.set(POND.x+Math.cos(k.a)*k.r, 2.75 + Math.sin(t*1.5+k.ph)*0.08, POND.z+Math.sin(k.a)*k.r);
    k.g.rotation.y = -k.a + Math.PI/2;
    k.g.rotation.z = Math.sin(t*4+k.ph)*0.1;
  }

  // ducks
  ducks.forEach((d,i)=>{
    d.a += dt*0.12;
    d.g.position.set(POND.x-5+Math.cos(d.a)*3+i*1.5, 3.55+Math.sin(t*1.6+d.ph)*0.09, POND.z+4+Math.sin(d.a)*3);
    d.g.rotation.y = -d.a;
  });

  renderer.render(scene, camera);
}
const _p=new THREE.Vector3(), _c2=new THREE.Color();
animate();

addEventListener('resize', ()=>{
  camera.aspect = innerWidth/innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

// fade the loader once the first frames are in
requestAnimationFrame(()=>requestAnimationFrame(()=>{
  document.getElementById('loader').classList.add('hide');
}));
</script>
</body>
</html>

How it's built:

  • Everything is procedural voxels. A ~200×200 height field (value-noise terrain with a rim of mountains, snow caps, a carved stream, pond bowl, flattened village plateau and the pagoda hill) is filled column by column with color-graded strata — grass, dirt, stone, sand beds — then passed through a hidden-face culling pass so only visible voxels reach the GPU. The whole world renders as one InstancedMesh (with per-instance color and slight per-voxel luminance jitter for that hand-placed look), so the scene stays at a handful of draw calls.
  • The pagoda is a real structure: five tiers with vermillion posts, cream walls, lattice-lit windows, balconies, upswept corner tips with golden finials and bells, and a ringed golden spire — on a checkered stone plaza with parapet, carved stairway, and a raked sand garden in one corner.
  • The garden has a red torii gate, an arched footbridge over the stream, stone lanterns, fences, flower patches, ponds ringed by a gravel path, and ~20 cherry trees (hand-placed avenues plus scattered ones) that continuously shed 650 animated petals on the wind.
  • The village lives: ten varied houses with gabled roofs, glowing lattice windows, red door lanterns and smoking chimneys (animated voxel smoke); a well, market stalls with striped awnings, crates, barrels, a clothesline, rice paddies with sprouts, and five villagers actually walking their routes (with swinging legs and arms), plus static folk chatting, waving and sitting around.
  • Ambient life everywhere: shimmering transparent water with koi gliding beneath it, bobbing ducks, four birds circling the pagoda, and drifting voxel clouds that cast real moving shadows across the valley.
  • Interaction: a cinematic fly-in on load, then full orbit/zoom/pan with damping and slow auto-orbit, plus toggles for petals, auto-rotate and a view reset.

Just save it as an .html file and open it — no build step, everything runs locally.

模型输出的 SVG 1模型输出的 SVG 2模型输出的 SVG 3
6.36s
首Token
实际等待 6.62s
39
思考TPS
884s · 34,205 tok
73
输出TPS
247s · 18,039 tok
52,244
总Tokens(官方)
输入 90
计时详情
实际等待(客户端)
6.62s
客户端请求准备
0.01s
本站代理准备
0.00s
上游响应头到达
6.36s
上游首字节到达
6.36s
上游首 Token 到达
6.36s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-28T07:30:37.299Z",
  "transport": "railway",
  "ttftSource": "proxy",
  "clientTtftMs": 6617.299999713898,
  "completionId": "20260928153037f5d4127c428f4644",
  "streamResumes": 25,
  "proxyPrepareMs": 0,
  "proxyRequestId": "etiZaQlYTeO8kWxw9o6EoQ",
  "upstreamStatus": 200,
  "upstreamTtftMs": 6359,
  "clientHeadersMs": 296.69999980926514,
  "clientPrepareMs": 13.099999904632568,
  "upstreamHeadersMs": 6358,
  "upstreamFirstByteMs": 6359,
  "clientFirstContentMs": 890490
}
总用时 1138s · 平均 46 tok/s · 峰值 142 tok/s
小米 MiMo-V2.6 Flash
mimo-v2.6-flash
完成

历史结果 · 2026/9/28 07:53:39

The user wants a single HTML file with a detailed voxel art scene featuring: 1. A pagoda 2. A beautiful garden with trees 3. Cherry blossoms 4. A village with people 5. Detailed, colorful, impressive, varied I'll use Three.js for3D voxel rendering. Let me create an elaborate scene with: - A multi-tiered pagoda with traditional Japanese architecture - Cherry blossom trees with pink petals - Regular trees (green) - A village with houses - People (small voxel characters) - Garden elements (pond, paths, flowers, rocks, lanterns) - Ground with varied terrainLet me plan this carefully and create a comprehensive scene using instanced meshes or individual box geometries for performance. Actually, for performance with lots of voxels, I should use InstancedMesh or merge geometries. Let me use a voxel builder approach where I define voxels and then render them efficiently. Let me think about the structure: 1. **Pagoda**: Multi-tiered (5-7 tiers), traditional East Asian style, with curved eaves 2. **Cherry blossom trees**: Pink/white blossom canopy 3. **Green trees**: Various green shades 4. **Village**: Small traditional houses with curved roofs 5. **People**: Small humanoid voxel characters 6. **Garden**: Paths, pond, lanterns, rocks, flowers 7. **Ground**: Varied terrain I'll create a voxel color palette and build everything voxel by voxel using box geometries merged together. Let me write the code: ```html <!DOCTYPE html> <html> <head> <style> body { margin: 0; overflow: hidden; background: #000; } canvas { display: block; } #info { position: absolute; top: 10px; left: 10px; color: white; font-family: sans-serif; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> // Scene setup... // Voxel builder... // Build pagoda, trees, village, people, garden... </script> </body> </html> ``` Let me think about using BufferGeometry merging for performance. I'll create a system that collects all voxel boxes and merges them into as few draw calls as possible. Actually, for a clean approach, I'll use a voxel system where: - Each voxel is a 1x1x1 cube - I collect voxels with their positions and colors - I create merged BufferGeometries grouped by color (or use vertex colors) Let me use vertex colors approach - merge all box geometries into one BufferGeometry with vertex colors. Here's my approach: ```javascript function createVoxelScene() { const voxels = []; function addVoxel(x, y, z, color, size = 1) { voxels.push({ x, y, z, color, size }); } // Build everything... // Merge all voxels into one geometry // ... } ``` Actually, let me think about this more carefully. Merging thousands of box geometries into a single BufferGeometry with vertex colors is the way to go. Let me plan the coordinates: - Ground level: y = 0 - Pagoda: centered at around (0, 0, 0) - Village: offset to one side - Trees: scattered around - Pond: one area Let me design the pagoda: - 7 tiers - Each tier gets smaller as it goes up - Base tier: ~20x20 voxels - Top tier: ~6x6 voxels - Tier height: ~8 voxels each - Curved roof edges Village: ~5-8 small houses, each ~8x8x6 voxels Trees: Various sizes, 8-15 voxels tall People: ~2-3 voxels tall figures Let me code this up properly. For the pagoda base, I'll make it 20x20. Then each level up: - Level 0 (ground): stone base 22x22x2 - Tier 1: building 16x16x6, roof extends to 22x22 - Tier 2: building 12x12x5, roof extends to 17x17 - Tier 3: building 9x9x5, roof extends to 13x13 - Tier 4: building 7x7x4, roof extends to 10x10 - Tier 5: building 5x5x4, roof extends to 8x8 - Top: spire Roof design with curved edges - I'll taper the roof at the corners. Let me start writing the actual code. I'll use a helper function to add boxes efficiently. ```javascript const geometry = new THREE.BufferGeometry(); // ... merge all boxes with vertex colors ``` Actually, let me use THREE.js's built-in approach. I'll create individual BoxGeometries, translate them, and then merge using BufferGeometryUtils.mergeBufferGeometries() or a custom merge function. Hmm, but BufferGeometryUtils might not be included in the core. Let me use a manual merge approach or include the utilities. Let me write a manual merge function that takes an array of positions and colors and creates a BufferGeometry. Here's my plan for the code structure: ```javascript // Voxel data collection let positions = []; let colors = []; function addVoxel(x, y, z, r, g, b, sx=1, sy=1, sz=1) { // Add a box at (x,y,z) with size (sx,sy,sz) and color (r,g,b) // Push36 vertices (6 faces * 2 triangles * 3 vertices) } // Then create BufferGeometry from positions and colors ``` Let me think about the scale. Each voxel = 1 unit in Three.js. The scene should be maybe 200x200 units. Let me define a color palette first: ```javascript const colors = { // Pagoda pagodaRed: [0.7, 0.1, 0.1], pagodaRoof: [0.2, 0.3, 0.4], pagodaGold: [0.9, 0.7, 0.2], pagodaWall: [0.8, 0.6, 0.4], // Trees trunk: [0.4, 0.25, 0.1], leaf1: [0.1, 0.5, 0.1], leaf2: [0.2, 0.6, 0.2], cherryTrunk: [0.3, 0.2, 0.15], cherryBlossom: [1.0, 0.7, 0.8], cherryBlossom2: [1.0, 0.8, 0.85], // Village houseWood: [0.6, 0.4, 0.2], houseRoof: [0.3, 0.3, 0.35], houseWall: [0.8, 0.7, 0.5], houseRoof2: [0.4, 0.2, 0.1], // Ground grass: [0.2, 0.7, 0.2], grass2: [0.3, 0.75, 0.25], dirt: [0.5, 0.35, 0.2], stone: [0.5, 0.5, 0.5], path: [0.7, 0.6, 0.4], water: [0.2, 0.5, 0.9], sand: [0.9, 0.8, 0.6], // People skin: [0.9, 0.7, 0.5], red: [0.9, 0.2, 0.2], blue: [0.2, 0.3, 0.8], green: [0.2, 0.7, 0.3], yellow: [0.9, 0.8, 0.2], white: [0.9, 0.9, 0.9], // Lanterns lantern: [1.0, 0.8, 0.3], lanternLight: [1.0, 0.9, 0.5], // Flowers flowerRed: [0.9, 0.2, 0.3], flowerYellow: [0.95, 0.9, 0.3], flowerPurple: [0.6, 0.2, 0.7], flowerBlue: [0.3, 0.3, 0.9], // Rocks rock: [0.5, 0.5, 0.55], rock2: [0.6, 0.6, 0.6], // Bridge bridge: [0.6, 0.35, 0.15], bridgeRail: [0.5, 0.3, 0.1], }; ``` Now let me think about building the actual geometry. For merging boxes, I need to generate the vertex data for each box: ```javascript function addBoxToData(px, py, pz, sx, sy, sz, color) { //6 faces of a box, each with 2 triangles = 4 vertices with normals // or 6 vertices per face (no indexing) // Actually, for simplicity, let's just add non-indexed vertices // 6 faces * 2 triangles * 3 vertices = 36 vertices per box} ``` Let me define the box vertices: For a box centered at (cx, cy, cz) with half-sizes (hx, hy, hz): Front face (z = cz + hz): (-hx, -hy, hz), (+hx, -hy, hz), (+hx, +hy, hz) (-hx, -hy, hz), (+hx, +hy, hz), (-hx, +hy, hz) Back face (z = cz - hz): (+hx, -hy, -hz), (-hx, -hy, -hz), (-hx, +hy, -hz) (+hx, -hy, -hz), (-hx, +hy, -hz), (+hx, +hy, -hz) ...etc for all 6 faces This is a lot of data. Let me write it efficiently. Actually, I think a cleaner approach would be to: 1. Collect all box data (position, size, color) 2. Use a loop to generate the merged BufferGeometry Let me write this step by step. Actually, for the number of voxels we're talking about (potentially thousands), let me be smart about it. I'll define helper functions like `addBox(x, y, z, width, height, depth, color)` that adds a box to a collection, then build the geometry from all collected boxes. Let me also think about what makes this impressive: - Detailed pagoda with ornate details - Multiple cherry blossom trees with petals falling (animation?) - A pond with reflection-like colors - Stone paths - Torii gates - A stone bridge - Various flowers - Garden lanterns - Mountains in the background - Clouds - Village houses with different styles - People doing different

Here's a single HTML file with an elaborate voxel art scene featuring a pagoda, garden, cherry blossoms, village, and more:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Voxel Pagoda Garden</title>
<style>
*{margin:0;padding:0}body{overflow:hidden;background:#1a1a2e}canvas{display:block}
#ui{position:absolute;top:18px;left:50%;transform:translateX(-50%);color:rgba(255,255,255,.7);font:600 15px/1.5 'Segoe UI',sans-serif;text-shadow:0 2px 8px rgba(0,0,0,.6);pointer-events:none;letter-spacing:3px;white-space:nowrap}
#hint{position:absolute;bottom:18px;left:50%;transform:translateX(-50%);color:rgba(255,255,255,.3);font:12px sans-serif;text-shadow:0 1px 4px rgba(0,0,0,.6);pointer-events:none}
</style>
</head>
<body>
<div id="ui">⛩ VOXEL PAGODA GARDEN ⛩</div>
<div id="hint">Drag to rotate • Scroll to zoom • Right-click to pan</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
const scene=new THREE.Scene;
scene.background=new THREE.Color(0x87CEEB);
scene.fog=new THREE.FogExp2(0x94B8D4,.0018);
const camera=new THREE.PerspectiveCamera(55,innerWidth/innerHeight,.1,600);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0x667799,.45));
scene.add(new THREE.HemisphereLight(0x88BBFF,0x446633,.35));
const sun=new THREE.DirectionalLight(0xFFF4D6,1.3);
sun.position.set(80,150,60);
sun.castShadow=true;
sun.shadow.mapSize.set(4096,4096);
const sc=sun.shadow.camera;sc.left=-140;sc.right=140;sc.top=140;sc.bottom=-140;sc.near=1;sc.far=400;
sun.shadow.bias=-.001;scene.add(sun);
const fill=new THREE.DirectionalLight(0x8899BB,.3);fill.position.set(-50,80,-40);scene.add(fill);
let theta=.7,phi=.42,radius=140;
const target=new THREE.Vector3(0,18,0);
let mL=false,mR=false,lX=0,lY=0;
function updateCam(){camera.position.set(target.x+radius*Math.cos(phi)*Math.sin(theta),target.y+radius*Math.sin(phi),target.z+radius*Math.cos(phi)*Math.cos(theta));camera.lookAt(target)}
updateCam();
renderer.domElement.onmousedown=e=>{if(e.button==0)mL=true;if(e.button==2)mR=true;lX=e.clientX;lY=e.clientY};
addEventListener('mouseup',()=>{mL=mR=false});
addEventListener('mousemove',e=>{const dx=e.clientX-lX,dy=e.clientY-lY;if(mL){theta-=dx*.005;phi=Math.max(.05,Math.min(1.4,phi+dy*.005));updateCam()}if(mR){const fw=new THREE.Vector3().subVectors(target,camera.position).normalize();const rt=new THREE.Vector3().crossVectors(fw,camera.up).normalize();target.addScaledVector(rt,dx*.12).addScaledVector(camera.up,dy*.12);updateCam()}lX=e.clientX;lY=e.clientY});
renderer.domElement.onwheel=e=>{radius=Math.max(30,Math.min(350,radius+e.deltaY*.08));updateCam()};
renderer.domElement.oncontextmenu=e=>e.preventDefault();
let td=0;
renderer.domElement.ontouchstart=e=>{if(e.touches.length==1){lX=e.touches[0].clientX;lY=e.touches[0].clientY}else if(e.touches.length==2)td=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY)};
renderer.domElement.ontouchmove=e=>{e.preventDefault();if(e.touches.length==1){const dx=e.touches[0].clientX-lX,dy=e.touches[0].clientY-lY;theta-=dx*.005;phi=Math.max(.05,Math.min(1.4,phi+dy*.005));lX=e.touches[0].clientX;lY=e.touches[0].clientY;updateCam()}else if(e.touches.length==2){const d=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);radius=Math.max(30,Math.min(350,radius-(d-td)*.4));td=d;updateCam()}},{passive:false});

const C={
pw:[.7,.12,.12],pw2:[.88,.84,.75],pr:[.15,.2,.26],pg:[.85,.7,.15],pgb:[1,.82,.28],
ps:[.55,.52,.48],pw3:[.92,.78,.35],pdoor:[.35,.2,.1],
tk:[.35,.2,.1],lf:[.08,.42,.1],lf2:[.15,.55,.15],lf3:[.22,.65,.18],lf4:[.3,.72,.15],
ck:[.3,.18,.1],cp1:[1,.65,.75],cp2:[1,.8,.86],cp3:[.95,.5,.65],cp4:[1,.72,.8],
hw:[.82,.75,.55],hwd:[.5,.35,.2],hdd:[.35,.22,.12],
hr1:[.3,.3,.35],hr2:[.45,.28,.15],hr3:[.22,.28,.35],
hd:[.3,.2,.1],hwin:[.92,.82,.35],
g1:[.18,.6,.18],g2:[.22,.66,.2],g3:[.15,.55,.15],g4:[.26,.7,.22],
dt:[.48,.35,.2],st:[.5,.48,.45],st2:[.58,.56,.52],pt:[.62,.55,.42],
w1:[.2,.5,.85],w2:[.15,.4,.75],w3:[.3,.6,.95],w4:[.4,.7,.92],
sk:[.9,.72,.55],hb:[.1,.08,.08],
ln:[.38,.38,.4],ll:[1,.9,.5],
f1:[.9,.15,.2],f2:[.95,.9,.15],f3:[.55,.2,.7],f4:[.3,.35,.9],f5:[.9,.5,.1],fs:[.2,.5,.1],
rk:[.48,.48,.5],rk2:[.55,.55,.57],
br:[.58,.35,.15],brd:[.48,.28,.1],
tr:[.78,.18,.12],bm:[.3,.6,.2],bm2:[.38,.7,.25],fc:[.5,.4,.28],
mt:[.32,.38,.32],mt2:[.28,.34,.28],snow:[.9,.92,.95],cl:[.95,.95,.98],
s1:[.85,.15,.15],s2:[.15,.3,.8],s3:[.15,.65,.2],s4:[.9,.8,.15],
s5:[.5,.2,.7],s6:[.9,.5,.1],s7:[.9,.4,.6],s8:[.9,.9,.88],
p1:[.2,.2,.25],p2:[.2,.3,.5],p3:[.5,.5,.5],p4:[.45,.3,.2]
};

const pos=[],nor=[],col=[];
function addBox(x,y,z,w,h,d,c){
  const cR=c[0],cG=c[1],cB=c[2];
  const x0=x,x1=x+w,y0=y,y1=y+h,z0=z,z1=z+d;
  function t(a,b,c2,d2,e,f,g,h2,i,nx,ny,nz){
    pos.push(a,b,c2,d2,e,f,g,h2,i);nor.push(nx,ny,nz,nx,ny,nz,nx,ny,nz);
    col.push(cR,cG,cB,cR,cG,cB,cR,cG,cB);
  }
  t(x0,y0,z1,x1,y0,z1,x1,y1,z1,0,0,1);t(x0,y0,z1,x1,y1,z1,x0,y1,z1,0,0,1);
  t(x1,y0,z0,x0,y0,z0,x0,y1,z0,0,0,-1);t(x1,y0,z0,x0,y1,z0,x1,y1,z0,0,0,-1);
  t(x1,y0,z1,x1,y0,z0,x1,y1,z0,1,0,0);t(x1,y0,z1,x1,y1,z0,x1,y1,z1,1,0,0);
  t(x0,y0,z0,x0,y0,z1,x0,y1,z1,-1,0,0);t(x0,y0,z0,x0,y1,z1,x0,y1,z0,-1,0,0);
  t(x0,y1,z1,x1,y1,z1,x1,y1,z0,0,1,0);t(x0,y1,z1,x1,y1,z0,x0,y1,z0,0,1,0);
  t(x0,y0,z0,x1,y0,z0,x1,y0,z1,0,-1,0);t(x0,y0,z0,x1,y0,z1,x0,y0,z1,0,-1,0);
}
function V(x,y,z,c){addBox(x,y,z,1,1,1,c)}

// ---- GROUND ----
for(let gx=-130;gx<130;gx+=10)for(let gz=-130;gz<130;gz+=10){
  const ci=Math.floor(Math.random()*4);
  addBox(gx,-3,gz,10,3,10,[C.g1,C.g2,C.g3,C.g4][ci]);
}

// ---- PAGODA ----
function pagodaRoof(cx,cz,y,sz,trim,rc){
  const x=cx-Math.floor(sz/2),z=cz-Math.floor(sz/2),w=sz,d=sz;
  addBox(x,y,z,w,3,d,rc);
  addBox(x,y+3,z,w,1,1,trim);addBox(x,y+3,z+d-1,w,1,1,trim);
  addBox(x,y+3,z,1,1,d,trim);addBox(x+w-1,y+3,z,1,1,d,trim);
  const cs=[[x,z,-1,-1],[x+w-1,z,1,-1],[x,z+d-1,-1,1],[x+w-1,z+d-1,1,1]];
  for(const[ex,ez,dx,dz]of cs)for(let h=1;h<=4;h++)addBox(ex+dx*h,y+3+h,ez+dz*h,1,1,1,trim);
}
function pagodaLevel(cx,cz,y,sz,h,wc,pic,trim){
  const x=cx-Math.floor(sz/2),z=cz-Math.floor(sz/2),cs=sz-1;
  addBox(x,y,z,sz,1,sz,pic);
  addBox(x,y+1,z,1,h-1,1,pic);addBox(x+cs,y+1,z,1,h-1,1,pic);
  addBox(x,y+1,z+cs,1,h-1,1,pic);addBox(x+cs,y+1,z+cs,1,h-1,1,pic);
  addBox(x+1,y+1,z,sz-2,h-1,1,wc);addBox(x+1,y+1,z+cs,sz-2,h-1,1,wc);
  addBox(x,y+1,z+1,1,h-1,sz-2,wc);addBox(x+cs,y+1,z+1,1,h-1,sz-2,wc);
  if(sz>=8){const wy=y+Math.floor(h/2)-1,wh=Math.max(2,h-4),sp=Math.floor(sz/4);
    for(let i=1;i<4;i++){const wx=x+Math.floor(i*sz/4)-1;
      if(wx>x&&wx+2<x+cs){addBox(wx,wy,z,2,wh,1,C.pw3);addBox(wx,wy,z+cs,2,wh,1,C.pw3)}
      const wz=z+Math.floor(i*sz/4)-1;
      if(wz>z&&wz+2<z+cs){addBox(x,wy,wz,1,wh,2,C.pw3);addBox(x+cs,wy,wz,1,wh,2,C.pw3)}
    }
  }
  if(sz>=15)addBox(cx-2,y+1,z+cs,4,h-2,1,C.pdoor);
  const ov=Math.max(2,Math.floor(sz/5));
  pagodaRoof(cx,cz,y+h,sz+ov*2,trim,C.pr);
}
(function buildPagoda(){
  const cx=0,cz=0;
  addBox(-18,-4,-18,36,5,36,C.st);
  addBox(-18,1,-18,36,1,1,C.st2);addBox(-18,1,17,36,1,1,C.st2);
  addBox(-18,1,-18,1,1,36,C.st2);addBox(17,1,-18,1,1,36,C.st2);
  for(let s=0;s<3;s++)addBox(-5,s,19+s,10,1,1,C.st2);
  let y=1;
  pagodaLevel(cx,cz,y,20,9,C.pw,C.pw,C.pg);y+=13;
  pagodaLevel(cx,cz,y,15,7,C.pw2,C.pw,C.pg);y+=11;
  pagodaLevel(cx,cz,y,11,6,C.pw,C.pw,C.pg);y+=10;
  pagodaLevel(cx,cz,y,8,5,C.pw2,C.pw,C.pg);y+=9;
  pagodaLevel(cx,cz,y,6,4,C.pw,C.pw,C.pg);y+=8;
  for(let i=0;i<8;i++)V(cx,y+i,cz,i%2?C.pg:C.pgb);
  addBox(cx-1,y+2,cz-1,3,1,3,C.pgb);addBox(cx-1,y+5,cz-1,3,1,3,C.pgb);
})();

// ---- POND ----
(function buildPond(){
  const px=-14,pz=22,pw=26,pd=18;
  addBox(px-1,-1,pz-1,pw+2,1,1,C.dt);addBox(px-1,-1,pz+pd,pw+2,1,1,C.dt);
  addBox(px-1,-1,pz,1,1,pd,C.dt);addBox(px+pw,-1,pz,1,1,pd,C.dt);
  for(let i=px;i<px+pw;i++)for(let j=pz;j<pz+pd;j++)V(i,-2,j,[C.w1,C.w3,C.w4,C.w2][Math.floor(Math.random()*4)]);
  for(let i=0;i<6;i++)V(px+3+Math.floor(Math.random()*(pw-6)),-1,pz+3+Math.floor(Math.random()*(pd-6)),C.lf3);
  for(let i=0;i<8;i++)V(px+3+i*3,0,pz+Math.floor(pd/2)-1+Math.floor(Math.random()*3),C.st2);
  V(px+5,-1,pz+5,C.f5);V(px+6,-1,pz+5,C.f5);V(px+5,-1,pz+6,C.wh);
  V(px+16,-1,pz+8,C.wh);V(px+17,-1,pz+8,C.f1);
})();

// ---- BRIDGE ----
(function buildBridge(){
  const bx=-4,bz=20,bw=10,bd=4;
  for(let i=0;i<bw;i++){
    const ah=Math.round(Math.sin(i/(bw-1)*Math.PI)*2);
    addBox(bx+i,3+ah,bz,1,1,bd,C.br);addBox(bx+i,2+ah,bz,1,1,bd,C.brd);
    addBox(bx+i,4+ah,bz,1,2,1,C.brd);addBox(bx+i,4+ah,bz+bd-1,1,2,1,C.brd);
  }
  for(let i=0;i<bw;i+=3){const ah=Math.round(Math.sin(i/(bw-1)*Math.PI)*2);addBox(bx+i,6+ah,bz,1,1,1,C.brd)}
})();

// ---- TORII ----
(function(){
  const tx=0,tz=44;
  addBox(tx-6,0,tz,2,16,2,C.tr);addBox(tx+4,0,tz,2,16,2,C.tr);
  addBox(tx-8,16,tz-1,18,2,4,C.tr);addBox(tx-6,12,tz-1,14,2,3,C.tr);
  addBox(tx-1,12,tz,3,4,2,C.tr);
})();

// ---- PATHS ----
(function(){
  for(let z=18;z<44;z++)addBox(-2,0,z,5,1,1,C.pt);
  for(let x=18;x<50;x++)addBox(x,0,-3,1,1,5,C.pt);
  for(let x=-22;x<22;x++){addBox(x,0,-22,1,1,4,C.pt);addBox(x,0,22,1,1,4,C.pt)}
  for(let z=-22;z<22;z++){addBox(-22,0,z,4,1,1,C.pt);addBox(18,0,z,4,1,1,C.pt)}
  for(let i=0;i<14;i++)V(-16+i*3,0,16+Math.floor(Math.sin(i*.8)*2),C.st2);
})();

// ---- LANTERNS ----
function lantern(x,z){addBox(x-1,0,z-1,3,2,3,C.ln);addBox(x,2,z,1,4,1,C.ln);addBox(x-1,6,z-1,3,2,3,C.ll);addBox(x-1,8,z-1,3,1,3,C.ln);addBox(x,9,z,1,2,1,C.ln);V(x,11,z,C.ln)}
[[0,38],[0,50],[-5,26],[10,26],[20,-3],[38,-3],[-20,-16],[15,16],[-10,16],[18,10],[-16,-22],[25,16],[-26,0],[30,-16],[8,30],[-8,32],[14,-18],[42,14],[56,-8],[-34,12]].forEach(([lx,lz])=>lantern(lx,lz));

// ---- FENCE ----
(function(){
  const r=32;
  for(let i=-r;i<r;i+=5){
    if(Math.abs(i)>4){V(i,0,r,C.fc);V(i,1,r,C.fc);V(i,2,r,C.fc);if(i+4<r)addBox(i+1,1,r,4,1,1,C.fc)}
    V(i,0,-r,C.fc);V(i,1,-r,C.fc);V(i,2,-r,C.fc);if(i+4<r)addBox(i+1,1,-r,4,1,1,C.fc);
    V(-r,0,i,C.fc);V(-r,1,i,C.fc);V(-r,2,i,C.fc);if(i+4<r)addBox(-r,1,i+1,1,1,4,C.fc);
    V(r,0,i,C.fc);V(r,1,i,C.fc);V(r,2,i,C.fc);if(i+4<r)addBox(r,1,i+1,1,1,4,C.fc);
  }
})();

// ---- CHERRY TREES ----
function cherryTree(x,z,h){
  for(let i=0;i<h;i++)addBox(x,i,z,2,1,2,C.ck);
  for(let b=0;b<Math.floor(Math.random()*3)+2;b++){const dirs=[[-1,0],[1,0],[0,-1],[0,1]][b%4];
    for(let i=1;i<=Math.floor(Math.random()*3)+2;i++)V(x+dirs[0]*i,h-2+Math.floor(i*.5),z+dirs[1]*i,C.ck)}
  const cy=h,cr=Math.floor(h/2)+3;
  for(let dx=-cr;dx<=cr;dx++)for(let dz=-cr;dz<=cr;dz++)for(let dy=0;dy<=cr;dy++){
    const dist=Math.sqrt(dx*dx+dz*dz+dy*dy*1.5);if(dist<cr){
      const sd=cr-dist,prob=sd<2?.65:.12;if(Math.random()<prob)
        V(x+dx,cy+dy,z+dz,[C.cp1,C.cp2,C.cp3,C.cp4][Math.floor(Math.random()*4)]);
    }}
}
[[-16,-10],[15,-14],[-12,20],[18,18],[-8,34],[10,36],[-25,-6],[22,-8],[-30,10],[28,-18]].forEach(([tx,tz])=>cherryTree(tx,tz,9+Math.floor(Math.random()*5)));

// ---- GREEN TREES ----
function greenTree(x,z,h){
  for(let i=0;i<h;i++)addBox(x,i,z,2,1,2,C.tk);
  for(let b=0;b<Math.floor(Math.random()*3)+2;b++){const dirs=[[-1,0],[1,0],[0,-1],[0,1]][b%4];
    for(let i=1;i<=Math.floor(Math.random()*3)+2;i++)V(x+dirs[0]*i,h-2+Math.floor(i*.5),z+dirs[1]*i,C.tk)}
  const cy=h,cr=Math.floor(h/2)+2;
  for(let dx=-cr;dx<=cr;dx++)for(let dz=-cr;dz<=cr;dz++)for(let dy=-1;dy<=cr;dy++){
    const dist=Math.sqrt(dx*dx+dz*dz+dy*dy*1.4);if(dist<cr){
      const sd=cr-dist,prob=sd<2?.6:.1;if(Math.random()<prob)
        V(x+dx,cy+dy,z+dz,[C.lf,C.lf2,C.lf3,C.lf4][Math.floor(Math.random()*4)]);
    }}
}
[[-42,-25],[44,-30],[-46,32],[48,36],[-55,5],[52,10],[-16,-52],[16,-48],[60,-8],[-60,18],[36,50],[-36,46],[0,-58],[66,25],[-66,-10]].forEach(([tx,tz])=>greenTree(tx,tz,12+Math.floor(Math.random()*7)));

// ---- BAMBOO ----
(function(){
  const gx=-48,gz=2;
  for(let i=0;i<18;i++){const bx=gx+Math.floor(Math.random()*8),bz=gz+Math.floor(Math.random()*18);
    const h=13+Math.floor(Math.random()*10);
    for(let j=0;j<h;j++)V(bx,j,bz,j%3?C.bm:C.bm2);
    V(bx,h,bz,C.bm);V(bx+1,h-1,bz,C.bm);V(bx-1,h-1,bz,C.bm);V(bx,h,bz+1,C.bm);V(bx,h,bz-1,C.bm);
    V(bx+1,h,bz+1,C.bm);V(bx-1,h,bz-1,C.bm);
  }
})();

// ---- VILLAGE HOUSES ----
function house(x,z,w,d,h,rc,wc){
  addBox(x,0,z,w,h,1,wc);addBox(x,0,z+d-1,w,h,1,wc);
  addBox(x,0,z+1,1,h,d-2,wc);addBox(x+w-1,0,z+1,1,h,d-2,wc);
  addBox(x,0,z,w,1,d,C.hdd);
  const rY=h,rH=Math.floor(d/2)+2;
  for(let rz=0;rz<d;rz++){const layer=Math.min(rz,d-1-rz);if(layer<rH)addBox(x-1,rY+layer,rz+z,w+2,1,1,rc)}
  addBox(x-1,rY+rH,z+Math.floor(d/2)-1,w+2,1,2,rc);
  const dx2=x+Math.floor(w/2)-1;addBox(dx2,1,z+d-1,3,h-1,1,C.hd);
  if(w>=6){addBox(x+1,Math.floor(h/3),z+d-1,2,2,1,C.hwin);addBox(x+w-3,Math.floor(h/3),z+d-1,2,2,1,C.hwin);
    addBox(x-1,Math.floor(h/3),z+Math.floor(d/3),1,2,2,C.hwin);addBox(x+w,Math.floor(h/3),z+Math.floor(d/3),1,2,2,C.hwin)}
  addBox(dx2,0,z+d,3,1,1,C.dt);
}
[[32,-16,10,8,6,C.hr1,C.hw],[48,-12,8,10,5,C.hr2,C.hwd],[36,4,12,8,7,C.hr3,C.hw],
[54,4,8,8,5,C.hr1,C.hw],[42,20,10,10,6,C.hr2,C.hwd],[58,-22,8,6,5,C.hr3,C.hw],
[62,12,9,8,6,C.hr1,C.hw],[34,-32,8,7,5,C.hr2,C.hw]].forEach(h2=>house(...h2));

// ---- PEOPLE ----
function person(x,z,shirt,pants){
  V(x,5,z,C.sk);V(x,6,z,C.hb);
  V(x,4,z,shirt);V(x,3,z,shirt);
  V(x-1,4,z,shirt);V(x+1,4,z,shirt);V(x-1,3,z,C.sk);V(x+1,3,z,C.sk);
  V(x,2,z,pants);V(x,1,z,pants);V(x,0,z,pants);
}
[[5,30,C.s1,C.p1],[-3,38,C.s2,C.p2],[18,-3,C.s3,C.p3],[30,-3,C.s4,C.p1],
[38,-12,C.s5,C.p3],[48,-12,C.s6,C.p2],[-8,18,C.s7,C.p1],[10,12,C.s8,C.p3],
[-22,15,C.s1,C.p4],[5,-28,C.s2,C.p1],[44,16,C.s3,C.p3],[52,0,C.s4,C.p2],
[-12,-20,C.s5,C.p1],[2,-20,C.s7,C.p3],[-20,-26,C.s6,C.p4],[28,10,C.s1,C.p2],
[0,40,C.s2,C.p1],[50,20,C.s8,C.p3],[-5,8,C.s3,C.p4],[15,-8,C.s4,C.p1],
[56,-16,C.s5,C.p2],[34,0,C.s7,C.p3],[-26,-8,C.s1,C.p1],[8,36,C.s2,C.p4],
[-16,0,C.s6,C.p3],[48,-28,C.s3,C.p1],[64,0,C.s4,C.p2],[40,22,C.s5,C.p4],
[62,-6,C.s7,C.p1],[12,-38,C.s8,C.p3]].forEach(([px,pz,sh,pn])=>person(px,pz,sh,pn));

// ---- FLOWERS ----
function flower(x,z,c){V(x,0,z,C.fs);V(x,1,z,c);V(x,1,z+1,c);V(x+1,1,z,c);V(x-1,0,z,C.lf2);V(x+1,0,z,C.lf3)}
[[-14,16,C.f1],[-8,16,C.f2],[0,16,C.f3],[8,16,C.f4],[14,16,C.f5],
[-14,-16,C.f2],[-6,-16,C.f1],[6,-16,C.f4],[14,-16,C.f3],
[22,18,C.f5],[26,18,C.f2],[-22,18,C.f1],[-26,18,C.f4],
[20,-14,C.f3],[24,-20,C.f5],[-20,-14,C.f2],[-24,-20,C.f1],
[32,14,C.f4],[40,-3,C.f5],[48,-3,C.f2],[6,38,C.f1],[-6,38,C.f3],
[36,26,C.f4],[44,24,C.f5],[52,14,C.f2],[-38,0,C.f3],[0,-42,C.f1]
].forEach(([fx,fz,fc])=>flower(fx,fz,fc));

// ---- ROCKS ----
[[-10,22,2],[16,20,3],[-26,24,2],[22,-16,3],[-32,-20,2],[10,-24,2],
[-16,-32,3],[26,26,2],[-5,27,1],[8,30,2],[-22,12,2],[20,14,1],
[-30,6,2],[34,-12,2],[-38,16,3],[42,26,2],[5,-42,2],[-8,-44,3],
[14,32,1],[-14,34,2],[36,32,3],[-36,32,2],[-16,26,1],[28,-6,2]
].forEach(([rx,rz,sz])=>{const c=Math.random()>.5?C.rk:C.rk2;
  for(let i=0;i<sz;i++)for(let j=0;j<sz;j++)for(let k=0;k<sz;k++)if(Math.random()>.3||k===0)V(rx+i,k,rz+j,c)});

// ---- MOUNTAINS ----
[[-70,-80,35,45],[-35,-85,42,50],[0,-82,52,55],[35,-85,45,50],[70,-80,38,45],
[-110,-75,32,40],[110,-75,35,40],[-55,-70,28,35],[55,-70,30,38],
[0,-95,58,62],[-95,-90,40,48],[95,-90,42,46],[-135,-85,28,35],[135,-85,30,36]
].forEach(([mx,mz,mh,mw])=>{
  const nl=Math.floor(mh/3);
  for(let L=0;L<nl;L++){const t2=L/nl,hw=Math.floor(mw*(1-t2*.9));if(hw<=0)continue;
    const y=L*3-3,snowy=t2>.65,co=L%2;
    addBox(mx-hw,y,mz-hw,hw*2,3,hw*2,snowy?C.snow:(co?C.mt:C.mt2));
    for(let e=0;e<Math.min(hw/3,6);e++){const a=(e/6)*Math.PI*2;
      V(mx+Math.floor(Math.cos(a)*hw),y,mz+Math.floor(Math.sin(a)*hw),snowy?C.snow:(co?C.mt2:C.mt));
      V(mx+Math.floor(Math.cos(a)*hw),y+2,mz+Math.floor(Math.sin(a)*hw),snowy?C.snow:(co?C.mt2:C.mt))
    }}
});

// ---- CLOUDS ----
[[-30,55,-40,15],[40,60,-30,12],[0,65,-50,18],[-50,58,20,10],[30,55,30,14],
[-20,62,45,16],[60,50,-60,11],[-70,55,-10,13],[15,68,-70,15],[-45,64,-65,12],
[50,62,50,17],[-60,58,45,14],[80,56,10,12],[-80,60,30,10],[0,72,60,14]
].forEach(([cx,cy,cz,s])=>{
  for(let dx=-s;dx<=s;dx++)for(let dz=-Math.floor(s/2);dz<=Math.floor(s/2);dz++){
    const dist=(dx*dx)/(s*s)+(dz*dz)/((s/2)*(s/2));
    if(dist<=1&&Math.random()>.2){addBox(cx+dx,cy,cz+dz,1,1,1,C.cl);
      if(Math.random()>.5)addBox(cx+dx,cy+1,cz+dz,1,1,1,C.cl)}
  }
});

// ---- ZEN GARDEN ----
(function(){
  const gx=-30,gz=-12,w=16,d=12;
  addBox(gx,0,gz,w,1,d,[.85,.8,.65]);
  for(let z2=1;z2<d-1;z2+=2)addBox(gx+1,1,gz+z2,w-2,0.1,1,[.9,.85,.7]);
  [[3,3],[4,3],[3,4]].forEach(([dx,dz])=>{V(gx+dx,1,gz+dz,C.rk)});
  [[8,5],[9,5],[9,4]].forEach(([dx,dz])=>{V(gx+dx,1,gz+dz,C.rk2)});
  [[13,2],[13,3]].forEach(([dx,dz])=>{V(gx+dx,1,gz+dz,C.rk)});
  V(gx+1,1,gz+9,C.lf2);V(gx+2,1,gz+9,C.lf3);V(gx+1,1,gz+10,C.lf);
  V(gx+14,1,gz+10,C.lf3);V(gx+13,1,gz+10,C.lf);
})();

// ---- WELL ----
(function(){
  const wx=44,wz=14;
  addBox(wx-2,0,wz-2,5,3,5,C.rk);
  addBox(wx,1,wz,1,1,1,C.w1);addBox(wx+1,1,wz,1,1,1,C.w2);
  addBox(wx,1,wz+1,1,1,1,C.w2);addBox(wx+1,1,wz+1,1,1,1,C.w1);
  addBox(wx-2,3,wz-2,1,4,1,C.hdd);addBox(wx+3,3,wz+3,1,4,1,C.hdd);
  addBox(wx-3,7,wz-3,7,1,7,C.hr2);
  addBox(wx,5,wz+3,1,3,1,[.7,.6,.4]);V(wx,4,wz+3,C.hdd);
})();

// ---- MARKET STALL ----
(function(){
  const mx=54,mz=-4;
  addBox(mx,0,mz,1,5,1,C.hdd);addBox(mx+5,0,mz,1,5,1,C.hdd);
  addBox(mx,0,mz+4,1,5,1,C.hdd);addBox(mx+5,0,mz+4,1,5,1,C.hdd);
  addBox(mx-1,5,mz-1,8,1,7,C.s1);addBox(mx-1,6,mz-1,8,1,7,[.9,.9,.88]);
  addBox(mx+1,0,mz+2,4,3,1,C.hwd);
  V(mx+1,3,mz+2,C.f1);V(mx+2,3,mz+2,C.f2);V(mx+3,3,mz+2,C.f5);V(mx+4,3,mz+2,C.f3);
})();

// ---- SMALL SHRINE ----
(function(){
  const sx=34,sz=-28;
  addBox(sx-3,0,sz-3,7,4,7,C.hw);
  addBox(sx-3,0,sz-3,1,5,1,C.tr);addBox(sx+3,0,sz-3,1,5,1,C.tr);
  addBox(sx-3,0,sz+3,1,5,1,C.tr);addBox(sx+3,0,sz+3,1,5,1,C.tr);
  for(let r=0;r<4;r++)addBox(sx-3-r,4+r,sz-3-r,7+r*2,1,7+r*2,C.hr2);
  addBox(sx,0,sz+4,2,2,1,C.hdd);
})();

// ---- ASSEMBLE MESH ----
const geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));
geo.setAttribute('normal',new THREE.Float32BufferAttribute(nor,3));
geo.setAttribute('color',new THREE.Float32BufferAttribute(col,3));
geo.computeBoundingSphere();
const mat=new THREE.MeshPhongMaterial({vertexColors:true,shininess:15,specular:new THREE.Color(0x333333)});
const mesh=new THREE.Mesh(geo,mat);
mesh.castShadow=true;mesh.receiveShadow=true;
scene.add(mesh);

// ---- CHERRY BLOSSOM PARTICLES ----
const pCount=400;
const pGeo=new THREE.BufferGeometry();
const pPos=new Float32Array(pCount*3);
const pVel=[];
for(let i=0;i<pCount;i++){
  pPos[i*3]=(Math.random()-.5)*90;
  pPos[i*3+1]=Math.random()*55;
  pPos[i*3+2]=(Math.random()-.5)*90;
  pVel.push({x:(Math.random()-.5)*.02,y:-(.015+Math.random()*.04),z:(Math.random()-.5)*.02});
}
pGeo.setAttribute('position',new THREE.BufferAttribute(pPos,3));
const pMat=new THREE.PointsMaterial({color:0xffb7c5,size:.5,transparent:true,opacity:.75,sizeAttenuation:true});
const petals=new THREE.Points(pGeo,pMat);
scene.add(petals);

// ---- ANIMATION ----
const clock=new THREE.Clock();
function animate(){
  requestAnimationFrame(animate);
  const t=clock.getElapsedTime();
  const pA=pGeo.attributes.position.array;
  for(let i=0;i<pCount;i++){
    pA[i*3]+=pVel[i].x+Math.sin(t*1.5+i*.7)*.008;
    pA[i*3+1]+=pVel[i].y;
    pA[i*3+2]+=pVel[i].z+Math.cos(t*1.2+i*.5)*.008;
    if(pA[i*3+1]<0){pA[i*3]=(Math.random()-.5)*90;pA[i*3+1]=48+Math.random()*12;pA[i*3+2]=(Math.random()-.5)*90}
  }
  pGeo.attributes.position.needsUpdate=true;
  renderer.render(scene,camera);
}
animate();

addEventListener('resize',()=>{
  camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();
  renderer.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>
1.71s
首Token
实际等待 1.92s
67
思考TPS
1284s · 86,298 tok
102
输出TPS
96.1s · 9,824 tok
96,122
总Tokens(官方)
输入 85
计时详情
实际等待(客户端)
1.92s
客户端请求准备
0.01s
本站代理准备
0.00s
上游响应头到达
1.63s
上游首字节到达
1.63s
上游首 Token 到达
1.71s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-28T07:30:37.293Z",
  "transport": "railway",
  "ttftSource": "proxy",
  "clientTtftMs": 1917.0999999046326,
  "completionId": "1f39913e-9fdd-4c2b-acff-5becf46f9abb_70d7789b8a394ddb8bb37000f0f00697",
  "streamResumes": 30,
  "proxyPrepareMs": 0,
  "proxyRequestId": "gxXHPAtrT-qpVbQA0_TJvA",
  "upstreamStatus": 200,
  "upstreamTtftMs": 1707,
  "clientHeadersMs": 296.40000009536743,
  "clientPrepareMs": 13.099999904632568,
  "upstreamHeadersMs": 1629,
  "upstreamFirstByteMs": 1630,
  "clientFirstContentMs": 1285671.5999999046
}
总用时 1382s · 平均 70 tok/s · 峰值 163 tok/s
OpenRouter
stealth/space-bunny-alpha
失败

历史结果 · 2026/9/28 07:53:39

HTTP 429:Rate limit exceeded: free-models-per-day-stealth.
—
首Token
—
思考TPS
—
输出TPS
—
总Tokens(估算)
计时详情
客户端请求准备
0.01s
本站代理准备
0.00s
上游响应头到达
0.18s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-28T07:30:37.305Z",
  "transport": "railway",
  "proxyPrepareMs": 0,
  "proxyRequestId": "vw6mhQiFRLW3gBmyxtoGcA",
  "upstreamStatus": 429,
  "clientHeadersMs": 427.30000019073486,
  "clientPrepareMs": 13.199999809265137,
  "upstreamHeadersMs": 182
}
总用时 0.50s
MiniMax-M3.1-Flash-Preview
MiniMax-M3.1-Flash-Preview
完成

新增测试 · 2026/9/30 07:13:42

We need respond with single HTML file likely code. Need create elaborate voxel art scene pagoda garden village people. Need perhaps Three.js external CDN? Single HTML can load three from CDN. But "single HTML file" means all code one file, external library perhaps okay but self-contained preferable. Could implement canvas 2D isometric voxel renderer in HTML JS, no deps, highly detailed, interactive. Need create scene. User says use as many voxels; likely wants artifact code. We can deliver code only maybe. Need design creative elaborate. Need ensure actual voxel art, not just CSS. We can make responsive high detail canvas, 3D isometric projected voxels. We can procedurally create static voxels with thousands, render painter's algorithm. Add animated falling petals, fireflies, people, smoke, water. Controls orbit, zoom, day/night? Need high quality. Single HTML file. Could implement Three.js via CDN for real voxel boxes. Easier impressive. But dependency and "single HTML file" maybe acceptable but truly standalone better write WebGL? Canvas 2D isometric rendering of cube voxels can be detailed, maybe thousands. Need scene and performance. Draw each cube as top + left/right faces. 10k voxels can be heavy 30k polygons but okay. Static offscreen? Controls. We can cull and render. Need coordinate rotation perhaps maintain fixed iso with drag? Could calculate projection for each cube with yaw, pitch and z, painter sort by view depth. Each voxel can have x y z, color. Draw cube faces. Lots 12k voxels, each 3 polygons => 36k, perhaps 15fps. Optimize columns terrain and structures ~8k. Need use canvas resolution capped DPR maybe 1.5. Alternative three.js allows actual instanced boxes. 20k instances and simple phong with directional shadows, orbit controls. External CDN imported. Single file can be opened with internet. More reliable visually and 3D. But user likely expects generated HTML code, and perhaps environment has no CDN? Usually tasks for frontends accept imports. Could make "single HTML" and use Three.js ESM CDN. Need no separate files. Let's use importmap and OrbitControls CDN. Yet if opening local file, CORS? ESM from jsdelivr works. Could use classic script from unpkg three.min.js plus custom orbit controls maybe current builds removed examples/js. Better module. Need creative detailed voxel scene: - island base with cliff, pond, stream - pagoda 5 tier - roofs upturned corners, red columns, dark teal roof with gold trim, lanterns. - Japanese village around: 6 houses varied roofs, market stalls, paths, bridge. - garden: cherry blossom trees, pines, maples, bamboo grove, hedges, rocks, pond koi, fountains maybe. - people: stylized voxel residents walking paths, market, tending gardens, children. Use groups boxes animated. - environment: petals, fireflies, birds, smoke, water sparkle. - UI overlay: title, controls, toggles day/night, petals, labels maybe. - voxel aesthetic all components boxes. - Use InstancedMesh batches by color to perform 20k cubes. Geometry BoxGeometry. For animation dynamic people could use groups, but can also static plus animate transforms. Need complex. Could use 3D scene with voxel cubes. Use helpers: addVoxel(x,y,z,sx,sy,sz,color) adding to batch list (center). When flush batches by color, InstancedMesh. Shadows perhaps InstancedMesh receive/cast. 20k, each cube 12 tris. Shadows potentially expensive. Set only large structures? Instanced all cast. Shadow map 2048, maybe okay power machine. We can manage. Materials MeshStandardMaterial roughness 0.85, flatShading. color batches. InstancedMesh count exact. Need per-instance color if one mesh all? `new THREE.InstancedMesh(geometry, material, voxels.length); setMatrixAt; setColorAt` with one draw call. Great. But material vertexColors? InstancedMesh instanceColor recognized automatically. One material white. Use MeshStandardMaterial({color:0xffffff, roughness...}). Add 20k. Can receiveShadow castShadow. Huge but okay. Build all static. Need small emissive lanterns/water maybe separate material/meshes. Potential depth issues. all cubes. Add as one instanced mesh. Matrix = position & scale. The base coordinates. Use x horizontal, z depth, y up. Terrain width ~52, depth ~44. Pagoda rear right perhaps x=12,z=-8. Village left/front. pond front right. Path. Ground: Base island block y=-3, from x -28 to 28 and z -24 to 24. Could be rectangular chunky. Better layered base with individual column blocks for all grid cells? 56*48 ~2688. Add columns with color based on height. Then surface grass 0.15 high top boxes. Terrain height 0 mostly, pond depressed maybe region x 8-22 z 8-20, stream. y terrain 0. Add base blocks y=-2 height 4 for all. top at y=-0.1. For pond, lower top? island base cannot expose water if base top around -0.2 and water y 0.03. Ground at y=0.2. Let's set tile top ground block center -0.25, height .5 => top0. For pond, base top -0.65 and water top -0.15. Could build each grid cell ground tile as scale 1x1. Use color variation. 56*48 2688. Base rock cliffs at border using tiers. Use base bulk box maybe enough, with tile surfaces. Add jagged cliff around edges and waterfall. Scene as floating diorama? impressive. Ground block 56x2x48 center y -1.3. Side textured via voxel outcrops. Tile colors based on zones/paths. path grid around structures: define path predicate. Use tan colors. Cherry garden left back, village left front, pagoda right back, pond right front. Place paths winding as Manhattan segments. Need roofs: Pagoda with tiers. Each tier base y = 2,5,8,11,14 maybe five. square footprint decreasing 11,9.5,8,6.5,5. Add floor slabs, columns, wall panels, windows. Roofs hip? Japanese pagoda roof slopes and upturned corners. Create roof using tile rows in ring / solid. Need upturned roof geometry via cube stair-step rows: For each tier base y and width: - roof extends width+2; use horizontal square ring at base y? Roof should broad and slope upward toward center. Could create stepped roof surfaces: for each level k 0..4, inner half extents from outer width down to central. At each k, ring/plane around central ridge, dark teal. `tileRoof(cx, baseY, cz, half, rise, style)` using rows at half extents decreasing; each cell where max(abs dx,dz) close to half? Full square surface with each z/x, but boxes. At k=0, full slab at y+0.5*k maybe outer lower roof then next smaller slab above. This creates pagoda layered roof if square. Better ring staircase roof: for step 0..3, half=outer-half - step*1.2, y=base+step*0.6, add box cells within half? This would solid top visible. Under eaves outer ring at lower y. Add gold edge lines and red corner tips rising. Use roof tile patterns. For each k, every x,z within half, add scale 0.95, .35, .95, but need avoid excessive? ~400 each tier, fine. Maybe central rooftop on top tier. Add corner eaves: small stacked cubes at corners ascending/inward? Golden tips. Pagoda body beneath each roof: floor 6x etc. Use red beams, cream walls, windows, balconies. 5 tiers. spire. Village houses: function house(x,z,w,d,h,wall,roofcolor, orientation optional), boxes for walls, timber framing, door, windows, roof stepped gable. Roof function gable: for rows t 0..rise, width shrinks, z depth maybe. Need Japanese tiled roofs. Each row full x and z? Roof slab rectangles. Add ridgeline, eaves. gable direction perhaps width x. Use cubes. Add golden lit windows. Trees: Trunks branches with cube segments. Cherry canopy made clusters of cubes/boxes and individual pink blossom voxels. Need be impressive. Function voxelTree with trunk, branches, canopy clusters. Cherry blossoms add ~300 per tree. 6 trees = 1800. Pine trees with stacked foliage. Maple. Bamboo grove. Rocks, shrubs, flowers. Pond water as semi-transparent plane/box and stepping stones, koi fish (small group dynamic), bridge. Waterfall from elevated rock maybe pagoda hill. Add animated waterfall as translucent thin planes? Voxel style: blue cubes with animation offset and opacity? Could cre

Save this as index.html and open it in a modern browser. It is a complete single-file scene; Three.js is loaded from a CDN.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="theme-color" content="#102a32" />
  <title>Sakura Sanctuary — Voxel Garden Village</title>

  <style>
    @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Fraunces:opsz,wght@9..144,600;9..144,700&display=swap');

    :root {
      --ink: #17363a;
      --cream: #fff8e8;
      --peach: #ff937d;
      --pink: #f5a6bb;
      --gold: #ffd77a;
      --glass: rgba(15, 42, 45, 0.73);
      --line: rgba(255, 255, 255, 0.16);
    }

    * {
      box-sizing: border-box;
    }

    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      overflow: hidden;
      background: #b8dce5;
      color: var(--cream);
      font-family: "DM Sans", system-ui, sans-serif;
    }

    button {
      font: inherit;
    }

    #scene {
      position: fixed;
      inset: 0;
      display: block;
      width: 100%;
      height: 100%;
      outline: none;
      cursor: grab;
    }

    #scene:active {
      cursor: grabbing;
    }

    .vignette {
      position: fixed;
      inset: 0;
      pointer-events: none;
      background:
        radial-gradient(circle at 50% 44%, transparent 47%, rgba(8, 32, 36, 0.15) 100%),
        linear-gradient(180deg, rgba(6, 25, 30, 0.05), transparent 28%, transparent 70%, rgba(6, 25, 30, 0.12));
      z-index: 2;
    }

    .hud {
      position: fixed;
      inset: 0;
      z-index: 4;
      pointer-events: none;
    }

    .brand {
      position: absolute;
      top: 28px;
      left: 30px;
      width: min(440px, calc(100vw - 60px));
      filter: drop-shadow(0 3px 16px rgba(6, 29, 32, 0.2));
    }

    .eyebrow {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 8px;
      color: rgba(255, 248, 232, 0.82);
      font-size: 10px;
      font-weight: 700;
      letter-spacing: 0.22em;
      text-transform: uppercase;
    }

    .eyebrow::before {
      content: "";
      width: 27px;
      height: 2px;
      background: var(--gold);
      box-shadow: 0 0 10px rgba(255, 215, 122, 0.5);
    }

    h1 {
      margin: 0;
      color: #fff9e9;
      font-family: "Fraunces", Georgia, serif;
      font-size: clamp(34px, 4.6vw, 66px);
      line-height: 0.9;
      letter-spacing: -0.045em;
      text-shadow: 0 4px 24px rgba(6, 32, 34, 0.28);
    }

    h1 span {
      color: #ffd1cf;
      font-style: italic;
    }

    .brand p {
      max-width: 390px;
      margin: 14px 0 0;
      color: rgba(255, 249, 232, 0.76);
      font-size: 12px;
      line-height: 1.65;
    }

    .status-card {
      position: absolute;
      top: 28px;
      right: 30px;
      min-width: 210px;
      padding: 14px 16px;
      border: 1px solid var(--line);
      border-radius: 16px;
      background: var(--glass);
      box-shadow: 0 14px 45px rgba(9, 33, 35, 0.18);
      backdrop-filter: blur(16px) saturate(1.25);
    }

    .status-top {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 20px;
      margin-bottom: 10px;
      color: rgba(255, 255, 255, 0.58);
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    .live-dot {
      display: inline-block;
      width: 7px;
      height: 7px;
      margin-right: 6px;
      border-radius: 50%;
      background: #aee889;
      box-shadow: 0 0 11px #aee889;
      animation: pulse 2s ease-in-out infinite;
    }

    @keyframes pulse {
      50% { opacity: 0.45; transform: scale(0.8); }
    }

    .status-number {
      color: var(--gold);
      font-family: "Fraunces", Georgia, serif;
      font-size: 27px;
      line-height: 1;
    }

    .status-sub {
      margin-top: 7px;
      color: rgba(255, 255, 255, 0.5);
      font-size: 10px;
    }

    .controls {
      position: absolute;
      left: 50%;
      bottom: 25px;
      display: flex;
      align-items: center;
      gap: 5px;
      padding: 7px;
      border: 1px solid var(--line);
      border-radius: 17px;
      background: var(--glass);
      box-shadow: 0 18px 50px rgba(7, 27, 30, 0.28);
      backdrop-filter: blur(18px) saturate(1.3);
      transform: translateX(-50%);
      pointer-events: auto;
    }

    .control {
      position: relative;
      min-width: 44px;
      height: 42px;
      padding: 0 13px;
      border: 0;
      border-radius: 11px;
      color: rgba(255, 255, 255, 0.69);
      background: transparent;
      cursor: pointer;
      transition: 160ms ease;
      font-size: 11px;
      font-weight: 600;
    }

    .control:hover {
      color: white;
      background: rgba(255, 255, 255, 0.09);
      transform: translateY(-1px);
    }

    .control.active {
      color: #17383b;
      background: var(--gold);
      box-shadow: 0 5px 18px rgba(255, 197, 95, 0.19);
    }

    .control svg {
      width: 17px;
      height: 17px;
      vertical-align: middle;
    }

    .divider {
      width: 1px;
      height: 25px;
      margin: 0 3px;
      background: rgba(255, 255, 255, 0.12);
    }

    .hint {
      position: absolute;
      right: 30px;
      bottom: 29px;
      display: flex;
      align-items: center;
      gap: 10px;
      color: rgba(255, 255, 255, 0.58);
      font-size: 10px;
      letter-spacing: 0.04em;
      text-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
    }

    .mouse {
      position: relative;
      width: 16px;
      height: 24px;
      border: 1px solid rgba(255, 255, 255, 0.58);
      border-radius: 10px;
    }

    .mouse::before {
      content: "";
      position: absolute;
      top: 5px;
      left: 6px;
      width: 2px;
      height: 5px;
      border-radius: 2px;
      background: rgba(255, 255, 255, 0.65);
    }

    .label-layer {
      position: fixed;
      inset: 0;
      z-index: 3;
      pointer-events: none;
      overflow: hidden;
    }

    .world-label {
      position: absolute;
      min-width: 110px;
      padding: 7px 10px 7px 12px;
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 8px;
      color: white;
      background: rgba(17, 47, 49, 0.76);
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.18);
      backdrop-filter: blur(10px);
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.12em;
      text-align: left;
      text-transform: uppercase;
      transform: translate(-50%, -100%);
      transition: opacity 180ms ease;
    }

    .world-label::after {
      content: "";
      position: absolute;
      left: 50%;
      bottom: -18px;
      width: 1px;
      height: 18px;
      background: linear-gradient(rgba(255, 255, 255, 0.6), transparent);
    }

    .world-label small {
      display: block;
      margin-top: 2px;
      color: var(--gold);
      font-size: 7px;
      font-weight: 600;
      letter-spacing: 0.15em;
    }

    body.night .brand,
    body.night .status-card,
    body.night .controls,
    body.night .hint {
      color: white;
    }

    #loading {
      position: fixed;
      inset: 0;
      z-index: 20;
      display: grid;
      place-items: center;
      background: #123b40;
      transition: opacity 600ms ease, visibility 600ms ease;
    }

    #loading.hidden {
      opacity: 0;
      visibility: hidden;
    }

    .loader-content {
      width: min(360px, 75vw);
      text-align: center;
    }

    .loader-mark {
      display: grid;
      place-items: center;
      width: 66px;
      height: 66px;
      margin: 0 auto 20px;
      color: var(--gold);
      font-size: 32px;
      border: 2px solid rgba(255, 215, 122, 0.4);
      box-shadow:
        inset 0 0 0 8px rgba(255, 215, 122, 0.06),
        0 0 40px rgba(255, 215, 122, 0.12);
      transform: rotate(45deg);
    }

    .loader-mark span {
      transform: rotate(-45deg);
    }

    .loader-content h2 {
      margin: 0 0 8px;
      color: #fff4dd;
      font-family: "Fraunces", Georgia, serif;
      font-size: 27px;
    }

    .loader-content p {
      margin: 0 0 18px;
      color: rgba(255, 255, 255, 0.55);
      font-size: 11px;
    }

    .loader-track {
      height: 3px;
      overflow: hidden;
      border-radius: 4px;
      background: rgba(255, 255, 255, 0.1);
    }

    .loader-track div {
      width: 40%;
      height: 100%;
      background: var(--gold);
      animation: loading 1.2s ease-in-out infinite;
    }

    @keyframes loading {
      0% { transform: translateX(-110%); }
      100% { transform: translateX(350%); }
    }

    @media (max-width: 760px) {
      .brand {
        top: 19px;
        left: 20px;
      }

      h1 {
        font-size: 38px;
      }

      .brand p {
        display: none;
      }

      .status-card {
        top: 18px;
        right: 18px;
        min-width: 0;
        padding: 10px 12px;
      }

      .status-sub,
      .status-top > span {
        display: none;
      }

      .status-number {
        font-size: 21px;
      }

      .controls {
        bottom: 16px;
        max-width: calc(100vw - 24px);
        overflow-x: auto;
      }

      .control {
        min-width: 41px;
        padding: 0 10px;
        white-space: nowrap;
      }

      .control .control-text {
        display: none;
      }

      .hint {
        display: none;
      }
    }
  </style>

  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.164.1/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.1/examples/jsm/"
      }
    }
  </script>
</head>

<body>
  <canvas id="scene" aria-label="Interactive voxel pagoda garden and village"></canvas>
  <div class="vignette"></div>
  <div class="label-layer" id="labels"></div>

  <main class="hud">
    <header class="brand">
      <div class="eyebrow">A living voxel miniature</div>
      <h1>Sakura <span>Sanctuary</span></h1>
      <p>
        A five-tier pagoda, lantern-lit village, blossom gardens, koi ponds and
        hundreds of tiny stories—all built from colorful cubes.
      </p>
    </header>

    <aside class="status-card">
      <div class="status-top">
        <span><i class="live-dot"></i>World online</span>
        <span id="time-label">Golden daylight</span>
      </div>
      <div class="status-number" id="voxel-count">0 voxels</div>
      <div class="status-sub" id="scene-status">5 tiers · 18 villagers · 1 peaceful world</div>
    </aside>

    <nav class="controls" aria-label="Scene controls">
      <button class="control active" id="themeBtn" title="Toggle day and night">
        <span id="themeIcon">☾</span>
        <span class="control-text" id="themeText"> Night</span>
      </button>

      <button class="control active" id="petalBtn" title="Toggle falling petals">
        ✿ <span class="control-text">Petals</span>
      </button>

      <button class="control" id="labelBtn" title="Toggle location labels">
        ⌖ <span class="control-text">Labels</span>
      </button>

      <button class="control" id="cinemaBtn" title="Toggle automatic camera orbit">
        ◉ <span class="control-text">Cinematic</span>
      </button>

      <div class="divider"></div>

      <button class="control" id="resetBtn" title="Reset camera">
        ↺ <span class="control-text">Reset view</span>
      </button>
    </nav>

    <div class="hint">
      <span class="mouse"></span>
      <span>Drag to orbit · Scroll to zoom · Right-drag to pan</span>
    </div>
  </main>

  <div id="loading">
    <div class="loader-content">
      <div class="loader-mark"><span>❀</span></div>
      <h2>Growing the sanctuary</h2>
      <p>Planting trees, stacking pagodas & waking the villagers…</p>
      <div class="loader-track"><div></div></div>
    </div>
  </div>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    // -----------------------------------------------------------------------
    // Scene setup
    // -----------------------------------------------------------------------

    const canvas = document.querySelector('#scene');
    const renderer = new THREE.WebGLRenderer({
      canvas,
      antialias: true,
      powerPreference: 'high-performance'
    });

    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.65));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.08;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xb7dce4);
    scene.fog = new THREE.Fog(0xb7dce4, 68, 150);

    const camera = new THREE.PerspectiveCamera(
      41,
      window.innerWidth / window.innerHeight,
      0.1,
      240
    );
    camera.position.set(54, 43, 60);

    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 4, 0);
    controls.enableDamping = true;
    controls.dampingFactor = 0.055;
    controls.minDistance = 28;
    controls.maxDistance = 105;
    controls.minPolarAngle = 0.28;
    controls.maxPolarAngle = Math.PI * 0.47;
    controls.autoRotateSpeed = 0.34;
    controls.update();

    const hemi = new THREE.HemisphereLight(0xdff7ff, 0x756044, 2.15);
    scene.add(hemi);

    const sun = new THREE.DirectionalLight(0xfff1c5, 3.6);
    sun.position.set(-25, 48, 28);
    sun.castShadow = true;
    sun.shadow.mapSize.set(2048, 2048);
    sun.shadow.camera.left = -42;
    sun.shadow.camera.right = 42;
    sun.shadow.camera.top = 40;
    sun.shadow.camera.bottom = -40;
    sun.shadow.camera.near = 1;
    sun.shadow.camera.far = 115;
    sun.shadow.bias = -0.0004;
    sun.shadow.normalBias = 0.025;
    scene.add(sun);

    const rim = new THREE.DirectionalLight(0x7fc7e3, 0.85);
    rim.position.set(35, 18, -35);
    scene.add(rim);

    // -----------------------------------------------------------------------
    // Deterministic helpers
    // -----------------------------------------------------------------------

    function mulberry32(seed) {
      return function() {
        let t = seed += 0x6D2B79F5;
        t = Math.imul(t ^ t >>> 15, t | 1);
        t ^= t + Math.imul(t ^ t >>> 7, t | 61);
        return ((t ^ t >>> 14) >>> 0) / 4294967296;
      };
    }

    const random = mulberry32(84127);
    const rand = (a, b) => a + (b - a) * random();
    const pick = arr => arr[Math.floor(random() * arr.length)];
    const clamp = THREE.MathUtils.clamp;
    const lerp = THREE.MathUtils.lerp;

    const voxels = [];
    const glowVoxels = [];
    const waterCells = [];
    const labelAnchors = [];

    function addV(x, y, z, sx, sy, sz, color) {
      voxels.push({ x, y, z, sx, sy, sz, color });
    }

    function addGlow(x, y, z, sx, sy, sz, color = 0xffc85d) {
      glowVoxels.push({ x, y, z, sx, sy, sz, color });
    }

    function addVBatch(list, fn) {
      for (const item of list) fn(item);
    }

    function addBoxMesh(parent, sx, sy, sz, color, x = 0, y = 0, z = 0) {
      const material = new THREE.MeshStandardMaterial({
        color,
        roughness: 0.82,
        metalness: 0
      });
      const mesh = new THREE.Mesh(new THREE.BoxGeometry(sx, sy, sz), material);
      mesh.position.set(x, y, z);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      parent.add(mesh);
      return mesh;
    }

    // -----------------------------------------------------------------------
    // Island, terrain, water channels, and path network
    // -----------------------------------------------------------------------

    function isWaterCell(x, z) {
      const pond =
        Math.pow((x - 14) / 8.5, 2) +
        Math.pow((z - 10.2) / 6.7, 2);

      const irregular =
        1 +
        Math.sin(x * 1.61 + z * 0.34) * 0.08 +
        Math.cos(z * 1.27 - x * 0.18) * 0.07;

      const stream =
        x > 0 && x < 8 &&
        Math.abs(z - (8.1 + Math.sin(x * 0.44) * 0.8)) < 0.75;

      return (pond < irregular && x < 24) || stream;
    }

    function isPath(x, z) {
      const mainRoad = z >= 2 && z <= 4 && x > -25 && x < 22;
      const pagodaRoad = x >= 3 && x <= 5 && z > -18 && z < 8;
      const villageLane = x >= -24 && x <= -5 && Math.abs(z + 1) < 1.1;
      const gardenWalk = z >= -16 && z <= -14 && x > -25 && x < 6;
      const pondWalk = z >= 16 && z <= 17 && x > 2 && x < 23;
      const teaWalk = Math.abs(z - 6) < 1 && x > -5 && x < 5;
      return (
        mainRoad || pagodaRoad || villageLane ||
        gardenWalk || pondWalk || teaWalk
      );
    }

    function nearStructure(x, z) {
      if (x > 4 && x < 20 && z > -19 && z < -2) return true; // pagoda
      if (x > -25 && x < -5 && z > -5 && z < 16) return true; // village
      if (x > 1 && x < 8 && z > 3 && z < 10) return true; // tea house
      return false;
    }

    // Deep rock foundation.
    addV(0, -2.38, 0, 56, 3.15, 48, '#53666b');
    addV(0, -3.5, 0, 53, 0.55, 45, '#3b5158');

    const grassColors = ['#65a85f', '#6eb266', '#76b96b', '#5f9f5a', '#80bd70'];
    const dirtColors = ['#b98c63', '#c79b70', '#aa7b58', '#d0a77d'];
    const stoneColors = ['#849494', '#728889', '#9aa6a0', '#61797c'];

    for (let x = -28; x < 28; x++) {
      for (let z = -24; z < 24; z++) {
        if (isWaterCell(x, z)) {
          waterCells.push({ x, z });
          addV(x, -0.72, z, 0.99, 0.12, 0.99, pick(['#506d68', '#49655f', '#5b756c']));
          continue;
        }

        const color = isPath(x, z) ? pick(dirtColors) : pick(grassColors);
        addV(x, -0.32, z, 0.99, 0.86, 0.99, color);

        // Small stone fragments embedded in the ground.
        if (isPath(x, z) && random() < 0.075) {
          addV(
            x + rand(-0.2, 0.2),
            0.15,
            z + rand(-0.2, 0.2),
            rand(0.12, 0.3),
            rand(0.08, 0.18),
            rand(0.12, 0.3),
            pick(['#b3a38a', '#8c8173', '#d0b894'])
          );
        }
      }
    }

    // Break the rectangular edge with layered cliff voxels.
    for (let x = -28; x < 28; x++) {
      for (const z of [-24, 23]) {
        const h = rand(0.6, 2.7);
        addV(x, -1.65 + h * 0.15, z + (z < 0 ? -0.12 : 0.12),
          1.03, h, rand(0.7, 1.35), pick(stoneColors));
        if (random() < 0.24) {
          addV(x + rand(-0.25, 0.25), -2.8 - rand(0, 0.8), z,
            rand(0.45, 0.9), rand(0.5, 1.2), rand(0.5, 0.9), pick(stoneColors));
        }
      }
    }

    for (let z = -23; z < 24; z++) {
      for (const x of [-28, 27]) {
        const h = rand(0.7, 2.6);
        addV(x + (x < 0 ? -0.1 : 0.1), -1.6 + h * 0.16, z,
          rand(0.7, 1.3), h, 1.03, pick(stoneColors));
      }
    }

    // Ocean under the floating diorama.
    const oceanMaterial = new THREE.MeshPhysicalMaterial({
      color: 0x4b9db4,
      roughness: 0.3,
      metalness: 0.05,
      transparent: true,
      opacity: 0.84,
      clearcoat: 0.7
    });
    const ocean = new THREE.Mesh(new THREE.BoxGeometry(160, 0.25, 160), oceanMaterial);
    ocean.position.y = -4.35;
    ocean.receiveShadow = true;
    scene.add(ocean);

    // -----------------------------------------------------------------------
    // Grass, flowers, rocks, lanterns and tiny garden details
    // -----------------------------------------------------------------------

    const flowerColors = ['#fff2b3', '#ff9eae', '#e993c1', '#f5f7dd', '#a9d5ff'];

    for (let i = 0; i < 2350; i++) {
      const x = Math.floor(rand(-27, 27));
      const z = Math.floor(rand(-23, 23));

      if (isWaterCell(x, z) || isPath(x, z) || nearStructure(x, z)) continue;

      const bladeColor = pick(['#3f8c50', '#4e9d57', '#67ae5c', '#397b49']);
      const h = rand(0.17, 0.45);
      addV(x + rand(-0.33, 0.33), 0.12 + h * 0.5, z + rand(-0.33, 0.33),
        rand(0.06, 0.1), h, rand(0.06, 0.1), bladeColor);

      if (random() < 0.12) {
        addV(x, 0.48, z, 0.16, 0.16, 0.16, pick(flowerColors));
      }
    }

    // Moss and grass on selected rocks.
    function addRock(x, z, scale = 1, baseY = 0.12) {
      const layers = 3 + Math.floor(random() * 3);
      for (let i = 0; i < layers; i++) {
        const w = scale * rand(1.1, 2.2) * (1 - i * 0.12);
        addV(
          x + rand(-0.18, 0.18),
          baseY + i * scale * 0.38,
          z + rand(-0.18, 0.18),
          w,
          scale * 0.5,
          w * rand(0.65, 1.05),
          pick(stoneColors)
        );
      }
      if (random() < 0.65) {
        addV(x, baseY + layers * scale * 0.38, z,
          scale * rand(0.45, 0.9), 0.15, scale * rand(0.35, 0.7), '#639a56');
      }
    }

    [
      [-25, -20, 1.1], [-19, -19, 0.75], [-5, -19, 0.85],
      [23, -20, 1.2], [24, 6, 0.9], [24, 15, 1.1],
      [1, 17, 0.8], [-24, 19, 1.1], [-4, -7, 0.65]
    ].forEach(r => addRock(r[0], r[1], r[2]));

    function stoneLantern(x, z, scale = 1) {
      addV(x, 0.2, z, 0.8 * scale, 0.35 * scale, 0.8 * scale, '#687a79');
      addV(x, 0.62 * scale, z, 0.34 * scale, 0.75 * scale, 0.34 * scale, '#8d9991');
      addV(x, 1.08 * scale, z, 0.78 * scale, 0.28 * scale, 0.78 * scale, '#a0a79b');
      addGlow(x, 1.35 * scale, z, 0.42 * scale, 0.48 * scale, 0.42 * scale, 0xffbf5e);
      addV(x, 1.7 * scale, z, 0.95 * scale, 0.23 * scale, 0.95 * scale, '#75837f');
      addV(x, 1.9 * scale, z, 0.4 * scale, 0.25 * scale, 0.4 * scale, '#87928a');
    }

    [
      [-5, 4.8], [-5, -6], [-12, 4.7], [-20, 4.7],
      [3.5, 2], [3.5, -6], [3.5, -14], [8, 16.5],
      [18, 16.5], [22, 16.5], [1, 5.6]
    ].forEach(([x, z], i) => stoneLantern(x, z, i % 3 === 0 ? 0.85 : 1));

    // -----------------------------------------------------------------------
    // Pagoda
    // -----------------------------------------------------------------------

    function pagodaRoof(cx, y, cz, outer, rise) {
      for (let layer = 0; layer < 5; layer++) {
        const half = outer - layer * (outer * 0.16);
        const yy = y + layer * rise / 4;
        const tileColors = ['#24575b', '#2b6668', '#316e70', '#397777', '#24575b'];

        for (let x = -Math.ceil(half); x <= Math.ceil(half); x++) {
          for (let z = -Math.ceil(half); z <= Math.ceil(half); z++) {
            if (Math.abs(x) > half + 0.05 || Math.abs(z) > half + 0.05) continue;
            addV(
              cx + x,
              yy,
              cz + z,
              0.96,
              0.31,
              0.96,
              tileColors[layer]
            );
          }
        }
      }

      // Gold eave edging.
      const edge = Math.round(outer);
      for (let i = -edge; i <= edge; i++) {
        addV(cx + i, y - 0.08, cz - edge, 0.95, 0.22, 0.32, '#d7ad56');
        addV(cx + i, y - 0.08, cz + edge, 0.95, 0.22, 0.32, '#d7ad56');
        addV(cx - edge, y - 0.08, cz + i, 0.32, 0.22, 0.95, '#d7ad56');
        addV(cx + edge, y - 0.08, cz + i, 0.32, 0.22, 0.95, '#d7ad56');
      }

      // Upturned corners.
      for (const sx of [-1, 1]) {
        for (const sz of [-1, 1]) {
          for (let i = 0; i < 4; i++) {
            addV(
              cx + sx * (outer - i * 0.18),
              y + 0.12 + i * 0.27,
              cz + sz * (outer - i * 0.18),
              0.55,
              0.36,
              0.55,
              i === 3 ? '#f1c65f' : '#24575b'
            );
          }
        }
      }
    }

    function buildPagoda(cx, cz) {
      const levels = [
        { y: 0.35, w: 10.0 },
        { y: 3.95, w: 8.5 },
        { y: 7.15, w: 7.0 },
        { y: 9.95, w: 5.7 },
        { y: 12.35, w: 4.35 }
      ];

      // Stone terrace and stairway.
      addV(cx, 0.03, cz, 13.2, 0.6, 13.2, '#6f7d75');
      addV(cx, 0.32, cz, 12.4, 0.22, 12.4, '#a3a18a');
      for (let i = 0; i < 5; i++) {
        addV(cx, 0.5 - i * 0.08, cz + 5.1 + i * 0.62,
          3.2 + i * 0.18, 0.2, 0.65, '#c1b596');
      }

      for (let li = 0; li < levels.length; li++) {
        const { y, w } = levels[li];
        const d = w;
        const bodyH = 2.55 - li * 0.08;

        // Balcony/floor slab.
        addV(cx, y, cz, w + 0.85, 0.38, d + 0.85, '#a83e37');
        addV(cx, y + 0.28, cz, w + 0.45, 0.18, d + 0.45, '#d07154');

        // Plastered main body.
        addV(cx, y + bodyH * 0.58, cz, w, bodyH, d, '#eadbb8');

        // Dark wood lower and upper bands.
        addV(cx, y + 0.48, cz, w + 0.06, 0.32, d + 0.06, '#59352f');
        addV(cx, y + bodyH, cz, w + 0.08, 0.32, d + 0.08, '#713b34');

        // Corner columns.
        for (const sx of [-1, 1]) {
          for (const sz of [-1, 1]) {
            addV(cx + sx * w * 0.44, y + bodyH * 0.55, cz + sz * d * 0.44,
              0.36, bodyH + 0.3, 0.36, '#b84136');
          }
        }

        // Windows and doors on all four sides.
        for (let side = 0; side < 4; side++) {
          const alongX = side % 2 === 0;
          const sign = side < 2 ? -1 : 1;
          const panels = li < 3 ? 3 : 2;

          for (let p = 0; p < panels; p++) {
            const offset = (p - (panels - 1) / 2) * (w / (panels + 0.45));
            const panelW = w / (panels + 1.5);

            if (alongX) {
              const px = cx + offset;
              const pz = cz + sign * (d * 0.505);
              addV(px, y + 1.32, pz, panelW, 1.18, 0.14, '#294c4b');
              if ((li + p) % 3 !== 0) {
                addGlow(px, y + 1.32, pz + sign * 0.09,
                  panelW * 0.67, 0.78, 0.08, 0xffc25a);
              }
              addV(px, y + 1.32, pz + sign * 0.17,
                0.11, 1.25, 0.11, '#733c32');
            } else {
              const px = cx + sign * (w * 0.505);
              const pz = cz + offset;
              addV(px, y + 1.32, pz, 0.14, 1.18, panelW, '#294c4b');
              if ((li + p) % 3 !== 0) {
                addGlow(px + sign * 0.09, y + 1.32, pz,
                  0.08, 0.78, panelW * 0.67, 0xffc25a);
              }
              addV(px + sign * 0.17, y + 1.32, pz,
                0.11, 1.25, 0.11, '#733c32');
            }
          }
        }

        // Railings.
        for (const sz of [-1, 1]) {
          for (let i = -Math.floor(w / 2); i <= Math.floor(w / 2); i += 1) {
            addV(cx + i, y + 0.63, cz + sz * (w * 0.5 + 0.33),
              0.12, 0.58, 0.12, '#7f302b');
          }
          addV(cx, y + 0.92, cz + sz * (w * 0.5 + 0.33),
            w + 0.4, 0.14, 0.18, '#d36448');
        }

        pagodaRoof(cx, y + bodyH + 0.18, cz, w * 0.5 + 1.55, 1.75);
      }

      // Upper crown and finial.
      const topY = 17.0;
      addV(cx, topY, cz, 2.3, 0.65, 2.3, '#d5a64e');
      addV(cx, topY + 0.9, cz, 0.42, 1.7, 0.42, '#e7bd61');
      for (let i = 0; i < 4; i++) {
        addV(cx, topY + 1.5 + i * 0.42, cz,
          1.65 - i * 0.28, 0.18, 1.65 - i * 0.28, '#f0ca6c');
      }
      addV(cx, topY + 3.4, cz, 0.2, 1.1, 0.2, '#f3d982');

      labelAnchors.push({
        element: makeLabel('Five-Tier Pagoda', 'Harumi shrine'),
        position: new THREE.Vector3(cx, 20, cz)
      });
    }

    buildPagoda(12, -10);

    // -----------------------------------------------------------------------
    // Village houses
    // -----------------------------------------------------------------------

    function gableRoof(cx, y, cz, w, d, roofColor) {
      const steps = Math.ceil((d * 0.5 + 0.75) / 0.62);

      for (let s = 0; s < steps; s++) {
        const offset = d * 0.5 + 0.75 - s * 0.62;
        const yy = y + s * 0.39;

        for (const side of [-1, 1]) {
          addV(cx, yy, cz + side * offset,
            w + 1.1, 0.34, 0.68, s % 2 ? roofColor : '#31535a');
        }

        // Thin raised tile seams.
        for (let x = -Math.floor(w / 2); x <= Math.floor(w / 2); x += 2) {
          addV(cx + x, yy + 0.22, cz + offset,
            0.12, 0.16, 0.72, '#55727a');
        }
      }

      addV(cx, y + steps * 0.39 + 0.15, cz,
        w + 1.25, 0.32, 0.58, '#24484e');

      for (const side of [-1, 1]) {
        addV(cx, y - 0.08, cz + side * (d * 0.5 + 0.68),
          w + 1.35, 0.23, 0.24, '#1f4147');
      }
    }

    function house(x, z, w, d, h, wallColor, roofColor, doorColor) {
      addV(x, 0.18, z, w + 0.55, 0.35, d + 0.55, '#817e6d');
      addV(x, h * 0.56, z, w, h, d, wallColor);

      // Exposed timber framing.
      for (const sx of [-1, 1]) {
        for (const sz of [-1, 1]) {
          addV(x + sx * (w * 0.5 - 0.16), h * 0.54, z + sz * (d * 0.5 + 0.03),
            0.24, h, 0.22, '#684338');
        }
      }

      addV(x, h - 0.17, z, w + 0.12, 0.25, d + 0.12, '#5c3931');
      addV(x, h * 0.52, z + d * 0.5 + 0.03, w, 0.2, 0.2, '#684338');

      // Door and glowing paper windows.
      addV(x, h * 0.43, z + d * 0.5 + 0.13, 1.05, h * 0.78, 0.18, doorColor);
      addV(x, h * 0.43, z + d * 0.5 + 0.24, 0.12, h * 0.78, 0.1, '#422e2b');
      addV(x, h * 0.43, z + d * 0.5 + 0.25, 0.16, 0.12, 0.12, '#e6bd66');

      for (const sx of [-1, 1]) {
        addV(x + sx * w * 0.29, h * 0.57, z + d * 0.5 + 0.15,
          w * 0.22, h * 0.36, 0.14, '#304e4d');
        addGlow(x + sx * w * 0.29, h * 0.57, z + d * 0.5 + 0.24,
          w * 0.16, h * 0.25, 0.08, 0xffd37a);
      }

      // Side window.
      addGlow(x + w * 0.5 + 0.12, h * 0.58, z,
        0.08, h * 0.32, d * 0.3, 0xffca68);

      gableRoof(x, h + 0.18, z, w, d, roofColor);

      // Tiny chimney and smoke source.
      if (random() > 0.45) {
        addV(x + w * 0.28, h + 1.2, z - d * 0.17,
          0.48, 1.25, 0.48, '#64544a');
      }
    }

    house(-20, 10, 5.0, 4.2, 3.1, '#e7d6ae', '#315761', '#8f4335');
    house(-12.6, 11.3, 5.6, 4.5, 3.35, '#d9c59b', '#3d6265', '#324e4c');
    house(-20.2, 0.0, 4.4, 4.2, 2.8, '#d2b990', '#2d5259', '#a34e3c');
    house(-10.2, -0.1, 4.8, 4.0, 3.05, '#eadbb6', '#405f5d', '#84382f');
    house(-15.7, 16.3, 5.8, 4.0, 2.9, '#cfb38d', '#2a4e55', '#425b52');

    // Tea house beside the pond.
    house(4.3, 6.5, 4.3, 3.8, 2.7, '#ead9b5', '#31575a', '#6d4036');

    labelAnchors.push({
      element: makeLabel('Mochi Lane Village', '18 residents'),
      position: new THREE.Vector3(-16, 7.5, 9)
    });

    labelAnchors.push({
      element: makeLabel('Koi Garden', 'Still water'),
      position: new THREE.Vector3(15, 2.6, 11)
    });

    // -----------------------------------------------------------------------
    // Torii gate, market stalls, bridge, boats and well
    // -----------------------------------------------------------------------

    function torii(x, z) {
      for (const sx of [-1, 1]) {
        addV(x + sx * 2.45, 1.65, z, 0.5, 3.2, 0.55, '#b63e36');
        addV(x + sx * 2.45, 0.2, z, 0.8, 0.4, 0.85, '#69726a');
        addV(x + sx * 2.45, 2.8, z, 0.68, 0.25, 0.7, '#d65b43');
      }
      addV(x, 3.25, z, 5.9, 0.35, 0.6, '#c34838');
      addV(x, 3.7, z, 6.7, 0.32, 0.85, '#233f45');
      addV(x, 3.94, z, 6.0, 0.18, 0.6, '#d35b43');
      addV(x, 2.55, z, 5.1, 0.22, 0.28, '#a93632');
      addV(x, 2.3, z, 0.35, 0.65, 0.22, '#f1d599');
    }

    torii(-3.6, 3);

    function marketStall(x, z, colorA, colorB) {
      for (const sx of [-1, 1]) {
        for (const sz of [-1, 1]) {
          addV(x + sx * 1.5, 1.15, z + sz * 0.9, 0.14, 2.3, 0.14, '#644438');
        }
      }

      for (let i = 0; i < 8; i++) {
        addV(x - 1.75 + i * 0.5, 2.25, z,
          0.5, 0.2, 2.25, i % 2 ? colorA : colorB);
        addV(x - 1.75 + i * 0.5, 2.08, z - 1.15,
          0.5, 0.45, 0.16, i % 2 ? colorA : colorB);
      }

      addV(x, 0.86, z, 2.8, 0.25, 1.25, '#8b6041');
      for (let i = 0; i < 6; i++) {
        addV(x - 1 + (i % 3) * 0.7, 1.07, z - 0.3 + Math.floor(i / 3) * 0.55,
          0.35, 0.25, 0.3, pick(['#ef9a57', '#f2c65f', '#c84f45', '#8ebd68']));
      }
    }

    marketStall(-16.2, 4.7, '#df6957', '#f3d6a0');
    marketStall(-10.8, 4.7, '#4f8790', '#f2d6a0');
    marketStall(2.1, 3.8, '#db6a65', '#f5dfb6');

    // Red arched bridge over the inlet.
    for (let i = 0; i < 9; i++) {
      const t = (i - 4) / 4;
      const y = 0.18 + (1 - t * t) * 1.15;
      const z = 8.05 + Math.sin(t * 0.5) * 0.5;
      addV(4.2, y, z, 0.73, 0.25, 2.2, i % 2 ? '#9c4035' : '#ba4c38');
      if (i % 2 === 0) {
        for (const side of [-1, 1]) {
          addV(4.2 + side * 0.94, y + 0.52, z,
            0.14, 0.9, 0.14, '#b64335');
        }
      }
    }
    for (const side of [-1, 1]) {
      for (let i = 0; i < 9; i++) {
        const t = (i - 4) / 4;
        const y = 0.78 + (1 - t * t) * 1.15;
        addV(4.2 + side * 0.94, y, 8.05 + Math.sin(t * 0.5) * 0.5,
          0.19, 0.2, 2.3, '#ce5740');
      }
    }

    // Village well.
    addV(-7.4, 0.52, 6.2, 1.6, 0.75, 1.6, '#6c7770');
    addV(-7.4, 0.92, 6.2, 1.0, 0.18, 1.0, '#263f43');
    for (const sx of [-1, 1]) {
      addV(-7.4 + sx * 0.72, 1.45, 6.2, 0.15, 1.2, 0.15, '#614236');
    }
    addV(-7.4, 2.05, 6.2, 1.9, 0.22, 1.25, '#3c5556');

    // Small wooden boat beside the bridge.
    const boat = new THREE.Group();
    addBoxMesh(boat, 2.8, 0.35, 1.0, '#8f5838', 0, 0, 0);
    addBoxMesh(boat, 1.9, 0.25, 0.75, '#d08a45', 0, 0.27, 0);
    addBoxMesh(boat, 0.14, 0.16, 2.0, '#634435', 0.2, 0.36, 0);
    boat.position.set(6.4, 0.03, 10.3);
    boat.rotation.y = -0.3;
    scene.add(boat);

    // -----------------------------------------------------------------------
    // Trees and groves
    // -----------------------------------------------------------------------

    function branchSteps(x1, y1, z1, x2, y2, z2, width, color) {
      const steps = Math.max(2, Math.ceil(Math.hypot(x2 - x1, y2 - y1, z2 - z1) * 1.5));
      for (let i = 0; i <= steps; i++) {
        const t = i / steps;
        addV(
          lerp(x1, x2, t),
          lerp(y1, y2, t),
          lerp(z1, z2, t),
          width * (1 - t * 0.32),
          width * 0.8,
          width * (1 - t * 0.32),
          color
        );
      }
    }

    const blossomColors = ['#f6a7b8', '#f8b9c5', '#f19bae', '#ffd0d5', '#e98aa7', '#f6c0b4'];

    function cherryTree(x, z, height = 5.8, spread = 3.2) {
      addV(x, height * 0.42, z, 0.75, height * 0.82, 0.75, '#694337');
      addV(x + 0.18, height * 0.83, z, 0.5, height * 0.34, 0.52, '#79503b');

      for (let b = 0; b < 7; b++) {
        const a = (b / 7) * Math.PI * 2 + rand(-0.25, 0.25);
        const ex = x + Math.cos(a) * spread * rand(0.7, 1.0);
        const ez = z + Math.sin(a) * spread * rand(0.7, 1.0);
        const ey = height * rand(0.72, 1.0);
        branchSteps(x, height * 0.62, z, ex, ey, ez, 0.38, '#704637');
      }

      const count = 210 + Math.floor(random() * 65);
      for (let i = 0; i < count; i++) {
        const a = random() * Math.PI * 2;
        const r = Math.pow(random(), 0.58);
        const px = x + Math.cos(a) * spread * r + rand(-0.2, 0.2);
        const pz = z + Math.sin(a) * spread * r + rand(-0.2, 0.2);
        const py = height * rand(0.84, 1.22) + Math.sin(a * 3) * 0.32;
        const size = rand(0.38, 0.67);
        addV(px, py, pz, size, size * rand(0.65, 1), size, pick(blossomColors));
      }
    }

    [
      [-24, -15, 5.5, 3.2], [-18, -17.5, 6.2, 3.5],
      [-11, -17, 5.7, 3.1], [-23, -6, 5.2, 2.9],
      [-15, -8, 5.0, 2.7], [-3.1, -17.5, 5.8, 3.2],
      [22, -17.5, 5.2, 2.8]
    ].forEach(t => cherryTree(...t));

    function pineTree(x, z, height = 7, width = 2.3) {
      addV(x, height * 0.38, z, 0.65, height * 0.78, 0.65, '#644132');
      for (let layer = 0; layer < 6; layer++) {
        const t = layer / 5;
        const w = width * (1 - t * 0.64);
        const y = height * (0.42 + t * 0.52);
        const color = pick(['#285b4b', '#326b55', '#3a765a', '#2f6550']);
        addV(x, y, z, w, 0.65, w, color);
        addV(x, y + 0.22, z, w * 0.78, 0.35, w * 0.78,
          pick(['#4c805e', '#396f52', '#5a8d62']));
      }
      addV(x, height * 1.03, z, 0.7, 1.3, 0.7, '#356b51');
    }

    [
      [-26, -20, 6.3, 2], [-20, -21, 7.1, 2.2],
      [-5, -21, 6.8, 2.2], [25, -18, 7.4, 2.4],
      [25, -3, 6.1, 2.0], [-26, -1, 6.8, 2.1]
    ].forEach(t => pineTree(...t));

    function mapleTree(x, z) {
      branchSteps(x, 0.1, z, x + 0.2, 3.6, z, 0.6, '#714735');
      for (let i = 0; i < 5; i++) {
        const a = i / 5 * Math.PI * 2;
        branchSteps(x, 2.7, z, x + Math.cos(a) * 1.8, 4.3, z + Math.sin(a) * 1.8,
          0.3, '#764637');
      }
      const colors = ['#d9673f', '#ec8b45', '#bb4c38', '#f0a34d'];
      for (let i = 0; i < 130; i++) {
        const a = random() * Math.PI * 2;
        const r = Math.pow(random(), 0.55) * 2.1;
        addV(
          x + Math.cos(a) * r,
          rand(3.1, 5.1),
          z + Math.sin(a) * r,
          rand(0.35, 0.65), rand(0.35, 0.65), rand(0.35, 0.65),
          pick(colors)
        );
      }
    }

    mapleTree(18, 18.5);
    mapleTree(-2, 19);

    function bambooGrove(x0, z0) {
      for (let i = 0; i < 28; i++) {
        const x = x0 + rand(-3.6, 3.6);
        const z = z0 + rand(-2.7, 2.7);
        const h = rand(4.0, 6.8);
        const color = pick(['#47844f', '#5d9855', '#376f47', '#6aa45b']);

        for (let s = 0; s < 7; s++) {
          addV(x, 0.35 + s * (h / 7), z, 0.22, h / 7 - 0.08, 0.22, color);
        }

        for (let leaf = 0; leaf < 5; leaf++) {
          addV(
            x + rand(-0.65, 0.65),
            h * rand(0.66, 1.05),
            z + rand(-0.65, 0.65),
            rand(0.35, 0.8), 0.12, rand(0.25, 0.6),
            pick(['#4b8a4d', '#6b9d53', '#397447'])
          );
        }
      }
    }

    bambooGrove(-25.5, -11.5);
    bambooGrove(24, -11);

    labelAnchors.push({
      element: makeLabel('Moon Blossom Grove', '7 ancient trees'),
      position: new THREE.Vector3(-15, 10, -16)
    });

    // Pond border stones and lilies.
    const waterSet = new Set(waterCells.map(c => `${c.x},${c.z}`));
    for (const c of waterCells) {
      for (const [dx, dz] of [[1,0],[-1,0],[0,1],[0,-1]]) {
        if (!waterSet.has(`${c.x + dx},${c.z + dz}`) && random() < 0.32) {
          addRock(c.x + dx * 0.75, c.z + dz * 0.75, rand(0.28, 0.52));
        }
      }
    }

    for (let i = 0; i < 24; i++) {
      const x = rand(8, 21);
      const z = rand(6, 14.5);
      if (!isWaterCell(Math.floor(x), Math.floor(z))) continue;
      addV(x, -0.39, z, rand(0.3, 0.62), 0.07, rand(0.3, 0.62), '#5b9b58');
      if (random() < 0.4) {
        addV(x + 0.1, -0.3, z, 0.13, 0.17, 0.13, pick(['#f3a4b0', '#f7d5de']));
      }
    }

    // Reeds at the pond edge.
    for (let i = 0; i < 75; i++) {
      const x = rand(7, 22);
      const z = rand(5, 15);
      if (isWaterCell(Math.floor(x), Math.floor(z))) continue;
      addV(x, rand(0.45, 0.9), z, 0.08, rand(0.65, 1.25), 0.08, '#567c46');
      if (random() < 0.25) {
        addV(x, 1.2, z, 0.15, 0.3, 0.15, '#79523a');
      }
    }

    // -----------------------------------------------------------------------
    // Commit static voxels to a high-performance instanced mesh
    // -----------------------------------------------------------------------

    const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
    const voxelMaterial = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      roughness: 0.88,
      metalness: 0.01
    });

    const voxelMesh = new THREE.InstancedMesh(cubeGeometry, voxelMaterial, voxels.length);
    const dummy = new THREE.Object3D();

    voxels.forEach((v, i) => {
      dummy.position.set(v.x, v.y, v.z);
      dummy.scale.set(v.sx, v.sy, v.sz);
      dummy.rotation.set(0, 0, 0);
      dummy.updateMatrix();
      voxelMesh.setMatrixAt(i, dummy.matrix);
      voxelMesh.setColorAt(i, new THREE.Color(v.color));
    });

    voxelMesh.instanceMatrix.needsUpdate = true;
    voxelMesh.instanceColor.needsUpdate = true;
    voxelMesh.castShadow = true;
    voxelMesh.receiveShadow = true;
    voxelMesh.computeBoundingSphere();
    scene.add(voxelMesh);

    const glowMaterial = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      emissive: 0xff9a3d,
      emissiveIntensity: 0.38,
      roughness: 0.45
    });

    const glowMesh = new THREE.InstancedMesh(cubeGeometry, glowMaterial, glowVoxels.length);
    glowVoxels.forEach((v, i) => {
      dummy.position.set(v.x, v.y, v.z);
      dummy.scale.set(v.sx, v.sy, v.sz);
      dummy.updateMatrix();
      glowMesh.setMatrixAt(i, dummy.matrix);
      glowMesh.setColorAt(i, new THREE.Color(v.color));
    });
    glowMesh.instanceMatrix.needsUpdate = true;
    glowMesh.instanceColor.needsUpdate = true;
    scene.add(glowMesh);

    // Water is also voxel shaped, matching every water tile.
    const waterMaterial = new THREE.MeshPhysicalMaterial({
      color: 0x56aabd,
      emissive: 0x123b46,
      emissiveIntensity: 0.15,
      roughness: 0.22,
      metalness: 0.08,
      transparent: true,
      opacity: 0.82,
      clearcoat: 0.9,
      clearcoatRoughness: 0.18
    });

    const waterMesh = new THREE.InstancedMesh(cubeGeometry, waterMaterial, waterCells.length);
    waterCells.forEach((c, i) => {
      dummy.position.set(c.x, -0.52, c.z);
      dummy.scale.set(0.99, 0.13, 0.99);
      dummy.updateMatrix();
      waterMesh.setMatrixAt(i, dummy.matrix);
    });
    waterMesh.instanceMatrix.needsUpdate = true;
    waterMesh.receiveShadow = true;
    waterMesh.computeBoundingSphere();
    scene.add(waterMesh);

    document.querySelector('#voxel-count').textContent =
      `${(voxels.length + glowVoxels.length).toLocaleString()} voxels`;

    // -----------------------------------------------------------------------
    // Living villagers
    // -----------------------------------------------------------------------

    const people = [];
    const sharedPersonGeometry = new THREE.BoxGeometry(1, 1, 1);
    const personMaterials = new Map();

    function personMaterial(color) {
      if (!personMaterials.has(color)) {
        personMaterials.set(color, new THREE.MeshStandardMaterial({
          color,
          roughness: 0.85
        }));
      }
      return personMaterials.get(color);
    }

    function personPart(group, sx, sy, sz, color, x, y, z) {
      const mesh = new THREE.Mesh(sharedPersonGeometry, personMaterial(color));
      mesh.scale.set(sx, sy, sz);
      mesh.position.set(x, y, z);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      group.add(mesh);
      return mesh;
    }

    function makePerson(robe, hair = '#342c2c', skin = '#e6af7e', hat = false) {
      const group = new THREE.Group();
      const leftLeg = personPart(group, 0.23, 0.58, 0.25, '#39454a', -0.16, 0.35, 0);
      const rightLeg = personPart(group, 0.23, 0.58, 0.25, '#39454a', 0.16, 0.35, 0);
      personPart(group, 0.68, 0.85, 0.4, robe, 0, 1.04, 0);
      personPart(group, 0.19, 0.63, 0.22, robe, -0.45, 1.0, 0);
      personPart(group, 0.19, 0.63, 0.22, robe, 0.45, 1.0, 0);
      personPart(group, 0.49, 0.49, 0.46, skin, 0, 1.72, 0);
      personPart(group, 0.53, 0.18, 0.49, hair, 0, 1.91, -0.015);

      if (hat) {
        personPart(group, 0.9, 0.11, 0.85, '#d3b36b', 0, 2.06, 0);
        personPart(group, 0.55, 0.35, 0.52, '#d3b36b', 0, 2.23, 0);
      }

      return { group, leftLeg, rightLeg };
    }

    const walkPaths = [
      [[-23,3.2,0.12],[-5,3.2,0.12]],
      [[-22,-1,0.12],[-6,-1,0.12]],
      [[4.4,-16,0.12],[4.4,7,0.12]],
      [[-18,17,0.12],[-8,17,0.12]],
      [[-24,5,0.12],[-2,5,0.12]],
      [[4,16.8,0.12],[21,16.8,0.12]],
      [[-7,6,0.12],[2,6,0.12]]
    ];

    const robes = ['#d96453','#497e88','#d6a952','#795d8a','#5c9568','#b75b73','#d17b49'];

    for (let i = 0; i < 18; i++) {
      const person = makePerson(
        pick(robes),
        pick(['#30282a','#47322a','#241f23']),
        pick(['#efbd8e','#c98962','#e5a675','#8c5d45']),
        i % 5 === 0
      );
      person.group.position.set(...walkPaths[i % walkPaths.length][0]);
      scene.add(person.group);
      people.push({
        ...person,
        path: walkPaths[i % walkPaths.length],
        offset: random(),
        speed: rand(0.006, 0.012),
        bobPhase: random() * Math.PI * 2
      });
    }

    // Vendors remain at their stalls.
    [
      [-17.2, 4.0, '#c34e44', true],
      [-11.8, 4.0, '#5b7f72', true],
      [1.1, 3.2, '#c07a3f', false],
      [-7.3, 7.5, '#6e8491', false],
      [18.5, 2.2, '#a45163', true]
    ].forEach(([x,z,color,hat]) => {
      const p = makePerson(color, '#342b2a', '#dca276', hat);
      p.group.position.set(x, 0.12, z);
      p.group.rotation.y = Math.PI;
      scene.add(p.group);
    });

    function samplePath(points, t) {
      const lengths = [];
      let total = 0;
      for (let i = 0; i < points.length - 1; i++) {
        const length = Math.hypot(
          points[i + 1][0] - points[i][0],
          points[i + 1][2] - points[i][2]
        );
        lengths.push(length);
        total += length;
      }

      let distance = ((t % 1) + 1) % 1 * total;
      for (let i = 0; i < lengths.length; i++) {
        if (distance <= lengths[i]) {
          const f = distance / lengths[i];
          const a = points[i];
          const b = points[i + 1];
          return {
            x: lerp(a[0], b[0], f),
            y: lerp(a[1], b[1], f),
            z: lerp(a[2], b[2], f),
            angle: Math.atan2(b[0] - a[0], b[2] - a[2])
          };
        }
        distance -= lengths[i];
      }

      return { x: points[0][0], y: points[0][1], z: points[0][2], angle: 0 };
    }

    // -----------------------------------------------------------------------
    // Koi, waterfall, petals, fireflies, smoke, birds and clouds
    // -----------------------------------------------------------------------

    const koi = [];
    for (let i = 0; i < 6; i++) {
      const group = new THREE.Group();
      personPart(group, 0.55, 0.16, 0.2, i % 2 ? '#f18b55' : '#f5d6b3', 0, 0, 0);
      personPart(group, 0.22, 0.14, 0.18, '#f5eee0', -0.34, 0, 0);
      personPart(group, 0.25, 0.12, 0.2, i % 2 ? '#e65f43' : '#df7751', 0.38, 0, 0);
      group.position.set(14, -0.34, 10);
      scene.add(group);
      koi.push({
        group,
        rx: rand(2.2, 6.2),
        rz: rand(1.3, 4.3),
        speed: rand(0.1, 0.18) * (i % 2 ? 1 : -1),
        phase: random() * Math.PI * 2,
        cx: 14.5,
        cz: 10.2
      });
    }

    // Waterfall voxels on the western cliff.
    const waterfallData = [];
    for (let i = 0; i < 72; i++) {
      waterfallData.push({
        x: -27.75 + rand(-0.7, 0.7),
        z: -10 + rand(-1.5, 1.5),
        y: rand(-4, 0.7),
        speed: rand(0.025, 0.05),
        phase: random()
      });
    }

    const waterfallMat = new THREE.MeshBasicMaterial({
      color: 0xa7e8f2,
      transparent: true,
      opacity: 0.66
    });
    const waterfallMesh = new THREE.InstancedMesh(
      cubeGeometry,
      waterfallMat,
      waterfallData.length
    );
    waterfallMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    scene.add(waterfallMesh);

    // Falling sakura petals.
    const petals = [];
    const petalMaterial = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      roughness: 0.7,
      emissive: 0x5b172b,
      emissiveIntensity: 0.12,
      side: THREE.DoubleSide
    });

    for (let i = 0; i < 150; i++) {
      petals.push({
        x: rand(-32, 32),
        y: rand(0, 23),
        z: rand(-28, 28),
        phase: random() * Math.PI * 2,
        speed: rand(0.55, 1.15),
        size: rand(0.11, 0.24)
      });
    }

    const petalMesh = new THREE.InstancedMesh(cubeGeometry, petalMaterial, petals.length);
    petalMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    petals.forEach((p, i) => petalMesh.setColorAt(i, new THREE.Color(pick(blossomColors))));
    petalMesh.instanceColor.needsUpdate = true;
    scene.add(petalMesh);

    // Fireflies use tiny emissive voxel cubes.
    const fireflies = [];
    for (let i = 0; i < 48; i++) {
      fireflies.push({
        x: rand(-24, 23),
        y: rand(1, 7.5),
        z: rand(-21, 20),
        phase: random() * Math.PI * 2,
        speed: rand(0.3, 0.8)
      });
    }
    const fireflyMat = new THREE.MeshBasicMaterial({
      color: 0xffe58a,
      transparent: true,
      opacity: 0.9
    });
    const fireflyMesh = new THREE.InstancedMesh(cubeGeometry, fireflyMat, fireflies.length);
    fireflyMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    scene.add(fireflyMesh);

    // Chimney smoke.
    const smokeSources = [
      new THREE.Vector3(-18.6, 5.0, 9.2),
      new THREE.Vector3(-11.2, 5.4, 10.5),
      new THREE.Vector3(5.5, 4.5, 5.9)
    ];
    const smokeData = [];
    smokeSources.forEach(source => {
      for (let i = 0; i < 11; i++) {
        smokeData.push({ source, i, phase: random() * 6 });
      }
    });
    const smokeMat = new THREE.MeshBasicMaterial({
      color: 0xd9e1de,
      transparent: true,
      opacity: 0.22,
      depthWrite: false
    });
    const smokeMesh = new THREE.InstancedMesh(cubeGeometry, smokeMat, smokeData.length);
    smokeMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    scene.add(smokeMesh);

    // Voxel birds.
    const birds = [];
    for (let i = 0; i < 7; i++) {
      const group = new THREE.Group();
      addBoxMesh(group, 0.7, 0.13, 0.25, '#34494d', 0, 0, 0);
      const left = addBoxMesh(group, 0.8, 0.12, 0.18, '#34494d', -0.5, 0, 0);
      const right = addBoxMesh(group, 0.8, 0.12, 0.18, '#34494d', 0.5, 0, 0);
      scene.add(group);
      birds.push({ group, left, right, offset: i * 1.7, height: rand(13, 19) });
    }

    // Chunky clouds.
    const clouds = [];
    for (let i = 0; i < 5; i++) {
      const group = new THREE.Group();
      const pieces = 9 + Math.floor(random() * 7);
      for (let p = 0; p < pieces; p++) {
        addBoxMesh(
          group,
          rand(2.0, 4.2),
          rand(0.7, 1.35),
          rand(1.6, 3.5),
          pick(['#eef4ed', '#e4ece7', '#f5f1e6']),
          rand(-4, 4),
          rand(-0.3, 0.5),
          rand(-1.5, 1.5)
        );
      }
      group.position.set(rand(-55, 45), rand(23, 31), rand(-55, -30));
      group.scale.setScalar(rand(0.7, 1.15));
      scene.add(group);
      clouds.push({ group, speed: rand(0.12, 0.25) });
    }

    // -----------------------------------------------------------------------
    // Sky stars and voxel sun/moon
    // -----------------------------------------------------------------------

    const stars = [];
    for (let i = 0; i < 190; i++) {
      const angle = random() * Math.PI * 2;
      const radius = rand(65, 95);
      stars.push({
        x: Math.cos(angle) * radius,
        y: rand(15, 65),
        z: Math.sin(angle) * radius,
        size: rand(0.08, 0.22)
      });
    }

    const starMaterial = new THREE.MeshBasicMaterial({
      color: 0xdff6ff,
      transparent: true,
      opacity: 0
    });
    const starMesh = new THREE.InstancedMesh(cubeGeometry, starMaterial, stars.length);
    stars.forEach((s, i) => {
      dummy.position.set(s.x, s.y, s.z);
      dummy.scale.set(s.size, s.size, s.size);
      dummy.updateMatrix();
      starMesh.setMatrixAt(i, dummy.matrix);
    });
    scene.add(starMesh);

    function createSkyOrb(x, y, z, color) {
      const group = new THREE.Group();
      const mat = new THREE.MeshBasicMaterial({ color, transparent: true });
      const cells = [
        [-1,1],[0,1],[1,1],[-2,0],[-1,0],[0,0],[1,0],[2,0],
        [-2,-1],[-1,-1],[0,-1],[1,-1],[2,-1],[-1,-2],[0,-2],[1,-2]
      ];
      for (const [cx, cy] of cells) {
        const mesh = new THREE.Mesh(new THREE.BoxGeometry(0.78, 0.78, 0.3), mat);
        mesh.position.set(cx * 0.74, cy * 0.74, 0);
        group.add(mesh);
      }
      group.position.set(x, y, z);
      scene.add(group);
      return group;
    }

    const sunOrb = createSkyOrb(-45, 37, -68, 0xffd27a);
    const moonOrb = createSkyOrb(43, 35, -67, 0xd9efff);
    moonOrb.visible = false;

    // -----------------------------------------------------------------------
    // Labels
    // -----------------------------------------------------------------------

    function makeLabel(title, subtitle) {
      const el = document.createElement('div');
      el.className = 'world-label';
      el.innerHTML = `${title}<small>${subtitle}</small>`;
      el.style.display = 'none';
      document.querySelector('#labels').appendChild(el);
      return el;
    }

    // The pagoda label was requested before its DOM function was defined.
    // Rebuilding the small list is unnecessary: createLabel below is hoisted as
    // a function declaration, so the original calls above work correctly.

    let labelsVisible = false;
    const projected = new THREE.Vector3();

    function updateLabels() {
      for (const label of labelAnchors) {
        if (!labelsVisible) {
          label.element.style.display = 'none';
          continue;
        }

        projected.copy(label.position).project(camera);
        const visible =
          projected.z > -1 && projected.z < 1 &&
          projected.x > -1.15 && projected.x < 1.15 &&
          projected.y > -1.15 && projected.y < 1.15;

        if (!visible) {
          label.element.style.display = 'none';
          continue;
        }

        label.element.style.display = 'block';
        label.element.style.left = `${(projected.x * 0.5 + 0.5) * innerWidth}px`;
        label.element.style.top = `${(-projected.y * 0.5 + 0.5) * innerHeight}px`;
        label.element.style.opacity = String(
          clamp(1 - Math.abs(projected.z - 0.7), 0.25, 1)
        );
      }
    }

    // -----------------------------------------------------------------------
    // UI behavior
    // -----------------------------------------------------------------------

    let night = false;
    let nightBlend = 0;
    let petalsEnabled = true;
    let cinematic = false;

    const themeBtn = document.querySelector('#themeBtn');
    const themeIcon = document.querySelector('#themeIcon');
    const themeText = document.querySelector('#themeText');
    const timeLabel = document.querySelector('#time-label');
    const petalBtn = document.querySelector('#petalBtn');
    const labelBtn = document.querySelector('#labelBtn');
    const cinemaBtn = document.querySelector('#cinemaBtn');
    const resetBtn = document.querySelector('#resetBtn');

    function toggleTheme() {
      night = !night;
      document.body.classList.toggle('night', night);
      themeBtn.classList.toggle('active', !night);
      themeIcon.textContent = night ? '☀' : '☾';
      themeText.textContent = night ? ' Day' : ' Night';
      timeLabel.textContent = night ? 'Moonlit evening' : 'Golden daylight';
      moonOrb.visible = night;
    }

    themeBtn.addEventListener('click', toggleTheme);

    petalBtn.addEventListener('click', () => {
      petalsEnabled = !petalsEnabled;
      petalBtn.classList.toggle('active', petalsEnabled);
      petalMesh.visible = petalsEnabled;
    });

    labelBtn.addEventListener('click', () => {
      labelsVisible = !labelsVisible;
      labelBtn.classList.toggle('active', labelsVisible);
    });

    cinemaBtn.addEventListener('click', () => {
      cinematic = !cinematic;
      controls.autoRotate = cinematic;
      cinemaBtn.classList.toggle('active', cinematic);
    });

    resetBtn.addEventListener('click', () => {
      camera.position.set(54, 43, 60);
      controls.target.set(0, 4, 0);
      controls.update();
    });

    window.addEventListener('keydown', event => {
      if (event.key.toLowerCase() === 'n') toggleTheme();
      if (event.key.toLowerCase() === 'l') labelBtn.click();
      if (event.key.toLowerCase() === 'p') petalBtn.click();
      if (event.key.toLowerCase() === 'c') cinemaBtn.click();
      if (event.key.toLowerCase() === 'r') resetBtn.click();
    });

    canvas.addEventListener('dblclick', resetBtn.click);

    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.65));
    });

    // -----------------------------------------------------------------------
    // Animation
    // -----------------------------------------------------------------------

    const clock = new THREE.Clock();
    const dayBackground = new THREE.Color(0xb7dce4);
    const nightBackground = new THREE.Color(0x102d46);
    const dayFog = new THREE.Color(0xb7dce4);
    const nightFog = new THREE.Color(0x17374d);
    const daySun = new THREE.Color(0xfff1c5);
    const nightSun = new THREE.Color(0xa9caff);
    const dayHemi = new THREE.Color(0xdff7ff);
    const nightHemi = new THREE.Color(0x7ea6dc);

    function updateDayNight(delta) {
      nightBlend = lerp(nightBlend, night ? 1 : 0, 1 - Math.pow(0.025, delta));
      scene.background.copy(dayBackground).lerp(nightBackground, nightBlend);
      scene.fog.color.copy(dayFog).lerp(nightFog, nightBlend);
      sun.color.copy(daySun).lerp(nightSun, nightBlend);
      sun.intensity = lerp(3.6, 1.15, nightBlend);
      sun.position.x = lerp(-25, 32, nightBlend);
      hemi.color.copy(dayHemi).lerp(nightHemi, nightBlend);
      hemi.intensity = lerp(2.15, 0.8, nightBlend);
      rim.intensity = lerp(0.85, 1.2, nightBlend);
      glowMaterial.emissiveIntensity = lerp(0.38, 2.2, nightBlend);
      oceanMaterial.color.set(0x4b9db4).lerp(new THREE.Color(0x244a65), nightBlend);
      waterMaterial.color.set(0x56aabd).lerp(new THREE.Color(0x285c78), nightBlend);
      fireflyMat.opacity = nightBlend * 0.95;
      starMaterial.opacity = nightBlend * 0.9;
      renderer.toneMappingExposure = lerp(1.08, 0.9, nightBlend);
    }

    function updatePeople(time) {
      for (const person of people) {
        const t = person.offset + time * person.speed;
        const pos = samplePath(person.path, t);
        person.group.position.set(pos.x, pos.y + Math.abs(Math.sin(time * 5 + person.bobPhase)) * 0.035, pos.z);
        person.group.rotation.y = pos.angle;

        const stride = Math.sin(time * 5.2 + person.bobPhase) * 0.48;
        person.leftLeg.rotation.x = stride;
        person.rightLeg.rotation.x = -stride;
      }
    }

    function updateKoi(time) {
      for (const fish of koi) {
        const a = time * fish.speed + fish.phase;
        const x = fish.cx + Math.cos(a) * fish.rx;
        const z = fish.cz + Math.sin(a) * fish.rz;
        fish.group.position.set(x, -0.34 + Math.sin(time * 2 + fish.phase) * 0.025, z);
        fish.group.rotation.y = -a + (fish.speed > 0 ? Math.PI / 2 : -Math.PI / 2);
      }
    }

    function updateWaterfall(time) {
      waterfallData.forEach((p, i) => {
        const y = 0.8 - (((time * p.speed + p.phase) % 1) * 5.2);
        dummy.position.set(p.x, y, p.z);
        dummy.scale.set(0.22, 0.42, 0.22);
        dummy.rotation.set(0, 0, 0);
        dummy.updateMatrix();
        waterfallMesh.setMatrixAt(i, dummy.matrix);
      });
      waterfallMesh.instanceMatrix.needsUpdate = true;
    }

    function updatePetals(time) {
      if (!petalsEnabled) return;
      petals.forEach((p, i) => {
        const y = ((p.y - time * p.speed + 30) % 26) + 0.2;
        dummy.position.set(
          p.x + Math.sin(time * 0.65 + p.phase) * 2.1,
          y,
          p.z + Math.cos(time * 0.4 + p.phase) * 1.1
        );
        dummy.rotation.set(time * 0.7 + p.phase, time + p.phase, time * 0.4);
        dummy.scale.set(p.size, p.size * 0.35, p.size * 0.65);
        dummy.updateMatrix();
        petalMesh.setMatrixAt(i, dummy.matrix);
      });
      petalMesh.instanceMatrix.needsUpdate = true;
    }

    function updateFireflies(time) {
      fireflies.forEach((f, i) => {
        dummy.position.set(
          f.x + Math.sin(time * f.speed + f.phase) * 1.2,
          f.y + Math.sin(time * f.speed * 1.7 + f.phase) * 0.45,
          f.z + Math.cos(time * f.speed + f.phase) * 1.1
        );
        const pulse = 0.65 + Math.sin(time * 3.2 + f.phase) * 0.35;
        dummy.scale.setScalar(0.09 * pulse);
        dummy.rotation.set(0, time + f.phase, 0);
        dummy.updateMatrix();
        fireflyMesh.setMatrixAt(i, dummy.matrix);
      });
      fireflyMesh.instanceMatrix.needsUpdate = true;
    }

    function updateSmoke(time) {
      smokeData.forEach((p, i) => {
        const t = (time * 0.16 + p.phase) % 1;
        dummy.position.set(
          p.source.x + Math.sin(t * 5 + p.i) * t * 0.8,
          p.source.y + t * 3.2,
          p.source.z + t * 0.55
        );
        dummy.scale.setScalar(0.18 + t * 0.62);
        dummy.rotation.set(t, t * 0.4, 0);
        dummy.updateMatrix();
        smokeMesh.setMatrixAt(i, dummy.matrix);
      });
      smokeMesh.instanceMatrix.needsUpdate = true;
    }

    function updateBirds(time) {
      for (const bird of birds) {
        const x = ((time * 1.25 + bird.offset + 60) % 120) - 60;
        bird.group.position.set(x, bird.height + Math.sin(time + bird.offset), -8);
        bird.group.rotation.y = -0.15;
        const flap = Math.sin(time * 6 + bird.offset) * 0.35;
        bird.left.rotation.z = flap;
        bird.right.rotation.z = -flap;
      }
    }

    function updateClouds(time, delta) {
      for (const cloud of clouds) {
        cloud.group.position.x += cloud.speed * delta;
        if (cloud.group.position.x > 68) cloud.group.position.x = -68;
      }
    }

    function animate() {
      const delta = Math.min(clock.getDelta(), 0.05);
      const time = clock.elapsedTime;

      controls.update();
      updateDayNight(delta);
      updatePeople(time);
      updateKoi(time);
      updateWaterfall(time);
      updatePetals(time);
      updateFireflies(time);
      updateSmoke(time);
      updateBirds(time);
      updateClouds(time, delta);

      sunOrb.rotation.y = Math.sin(time * 0.04) * 0.08;
      moonOrb.rotation.y = -Math.sin(time * 0.035) * 0.08;
      boat.position.y = -0.01 + Math.sin(time * 1.25) * 0.035;
      boat.rotation.z = Math.sin(time * 0.8) * 0.012;

      waterMaterial.emissiveIntensity = lerp(0.12, 0.28, Math.sin(time * 0.7) * 0.5 + 0.5);

      updateLabels();
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
    }

    // Let the first frame render before removing the loader.
    requestAnimationFrame(() => {
      renderer.render(scene, camera);
      setTimeout(() => {
        document.querySelector('#loading').classList.add('hidden');
      }, 420);
      animate();
    });
  </script>
</body>
</html>
0.76s
首Token
实际等待 1.02s
80
思考TPS
98.7s · 7,871 tok
190
输出TPS
112s · 21,413 tok
29,284
总Tokens(官方)
输入 279
计时详情
实际等待(客户端)
1.02s
客户端请求准备
0.01s
本站代理准备
0.00s
上游响应头到达
0.76s
上游首字节到达
0.76s
上游首 Token 到达
0.76s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-30T07:13:42.460Z",
  "transport": "railway",
  "ttftSource": "proxy",
  "clientTtftMs": 1016.0999999046326,
  "completionId": "070be9a6fb8afade4ca7f8feaefd1509",
  "streamResumes": 4,
  "proxyPrepareMs": 1,
  "proxyRequestId": "IfQQ59i6Rh2OaraRn6XIxQ",
  "upstreamStatus": 200,
  "upstreamTtftMs": 758,
  "clientHeadersMs": 251.40000009536743,
  "clientPrepareMs": 11.5,
  "upstreamHeadersMs": 756,
  "upstreamFirstByteMs": 758,
  "clientFirstContentMs": 99669.2000002861
}
总用时 212s · 平均 139 tok/s · 峰值 350 tok/s
SB
Space Bunny Alpha
stealth/space-bunny-alpha
已停止

新增测试 · 2026/9/30 07:12:47

We need respond with single HTML file likely code. Need design elaborate voxel art scene pagoda garden trees cherry blossoms village people. Need perhaps use Three.js? Single HTML, can use CDN? "Create a single HTML file." We can provide full HTML perhaps using Canvas WebGL. Need impressive colorful voxels, many. We need decide implementation. Could create self-contained HTML with vanilla Canvas 2D isometric voxel rendering, no external dependencies. But user explicitly voxel art scene. Need detailed, interactive, elaborate. A single HTML file. We can write complete HTML code in final. Need maybe avoid too huge but desired detailed. Could leverage Three.js CDN, but single HTML might require internet, not ideal. Could create WebGL/canvas procedural. More impressive to use Three.js with instanced meshes? Yet "use as many voxels as you want" suggests performance. We can include script module importing Three.js CDN. But then not fully self-contained. Single file can be external dependency though. Better self-contained vanilla WebGL renderer? Could implement voxel ray-ish with canvas isometric. But "impressive" maybe 3D rotating camera, shadows, controls, day/night toggle. Three.js easiest. Need formulate answer likely just code. Must ensure no markdown? They ask create a single HTML file. We can provide code fence with instructions save index.html. Need perhaps include one HTML file using external CDN. Could use import map and three.js. But no OrbitControls if only module. Could implement orbit controls manually. Better use Three.js r160 from jsDelivr + OrbitControls module; import maps in HTML. Single HTML though dependencies. Could also use CSS and JS procedural instancing. Need create elaborate voxel scene: - Terrain island/grounds with water, paths. - Main pagoda central multi-tier, detailed eaves, red pillars, white walls, dark teal roofs, gold finials. Maybe five storeys. - Garden with cherry trees, pines, maples, bamboo, pond with koi, stone lanterns, bridges, rocks, flowers. - Village around with 10-15 houses, varied roofs, market stalls, paths, rice paddies maybe. - People: stylized voxel villagers with different clothing, walking animation. - Fireflies, petals, smoke, banners, boats. - Camera rotation zoom, cinematic mode, day/night. - UI with title and buttons. Attractive overlay, loading. - Colorful voxels via InstancedMesh for performance. Need build efficient. Three.js scene: renderer antialias maybe high performance; shadows. Directional lights. Fog. Voxel primitives using InstancedMesh grouped by geometry and material perhaps. We can have global builders: const voxels = Map key geometry uuid/material color? Each addVox creates matrix and color. InstancedMesh supports vertexColors if material vertexColors true. We can group by geometry key and perhaps use one material MeshLambertMaterial({vertexColors:true}). For BoxGeometry all boxes. Other shapes: cylinders low radial segments voxel-looking, cone roofs? But user asks voxels. Can use boxes only, and custom pyramid roof made of stacked shrinking rectangles. Cubes. Instanced boxes. We can use one global InstancedMesh with per instance color; potentially tens thousands cubes. Each box individually 12 triangles, 50k cubes = 600k tri manageable. But updating dynamic people individually if static instanced impossible. Create individual groups for dynamic persons (few dozen) with box meshes. Or maintain transforms in global instance buffers each frame for animated items. Better for water, people etc, separate meshes. Voxel box dimensions can scale, so one BoxGeometry. addVox(x,y,z,sx,sy,sz,color,ry=0) matrix with rotation, cast/receive shadows. Instanced mesh with max maybe 120000. If static, shadow map. Need build after scene construction. For dynamic, setDynamicVox with id? Could create objects. Could create all static via a `VoxelBatch` that stores matrix elements/colors, builds mesh at end. Add box. For rotated boxes, quaternion. Need all boxes axis or y rotation. Some roofs perhaps rotate around y. Good. For animated, define array dynamicObjects, each object with local voxels around origin; build a group with Mesh? 25 people x 7 = 175 meshes, okay, but draw calls huge. Could use a single dynamic InstancedMesh rebuilt for dynamic objects. Or use global matrices and update setMatrixAt; use static + dynamic batched. But water and petals can Points. Architecture: - batch: static array of {matrix Float32Array, color THREE.Color} - dyn records with x,y,z, scale, color, local matrix; update recomposes world = groupTransform * local. - create two instanced meshes static and dynamic. - Need shadows flags. `MeshStandardMaterial({vertexColors:true, roughness:0.85, metalness:0.0})`; instancedColor. - Water maybe large blue plane or voxels. Use semi-transparent water tiles at y 0.05? Large plane. - Terrain perhaps broad circular/irregular island at z? Coordinates. We can use 2D x-z plane, y up. Main pagoda centered perhaps (0,0,0). Village to front/right. Garden left/back. Need scenic composition. Coordinate design: Scene world extents x [-70,70], z [-58,58], y 0-45. Island terrain irregular, raised stone/grass. Water plane y=-0.8. Terrain made from voxel columns? To show voxel sides, use cells maybe 2x2. This could be 3k blocks. For every grid x,z within island shape, add box 2x varying height? Grass top. But if one block 2x2, can set sx=2 sy=top-base sz=2 with grass color, gives side green. Better layered: dirt base + green top cube. Number ~2500. Add path cells replacing top. Island shape function. Add water plane. Shoreline stone ring. Main pagoda at x=0,z=-7 maybe. Five tiers. Platform width 22. Each level y base 4, 10, 16,22,28? Height around 34. Tier footprint decreasing 18,16,14,12,10. White/plaster body, red columns, windows teal, balconies, railing. Roofs at y 9,15,21,27,33 using stacked slabs: dark teal roof cuboids decreasing width as y rises to create hip roof. But Japanese pagoda roofs have upturned eaves: build square rings as voxels. Roof consists: for layer t=0..4, width = tierW + (4-layer)*2 maybe bottom widest, h=.6; add full box slab. But filled rectangles. Add corners extended; edges in darker trim. At top add golden spire. Could add roof tile stripes as small blocks, but huge maybe manageable. For each tier, add roof slab rows. Use edge bright/dark. To mimic eaves, 3-4 layers: bottom width w+6, next w+4, next w+2, top w. Add box. This is stepped pyramid. Upturned corners by little cubes with positions ±(w/2+2) and y+ etc; red underside. Roof color dark indigo #164c5a, accents teal. Each tier maybe three roof layers. Body: base stone; cream walls; vermilion columns; balcony dark wood, railings. Use boxes. Stair entrance front z positive. Add torii maybe front of pagoda at z 15? Path. Garden: cherry blossom trees around left/pond. Tree trunks brown branching boxes, canopies clusters pink/light pink/white. Use 15-20 trees. Evergreen pines maybe cone voxel tiers. Maple orange. Bamboo grove. Flower beds and shrubs. Pond irregular shape left/front perhaps centered (-27,14) radius 18x13. Use water plane maybe elliptical via boxes clipped? Use water tiles (blue) and sand edge; koi as little red/orange dynamic/static. Stone bridge across. Pagoda central could be reflected in water. Village on right x 20-50, z -10..35, 14 houses. Houses on platforms, plaster walls, dark or orange roofs, wood beams, windows lit, doors. Need roads winding. Market stalls with awnings red/blue/yellow, crates. People. Perhaps shrine/gate. Paths: use terrain height. Need set ground y 0.0 top. Houses y .3 etc. Village fields back right with rows of crops. Terrain: Water plane 200. island shape. Need addVox for each 2.2 grid cell within e.g. ellipse/radial irregular. Determine top = maybe 0.2 + noise slight; add dirt box from y=-1.4 to top (center). Add top grass cap 0.25. Paths override based on function: - central main path from front z=47 to pagoda z=2, x around 0 with sin. - path to village on right. - paths to pond/garden. For each cell within path, top color warm beige and maybe paving blocks. Add top color. Stone stepping path maybe add individual. If path inside pagoda plaza, plaza separately. Island shape: for grid x=-66 to 66 step 2, z=-56..56. condition normalized ellipse + sin; top. Some extra rocks around. Need ensure z direction and camera initial position (70,55,85) looking at 0,10,0, so front is +z, right world x. Main pagoda center maybe z=-7, village +x + maybe 20. Pond at -25, +12. Cherry trees mostly back/left. Houses right. Could use addVox for terrain all as scaled boxes, maybe 4000. Static count likely: terrain ~4000 pagoda ~1500 trees 2000 houses 2500 decor 2000 => 12k cubes. Good. But all instanced mesh can only set colors with material vertexColors. Three uses instanceColor and MeshStandardMaterial. No need vertexColors? I think if setColorAt creates instanceColor, shader multiplies. `vertexColors` does not need true? `InstancedMesh.setColorAt` works if material vertexColors? likely yes. In recent three, instanceColor applied independent of vertexColors define? `USE_INSTANCING_COLOR`. Set material color white. Fine. We can set `vertexColors: true`. For single geometry has no color attribute though shader maybe expects color attribute only if vertexColors true. Instance color may still work but perhaps color attribute missing? The geometry has default? In shader, `USE_COLOR_ALPHA` if geometry has color? `USE_INSTANCING_COLOR` based on object.instanceColor. It multiplies vColor. `vertexColors: true` might define USE_COLOR but missing attribute? Better create BoxGeometry and `setAttribute('color', new Float32BufferAttribute([1,1,1...],3))`, material vertexColors true. Or simply not set vertexColors and instance colors still supported? Looking at Three WebGLPrograms, instancingColor is object.instanceColor !== null, defines USE_INSTANCING_COLOR. Color varying always. It multiplies. Material vertexColors false should still use instance color. likely. Need maybe use `frustumCulled=false` due global. Add staticInstanced castShadow/receiveShadow. Dynamic: Could create `DynamicBatch` with entries, one InstancedMesh. Each record {baseX, baseY, baseZ, yaw, local: [{x,y,z,sx,sy,sz,c}], phase, speed}. update sets matrix world*local. We can update colors only once. `DynamicBuilder` addRecord. At end instantiate with count. each update: objDummy.position.set(local.x, local.y, local.z); scale; quaternion y yaw+anim; then localMatrix. objDummy update; create world matrix via compose with position, quat base, scale one. multiply. Could use matrix arrays. People: 25, body components 8 each. Walk path segments. Each person can move between endpoints and rotate. Update at frame; legs swing. Use record with custom update. But dynamic batch expects local matrices and animated rotations per part. We'll create per-person own InstancedMesh? We can create a reusable voxel-person geometry built as children? 30 people x ~10 Mesh = 300 draw calls, maybe okay but not ideal. Better dynamic batch update custom. Define dynamicBuilder records with component list. Each component has position, scale, color and optional `part` (armL etc). update callback(person, t) computes world transform and parts, pushing matrices. But InstancedMesh count fixed equal number of local parts. Can build dynamic list. Each dynamic "actor" has parts; update sets matrix. Need allocate matrix per part. Simpler: const dynamicParts = []; each has actor and local. At end count parts. update loops actors, actor.update() returns base pos/yaw; loops its parts; transforms. `DynamicSystem.add(parts, updateFn)` returns actor. Components have x,y,z,sx,sy,sz,color, animate function maybe returns offsets/rotation. Could directly in person update modify component transform state. More code. Alternative individual groups, each voxel uses shared geometry and cached material by color. 40 people * 9 = 360 draw calls, plus environment one draw. Machines can handle. 60fps maybe fine, but shadows on 360 draw calls expensive. Use no cast shadows for people or set. Yet only 1k cubes. Good. For each person, create `Group`, children Mesh with shared `BoxGeometry`, `MeshStandardMaterial` palette. This is simple. Houses static. 25 people. Add limbs as named. update position/rotation, leg rotations. Use fixed number. We can cache materials globally. Petal particles Points not voxels, but okay as ambience. Koi individual groups maybe static/dynamic. Boats perhaps one group. Water shader? Use plane with `MeshPhysicalMaterial` and wave via changing vertices. Could create large plane and manually animate. But island over it. Sky gradient via large sphere ShaderMaterial? Self-contained. Could create canvas texture gradient mapped to sphere? no issue. `scene.background = Color`, fog. Better add sky dome with vertex colors? SphereGeometry large with shader based y, and sun disc. Or scene background gradient via CSS not visible if renderer clear alpha true? Can set renderer alpha and CSS radial gradient. But 3D fog. use background #a9d9e6. Add sun sprite (circle) in scene with basic material. Clouds: voxel clusters white boxes at y 40 and around. They are static batches but no shadows maybe. Add to voxel batch. They rotate slowly? Static okay. Cherry petal particles: THREE.Points with 800 points in pink, update falling positions. Pixel-like points using canvas square texture or PointsMaterial size 0.18, sizeAttenuation true; particles fall around cherry area, use positions. Could use tiny instanced cubes? 400 dynamic cubes would be one instanced mesh. Better Points as square (PointsMaterial likely square). Fine. Fireflies at night points yellow. Water: plane 240 x 220 segments 64, `MeshStandardMaterial({color:#43aebb, roughness:.25, metalness:.05, transparent:true, opacity:.88})`; rotate x -PI/2; update z? Plane geometry local x/y then rotation. For wave animate position attr. But pond needs water only, not surrounding? Large sea plane under island. Also pond overlay as ellipse at y=0.35? If water at -0.7, pond needs carve terrain / water. Terrain generation must omit / lower cells in pond shape. We'll create pond cells: for grid inside ellipse center(-27,14), radii (19,13), don't add land (or add base at y=-1.5, with water). Add water tiles blue at y=-0.1 maybe each 2x2 with slight colors; add shore sand around. Pond bridge across x direction? Bridge at z=14 spanning x -47 to -7, arch y 1.4-3.0. Add wooden planks and rails. Torii? Main front path. Pagoda maybe at x=0 z=-9. Garden detailed: - Pond x=-27,z=14. - rock garden left back x=-26,z=-22. - bamboo grove x=-47,z=-25. - cherry trees positions [(-17,-31),(-30,-38),(-43,-12),(-15,0?),(-41,31),(-10,28)] avoid pond/paths/pagoda. - pine trees. - stone lanterns (x coords). - benches maybe. - torii gate to pagoda at z=12? But pond left. Main path from front (z=44) to pagoda. Torii at z=13, x=0. - pagoda base x=0,z=-7. - village to right from x=20 to 53, z -30 to 35. Need houses not colliding. - path branches. - plaza and shrine steps. - village central square x=28,z=18, market. - houses positions explicit, varied. - crops field x=47,z=-25 maybe behind village. - windmill? maybe rice barn. - watchtower? no. Need structure code. Could be maybe 1500 lines. User wants elaborate. We can deliver ~800-1000 lines. Need balance output token limit, can use procedural loops to make many details. Potential browser compatibility: import modules from CDN. Need internet. Maybe can use `https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js` and OrbitControls. Direct import of OrbitControls uses bare `three`, requiring importmap. Use importmap: <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.161.0/examples/jsm/"}}</script> Then script type=module import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; Version consistent maybe 0.160.0. Addon path same version. Use jsdelivr. Fine. If user expects file only maybe external libraries okay. Could say requires internet to load Three.js. But "single HTML file" likely accepted. Could fully self-contained by including custom WebGL impossible. We can mention. Better perhaps use no external dependencies via CSS isometric renderer. But interactivity can be done. Yet single HTML truly self-contained is a stronger interpretation. Let's evaluate. We can create a voxel scene in HTML Canvas 2D with isometric projection and thousands of blocks. Could render pre
这次运行已结束,但厂商没有返回最终正文流,只返回了 reasoning / 中间推理 token。上方「思考过程」已自动展开,避免结果区显示为空。
0.88s
首Token
实际等待 2.21s
90
思考TPS
46.1s · ≈4,151 tok
—
输出TPS
4,151
总Tokens(估算)
计时详情
实际等待(客户端)
2.21s
客户端请求准备
0.24s
本站代理准备
0.84s
上游响应头到达
0.88s
上游重试次数
0

实际等待使用客户端时钟;上游计时从本站准备完成后开始,各项为累计耗时,不能直接相加。上游等待包含网络、调度、计算及缓冲,不能仅凭这些数值断定厂商排队。

诊断记录
{
  "attempts": 1,
  "startedAt": "2026-09-30T07:12:48.374Z",
  "transport": "railway",
  "ttftSource": "proxy",
  "clientTtftMs": 2206.5,
  "streamResumes": 1,
  "proxyPrepareMs": 843,
  "proxyRequestId": "8j2EG0ryRJq--gMIwUFZXw",
  "upstreamStatus": 200,
  "clientHeadersMs": 1377.3999996185303,
  "clientPrepareMs": 238,
  "upstreamHeadersMs": 881
}
总用时 48.5s · 平均 90 tok/s · 峰值 146 tok/s