Models 44

Muse Ember

Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
37.4 s
Cost
$0.0087
Output tokens
17,341
Spent thinking
9,204 tokens
Speed
505.3 tok/s
Console errors
0
First token after
3.1 s
First visible text after
26.9 s
Prompt tokens
574
Code size
23.5 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
model default
Max output
943,718 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
Build a low-poly 3D scene of a campfire in a forest clearing at night, with Three.js in a single HTML file. Load Three.js only through this import map (do not use any other URL or version): <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script> Requirements: - A ground with a few low-poly rocks and logs around a campfire in the center. - At least 30 low-poly trees placed randomly around the clearing (not on the fire). - The campfire has animated flames and a warm point light that flickers and casts shadows. - Sparks rising from the fire as a simple particle system. - A dark night sky with stars and a moon, plus fog that fades the distant trees. - OrbitControls to look around, with limits so the camera stays above the ground. - The renderer fills the window and handles resizing correctly.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 527 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Campfire Night — Low-Poly Forest Clearing</title>
<style>
  * { box-sizing: border-box; }
  html, body { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#050912; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
  canvas { display:block; }
  .panel{
    position:fixed; left:18px; top:18px; z-index:10; pointer-events:none;
    color:#eef3ff; padding:14px 16px; border-radius:16px;
    background: linear-gradient(135deg, rgba(8,14,32,.72), rgba(10,22,38,.45));
    border:1px solid rgba(160,190,255,.22);
    box-shadow:0 10px 30px rgba(0,0,0,.45);
    backdrop-filter: blur(8px);
    max-width:320px;
  }
  .panel h1{ margin:0 0 5px; font-size:22px; letter-spacing:.3px; line-height:1.1; }
  .panel p{ margin:0; font-size:13px; line-height:1.45; color:#b9c7e8; }
  .panel .fire{ color:#ffb347; }
  .hint{
    position:fixed; left:50%; bottom:16px; transform:translateX(-50%); z-index:10; pointer-events:none;
    white-space:nowrap; color:#dbe7ff; font-size:12.5px; padding:9px 14px; border-radius:999px;
    background:rgba(6,12,26,.62); border:1px solid rgba(160,190,255,.18); backdrop-filter: blur(8px);
    box-shadow:0 6px 20px rgba(0,0,0,.35);
  }
  .badge{
    position:fixed; right:18px; top:18px; z-index:10; pointer-events:none;
    font-size:11px; letter-spacing:1.2px; color:#aebfe6; padding:8px 11px; border-radius:999px;
    background:rgba(6,12,26,.5); border:1px solid rgba(160,190,255,.16); backdrop-filter: blur(6px);
  }
  .vignette{
    position:fixed; inset:0; z-index:5; pointer-events:none;
    background: radial-gradient(ellipse at center, transparent 58%, rgba(1,4,12,.48) 100%);
  }
  @media (max-width:640px){
    .panel{ left:12px; top:12px; padding:11px 13px; max-width:230px; }
    .panel h1{ font-size:18px; }
    .panel p{ font-size:12px; }
    .badge{ display:none; }
    .hint{ font-size:11px; bottom:10px; padding:8px 11px; }
  }
</style>
<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script>
</head>
<body>
<div class="vignette"></div>
<div class="panel">
  <h1><span class="fire">🔥</span> Campfire Night</h1>
  <p>A low-poly forest clearing under the moon — flickering firelight, rising sparks, fireflies and pine trees fading into the fog.</p>
</div>
<div class="badge">THREE.JS • LOW-POLY</div>
<div class="hint">🖱️ Drag to orbit &nbsp;•&nbsp; Scroll to zoom &nbsp;•&nbsp; Right-drag to pan</div>

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

/* ---------- Renderer / Scene / Camera ---------- */
const renderer = new THREE.WebGLRenderer({ antialias:true });
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.12;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x070d1f);
scene.fog = new THREE.Fog(0x0a1626, 16, 52);

const camera = new THREE.PerspectiveCamera(52, window.innerWidth/window.innerHeight, 0.1, 220);
camera.position.set(9.5, 5.6, 11.5);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.0, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.minDistance = 3.5;
controls.maxDistance = 26;
controls.minPolarAngle = 0.12;
controls.maxPolarAngle = Math.PI * 0.49; // camera stays above the ground
controls.autoRotate = true;
controls.autoRotateSpeed = 0.55;
const stopAuto = () => { controls.autoRotate = false; };
renderer.domElement.addEventListener('pointerdown', stopAuto, { once:true });
renderer.domElement.addEventListener('wheel', stopAuto, { once:true });

/* ---------- Helpers ---------- */
function radialTexture(){
  const c = document.createElement('canvas'); c.width = c.height = 128;
  const ctx = c.getContext('2d');
  const g = ctx.createRadialGradient(64,64,0,64,64,64);
  g.addColorStop(0,'rgba(255,255,255,1)');
  g.addColorStop(0.25,'rgba(255,255,255,0.55)');
  g.addColorStop(0.55,'rgba(255,255,255,0.18)');
  g.addColorStop(1,'rgba(255,255,255,0)');
  ctx.fillStyle = g; ctx.fillRect(0,0,128,128);
  return new THREE.CanvasTexture(c);
}
const glowTex = radialTexture();

function groundHeight(x,z){
  const r = Math.hypot(x,z);
  if(r < 6) return 0;
  const t = Math.min(1,(r-6)/12);
  return t * ((r-6)*0.045 + Math.sin(x*0.25)*Math.cos(z*0.22)*0.55);
}

/* ---------- Sky dome, stars, moon ---------- */
const sky = new THREE.Mesh(
  new THREE.SphereGeometry(95, 24, 14),
  new THREE.ShaderMaterial({
    uniforms:{
      topColor:{ value:new THREE.Color(0x02040d) },
      bottomColor:{ value:new THREE.Color(0x14273f) },
      offset:{ value:12 }, exponent:{ value:0.75 }
    },
    vertexShader:`varying vec3 vWorldPosition; void main(){ vec4 wp = modelMatrix * vec4(position,1.0); vWorldPosition = wp.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }`,
    fragmentShader:`uniform vec3 topColor; uniform vec3 bottomColor; uniform float offset; uniform float exponent; varying vec3 vWorldPosition; void main(){ float h = normalize(vWorldPosition + vec3(0.0, offset, 0.0)).y; gl_FragColor = vec4(mix(bottomColor, topColor, pow(max(h,0.0), exponent)), 1.0); }`,
    side:THREE.BackSide, depthWrite:false, fog:false
  })
);
sky.renderOrder = -10;
scene.add(sky);

// Stars
{
  const count = 950;
  const pos = new Float32Array(count*3);
  for(let i=0;i<count;i++){
    const theta = Math.random()*Math.PI*2;
    const y = 0.18 + Math.random()*0.82;
    const horiz = Math.sqrt(Math.max(0,1-y*y));
    const r = 82;
    pos[i*3]   = Math.cos(theta)*horiz*r;
    pos[i*3+1] = y*r;
    pos[i*3+2] = Math.sin(theta)*horiz*r;
  }
  const geo = new THREE.BufferGeometry();
  geo.setAttribute('position', new THREE.BufferAttribute(pos,3));
  const mat = new THREE.PointsMaterial({ color:0xffffff, size:0.85, map:glowTex, transparent:true, opacity:0.9, sizeAttenuation:true, depthWrite:false, fog:false });
  const stars = new THREE.Points(geo, mat);
  stars.name = 'stars';
  scene.add(stars);
}

// Moon + glow
const moonPos = new THREE.Vector3(-30, 30, -44);
const moon = new THREE.Mesh(new THREE.SphereGeometry(3.1, 20, 16), new THREE.MeshBasicMaterial({ color:0xe9efff, fog:false }));
moon.position.copy(moonPos);
scene.add(moon);
const moonGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map:glowTex, color:0xbfd2ff, transparent:true, opacity:0.5, blending:THREE.AdditiveBlending, depthWrite:false, fog:false }));
moonGlow.position.copy(moonPos);
moonGlow.scale.set(16,16,1);
scene.add(moonGlow);
// moon craters (small dark discs on near side)
for(let i=0;i<5;i++){
  const cr = new THREE.Mesh(new THREE.CircleGeometry(0.28+Math.random()*0.35, 12), new THREE.MeshBasicMaterial({ color:0xcbd6ee, fog:false }));
  const dir = new THREE.Vector3(0.35,0.25,0.9).normalize();
  const p = moonPos.clone().add(dir.clone().multiplyScalar(3.08));
  p.x += (Math.random()-0.5)*2.2; p.y += (Math.random()-0.5)*2.0;
  cr.position.copy(p); cr.lookAt(moonPos.clone().add(dir.clone().multiplyScalar(10)));
  scene.add(cr);
}

/* ---------- Lights ---------- */
scene.add(new THREE.HemisphereLight(0x35507a, 0x0d1a12, 0.65));
const moonLight = new THREE.DirectionalLight(0x9db8ff, 0.55);
moonLight.position.set(-22, 30, -24);
scene.add(moonLight);

const fireLight = new THREE.PointLight(0xff8c3a, 30, 32, 2);
fireLight.position.set(0, 1.15, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024,1024);
fireLight.shadow.camera.near = 0.4;
fireLight.shadow.camera.far = 32;
fireLight.shadow.bias = -0.008;
fireLight.shadow.normalBias = 0.03;
scene.add(fireLight);

const glowFill = new THREE.PointLight(0xffa04d, 6, 9, 2);
glowFill.position.set(0, 0.45, 0);
scene.add(glowFill);

/* ---------- Ground / clearing ---------- */
const groundGeo = new THREE.PlaneGeometry(105,105,42,42);
groundGeo.rotateX(-Math.PI/2);
{
  const p = groundGeo.attributes.position;
  for(let i=0;i<p.count;i++){
    p.setY(i, groundHeight(p.getX(i), p.getZ(i)));
  }
  groundGeo.computeVertexNormals();
}
const ground = new THREE.Mesh(groundGeo, new THREE.MeshStandardMaterial({ color:0x1d3825, roughness:1, flatShading:true }));
ground.receiveShadow = true;
scene.add(ground);

// colour patches
const patchColors = [0x24402a,0x182f1e,0x2a4a2d,0x382c1d,0x1a3523];
for(let i=0;i<26;i++){
  const r = 0.7 + Math.random()*1.8;
  const m = new THREE.Mesh(new THREE.CircleGeometry(r,7), new THREE.MeshStandardMaterial({ color:patchColors[i%patchColors.length], roughness:1, flatShading:true }));
  m.rotation.x = -Math.PI/2; m.rotation.z = Math.random()*Math.PI;
  const a = Math.random()*Math.PI*2, d = 2.2 + Math.random()*13;
  const x = Math.cos(a)*d, z = Math.sin(a)*d;
  m.position.set(x, groundHeight(x,z)+0.02, z);
  m.scale.set(1, 0.7+Math.random()*0.6, 1);
  m.receiveShadow = true;
  scene.add(m);
}
// dirt clearing + ash
const dirt = new THREE.Mesh(new THREE.CircleGeometry(3.35,26), new THREE.MeshStandardMaterial({ color:0x3d2c1e, roughness:1, flatShading:true }));
dirt.rotation.x = -Math.PI/2; dirt.position.y = 0.03; dirt.receiveShadow = true; scene.add(dirt);
const ash = new THREE.Mesh(new THREE.CircleGeometry(1.05,20), new THREE.MeshStandardMaterial({ color:0x4b4b50, roughness:1, flatShading:true }));
ash.rotation.x = -Math.PI/2; ash.position.y = 0.045; ash.receiveShadow = true; scene.add(ash);

/* ---------- Shared materials / geometries ---------- */
const woodMat  = new THREE.MeshStandardMaterial({ color:0x6b4423, roughness:0.95, flatShading:true });
const woodMat2 = new THREE.MeshStandardMaterial({ color:0x7d552f, roughness:0.95, flatShading:true });
const rockMat  = new THREE.MeshStandardMaterial({ color:0x6f7680, roughness:1, flatShading:true });
const rockMat2 = new THREE.MeshStandardMaterial({ color:0x565d68, roughness:1, flatShading:true });
const leafMats = [
  new THREE.MeshStandardMaterial({ color:0x1c4a2a, roughness:1, flatShading:true }),
  new THREE.MeshStandardMaterial({ color:0x235c33, roughness:1, flatShading:true }),
  new THREE.MeshStandardMaterial({ color:0x2a6b3a, roughness:1, flatShading:true }),
  new THREE.MeshStandardMaterial({ color:0x17402a, roughness:1, flatShading:true })
];

/* ---------- Rocks ---------- */
const rockGeo = new THREE.IcosahedronGeometry(1,0);
function addRock(x,z,s,mat){
  const m = new THREE.Mesh(rockGeo, mat||rockMat);
  m.position.set(x, groundHeight(x,z)+s*0.35, z);
  m.scale.set(s*(0.8+Math.random()*0.5), s*(0.6+Math.random()*0.5), s*(0.8+Math.random()*0.5));
  m.rotation.set(Math.random()*3, Math.random()*3, Math.random()*3);
  m.castShadow = true; m.receiveShadow = true;
  scene.add(m); return m;
}
// stone ring around fire
for(let i=0;i<12;i++){
  const a = (i/12)*Math.PI*2 + Math.random()*0.25;
  addRock(Math.cos(a)*1.45, Math.sin(a)*1.45, 0.16+Math.random()*0.12, i%2?rockMat:rockMat2);
}
// scattered rocks
for(let i=0;i<16;i++){
  const a = Math.random()*Math.PI*2, d = 3.2+Math.random()*13;
  addRock(Math.cos(a)*d, Math.sin(a)*d, 0.25+Math.random()*0.65, Math.random()<0.5?rockMat:rockMat2);
}
addRock(4.8, -4.2, 0.6); addRock(-5.2, 3.6, 0.5); addRock(2.5, 6.5, 0.35);

/* ---------- Log benches, stumps, fallen log ---------- */
const benchGeo = new THREE.CylinderGeometry(0.21,0.21,2.3,8);
[0.55, 2.45, 4.6].forEach(angle=>{
  const pivot = new THREE.Group(); pivot.rotation.y = angle;
  const log = new THREE.Mesh(benchGeo, woodMat2);
  log.rotation.z = Math.PI/2; log.position.set(0,0.23,4.25);
  log.castShadow = true; log.receiveShadow = true;
  pivot.add(log); scene.add(pivot);
  // ring caps
  [-1.08,1.08].forEach(off=>{
    const cap = new THREE.Mesh(new THREE.CircleGeometry(0.18,8), new THREE.MeshStandardMaterial({ color:0xc9a06a, roughness:1, flatShading:true }));
    cap.position.set(off,0.23,4.25); cap.rotation.y = off>0? Math.PI/2 : -Math.PI/2;
    scene.add(cap);
  });
});
// fallen log
{
  const log = new THREE.Mesh(new THREE.CylinderGeometry(0.28,0.32,3.2,8), woodMat);
  log.rotation.set(Math.PI/2, 0, 0.7);
  log.position.set(6.2, 0.3, -2.4); log.castShadow = true; log.receiveShadow = true; scene.add(log);
}
// stumps
[[3.9,2.6],[-3.8,-3.2]].forEach(([x,z])=>{
  const s = new THREE.Mesh(new THREE.CylinderGeometry(0.33,0.4,0.55,8), woodMat);
  s.position.set(x, groundHeight(x,z)+0.27, z); s.castShadow = true; s.receiveShadow = true; scene.add(s);
  const top = new THREE.Mesh(new THREE.CircleGeometry(0.3,8), new THREE.MeshStandardMaterial({ color:0xc9a06a, roughness:1, flatShading:true }));
  top.rotation.x = -Math.PI/2; top.position.set(x, groundHeight(x,z)+0.56, z); scene.add(top);
});

/* ---------- Tent ---------- */
{
  const tentPos = new THREE.Vector3(-6.6, groundHeight(-6.6,-6), -6);
  const tent = new THREE.Mesh(new THREE.ConeGeometry(1.65,1.85,4), new THREE.MeshStandardMaterial({ color:0xc65a33, roughness:0.95, flatShading:true }));
  tent.position.set(tentPos.x, tentPos.y+0.92, tentPos.z);
  tent.rotation.y = Math.PI/4 + 0.7; tent.scale.set(1,1,1.25);
  tent.castShadow = true; tent.receiveShadow = true; scene.add(tent);
  const door = new THREE.Mesh(new THREE.ConeGeometry(0.55,0.9,4), new THREE.MeshStandardMaterial({ color:0x2b1a12, roughness:1, flatShading:true }));
  const dir = tentPos.clone().multiplyScalar(-1).normalize();
  door.position.set(tentPos.x + dir.x*0.95, tentPos.y+0.45, tentPos.z + dir.z*0.95);
  door.rotation.y = Math.PI/4 + 0.7; door.scale.set(1,1,0.4);
  scene.add(door);
  // backpack
  const pack = new THREE.Mesh(new THREE.BoxGeometry(0.5,0.65,0.35), new THREE.MeshStandardMaterial({ color:0x7a3f1d, roughness:1, flatShading:true }));
  pack.position.set(tentPos.x+1.6, tentPos.y+0.33, tentPos.z+1.1); pack.rotation.y = 0.8; pack.castShadow = true; scene.add(pack);
}

/* ---------- Trees (40+) ---------- */
const trunkGeo = new THREE.CylinderGeometry(0.14,0.26,1.15,6);
const cone1Geo = new THREE.ConeGeometry(1.3,2.4,7);
const cone2Geo = new THREE.ConeGeometry(0.95,1.9,7);
const cone3Geo = new THREE.ConeGeometry(0.6,1.45,7);
const blobGeo  = new THREE.IcosahedronGeometry(1.25,0);
const trunkMat = new THREE.MeshStandardMaterial({ color:0x5a3d24, roughness:1, flatShading:true });

function createTree(){
  const g = new THREE.Group();
  const leaf = leafMats[Math.floor(Math.random()*leafMats.length)];
  const trunk = new THREE.Mesh(trunkGeo, trunkMat);
  trunk.position.y = 0.55; g.add(trunk);
  if(Math.random() < 0.24){
    const b1 = new THREE.Mesh(blobGeo, leaf); b1.position.y = 2.35; b1.scale.set(1,0.9,1); g.add(b1);
    const b2 = new THREE.Mesh(blobGeo, leaf); b2.position.y = 3.25; b2.scale.set(0.68,0.62,0.68); g.add(b2);
    g.userData.foliage = [b1,b2];
  } else {
    const c1 = new THREE.Mesh(cone1Geo, leaf); c1.position.y = 1.95; g.add(c1);
    const c2 = new THREE.Mesh(cone2Geo, leaf); c2.position.y = 3.05; g.add(c2);
    const c3 = new THREE.Mesh(cone3Geo, leaf); c3.position.y = 4.05; g.add(c3);
    g.userData.foliage = [c1,c2,c3];
  }
  g.userData.trunk = trunk;
  return g;
}
let treeCount = 0;
let attempts = 0;
while(treeCount < 44 && attempts < 400){
  attempts++;
  const a = Math.random()*Math.PI*2;
  const d = 9 + Math.pow(Math.random(),0.65)*27;
  const x = Math.cos(a)*d, z = Math.sin(a)*d;
  if(Math.hypot(x+6.6, z+6) < 3.2) continue; // keep tent clear
  const tree = createTree();
  const s = 0.85 + Math.random()*1.05 + Math.min(0.35, d*0.006);
  tree.scale.setScalar(s);
  tree.position.set(x, groundHeight(x,z)-0.05, z);
  tree.rotation.y = Math.random()*Math.PI*2;
  const cast = d < 23;
  tree.traverse(o=>{ if(o.isMesh){ o.castShadow = cast; o.receiveShadow = true; }});
  scene.add(tree); treeCount++;
}
// a few close hero trees for framing
[[ -9.5, 4.5, 1.25],[ 8.8, 5.8, 1.15],[ -3.5, -10.5, 1.35],[ 11, -7, 1.3]].forEach(([x,z,s])=>{
  const t = createTree(); t.scale.setScalar(s); t.position.set(x, groundHeight(x,z)-0.05, z); t.rotation.y = Math.random()*3;
  t.traverse(o=>{ if(o.isMesh){ o.castShadow = true; o.receiveShadow = true; }}); scene.add(t); treeCount++;
});

/* ---------- Grass tufts (instanced) & mushrooms ---------- */
{
  const grassGeo = new THREE.ConeGeometry(0.09,0.42,4);
  const grassMat = new THREE.MeshStandardMaterial({ color:0x2f6337, roughness:1, flatShading:true });
  const grass = new THREE.InstancedMesh(grassGeo, grassMat, 150);
  const dummy = new THREE.Object3D();
  for(let i=0;i<150;i++){
    const a = Math.random()*Math.PI*2, d = 2.6 + Math.random()*14;
    const x = Math.cos(a)*d, z = Math.sin(a)*d;
    dummy.position.set(x, groundHeight(x,z)+0.18, z);
    dummy.rotation.set((Math.random()-0.5)*0.35, Math.random()*3, (Math.random()-0.5)*0.35);
    dummy.scale.setScalar(0.7+Math.random()*1.1);
    dummy.updateMatrix(); grass.setMatrixAt(i, dummy.matrix);
  }
  grass.instanceMatrix.needsUpdate = true; scene.add(grass);
}
for(let i=0;i<7;i++){
  const a = Math.random()*Math.PI*2, d = 7+Math.random()*7;
  const x = Math.cos(a)*d, z = Math.sin(a)*d, y = groundHeight(x,z);
  const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.055,0.075,0.26,6), new THREE.MeshStandardMaterial({ color:0xe8dfcc, roughness:1, flatShading:true }));
  stem.position.set(x,y+0.13,z); scene.add(stem);
  const cap = new THREE.Mesh(new THREE.ConeGeometry(0.19,0.16,7), new THREE.MeshStandardMaterial({ color:0xc0392b, roughness:0.9, flatShading:true }));
  cap.position.set(x,y+0.32,z); cap.castShadow = true; scene.add(cap);
}

/* ---------- Campfire logs ---------- */
const teepeeGeo = new THREE.CylinderGeometry(0.07,0.095,1.15,6);
for(let i=0;i<6;i++){
  const pivot = new THREE.Group(); pivot.rotation.y = (i/6)*Math.PI*2 + 0.2;
  const log = new THREE.Mesh(teepeeGeo, woodMat);
  log.position.set(0.32,0.38,0); log.rotation.z = 0.62;
  log.castShadow = true; log.receiveShadow = true;
  pivot.add(log); scene.add(pivot);
}
for(let i=0;i<3;i++){
  const log = new THREE.Mesh(new THREE.CylinderGeometry(0.09,0.09,1.15,6), woodMat2);
  log.rotation.set(Math.PI/2, 0, i*1.05); log.position.set(0,0.1,0);
  log.castShadow = true; log.receiveShadow = true; scene.add(log);
}
// glowing embers
const embers = [];
const emberMat = new THREE.MeshStandardMaterial({ color:0x2a0d00, emissive:0xff5a00, emissiveIntensity:2.2, roughness:1, flatShading:true });
for(let i=0;i<11;i++){
  const e = new THREE.Mesh(new THREE.IcosahedronGeometry(0.06+Math.random()*0.05,0), emberMat);
  const a = Math.random()*Math.PI*2, d = Math.random()*0.45;
  e.position.set(Math.cos(a)*d, 0.07+Math.random()*0.06, Math.sin(a)*d);
  e.userData.phase = Math.random()*Math.PI*2;
  scene.add(e); embers.push(e);
}

/* ---------- Flames ---------- */
const flameGroup = new THREE.Group(); flameGroup.position.set(0,0.55,0); scene.add(flameGroup);
function flameLayer(radius,height,color,opacity,y){
  const m = new THREE.Mesh(
    new THREE.ConeGeometry(radius,height,7),
    new THREE.MeshBasicMaterial({ color, transparent:true, opacity, depthWrite:false, blending:THREE.AdditiveBlending })
  );
  m.position.y = y; m.renderOrder = 5;
  flameGroup.add(m); return m;
}
const flames = [
  flameLayer(0.56,1.55,0xff5e00,0.72,0.78),
  flameLayer(0.40,1.22,0xff9a1f,0.82,0.62),
  flameLayer(0.24,0.86,0xffd23f,0.92,0.44),
  flameLayer(0.13,0.52,0xfff3b0,0.95,0.26)
];
flames.forEach((f,i)=>{ f.userData.speed = 9+i*2.2; f.userData.phase = i*1.7; });

const flameGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map:glowTex, color:0xff8c30, transparent:true, opacity:0.38, blending:THREE.AdditiveBlending, depthWrite:false }));
flameGlow.position.set(0,1.05,0); flameGlow.scale.set(4.2,4.2,1);
scene.add(flameGlow);

/* ---------- Sparks particle system ---------- */
const SPARKS = 150;
const sparkPos = new Float32Array(SPARKS*3);
const sparkVel = new Float32Array(SPARKS*3);
const sparkLife = new Float32Array(SPARKS);
function resetSpark(i, randomLife){
  sparkPos[i*3]   = (Math.random()-0.5)*0.45;
  sparkPos[i*3+1] = 0.55 + Math.random()*0.25;
  sparkPos[i*3+2] = (Math.random()-0.5)*0.45;
  sparkVel[i*3]   = (Math.random()-0.5)*0.35;
  sparkVel[i*3+1] = 0.85 + Math.random()*1.35;
  sparkVel[i*3+2] = (Math.random()-0.5)*0.35;
  sparkLife[i] = randomLife ? Math.random()*2.4 : 1.4 + Math.random()*2.2;
}
for(let i=0;i<SPARKS;i++) resetSpark(i,true);
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparkPos,3));
const sparks = new THREE.Points(sparkGeo, new THREE.PointsMaterial({ color:0xffc04d, size:0.11, map:glowTex, transparent:true, opacity:0.95, blending:THREE.AdditiveBlending, depthWrite:false, sizeAttenuation:true }));
scene.add(sparks);

/* ---------- Fireflies ---------- */
const FLIES = 28;
const flyPos = new Float32Array(FLIES*3);
const flyData = [];
for(let i=0;i<FLIES;i++){
  flyData.push({ angle:Math.random()*Math.PI*2, radius:5+Math.random()*9, speed:(0.08+Math.random()*0.18)*(Math.random()<0.5?-1:1), h:0.5+Math.random()*1.2, phase:Math.random()*Math.PI*2 });
}
const flyGeo = new THREE.BufferGeometry();
flyGeo.setAttribute('position', new THREE.BufferAttribute(flyPos,3));
const fireflies = new THREE.Points(flyGeo, new THREE.PointsMaterial({ color:0xb8ff6a, size:0.14, map:glowTex, transparent:true, opacity:0.85, blending:THREE.AdditiveBlending, depthWrite:false }));
scene.add(fireflies);

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

/* ---------- Animation ---------- */
const clock = new THREE.Clock();
const starsObj = scene.getObjectByName('stars');

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

  // flickering fire light
  const flicker = Math.sin(t*11)*0.09 + Math.sin(t*23+1.3)*0.06 + Math.sin(t*37)*0.03;
  fireLight.intensity = 30 * (1 + flicker) + Math.random()*2.2;
  glowFill.intensity = 6 * (1 + flicker);
  flameGlow.material.opacity = 0.32 + flicker*0.55;
  const gs = 4.1 + flicker*1.4;
  flameGlow.scale.set(gs, gs, 1);

  // flames
  flames.forEach((f,i)=>{
    const s = f.userData.speed, p = f.userData.phase;
    f.scale.set(1 + Math.sin(t*s+p)*0.14, 1 + Math.sin(t*s*0.9+p)*0.28 + Math.sin(t*17+p)*0.06, 1 + Math.cos(t*s*0.8+p)*0.14);
    f.rotation.y += dt*(0.4+i*0.15);
    f.rotation.z = Math.sin(t*6+p)*0.07;
    f.rotation.x = Math.cos(t*5+p)*0.05;
  });
  embers.forEach(e=>{
    const s = 0.8 + Math.sin(t*4+e.userData.phase)*0.35;
    e.scale.setScalar(s);
  });

  // sparks
  for(let i=0;i<SPARKS;i++){
    sparkLife[i] -= dt;
    if(sparkLife[i] <= 0) resetSpark(i,false);
    sparkPos[i*3]   += (sparkVel[i*3] + Math.sin(t*3+i)*0.12)*dt;
    sparkPos[i*3+1] += sparkVel[i*3+1]*dt;
    sparkPos[i*3+2] += (sparkVel[i*3+2] + Math.cos(t*2.6+i)*0.12)*dt;
  }
  sparkGeo.attributes.position.needsUpdate = true;

  // fireflies
  for(let i=0;i<FLIES;i++){
    const d = flyData[i];
    const a = d.angle + t*d.speed;
    const x = Math.cos(a)*d.radius, z = Math.sin(a)*d.radius;
    flyPos[i*3] = x;
    flyPos[i*3+1] = groundHeight(x,z) + d.h + Math.sin(t*1.6+d.phase)*0.28;
    flyPos[i*3+2] = z;
  }
  flyGeo.attributes.position.needsUpdate = true;
  fireflies.material.opacity = 0.6 + Math.sin(t*2.2)*0.3;

  // gentle star twinkle / drift + moon glow pulse
  if(starsObj){ starsObj.rotation.y = t*0.004; starsObj.material.opacity = 0.82 + Math.sin(t*1.4)*0.12; }
  moonGlow.material.opacity = 0.44 + Math.sin(t*0.8)*0.06;

  controls.update();
  if(camera.position.y < 0.55) camera.position.y = 0.55; // hard floor guard
  renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
Reasoning trace (9,204 tokens)
Implementing a Threejs single-file low-poly campfire scene with import map, geometry, lighting, particles, fog, controls, and resize handling.Extending the scene with ground displacement, clearing, rocks, logs, and camera height constraints.Adding instanced trees, animated flame cones with flickering point light, stars, moon, sparks, and UI overlay.Extending the scene with deterministic ground displacement, shared tree and rock geometries, and placement logic.Adding campfire teepee logs, layered flame cones, spark and ember particle systems, and moon and star fields.Selecting InstancedMesh for grass and enabling auto-rotate with pointerdown disable while clamping camera height and finalizing layout.Extending geometry processing to apply heightmap deformation, materials, and additional props with lighting and effects.Adding UI overlay styling, mobile scaling, and instanced grass while adjusting light intensities and particle sizes.Verifying three.js API usage and correcting bench and teepee log orientations.Adjusting shadow bias and cast settings while adding instanced grass, fireflies, and embers with shared materials.