Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 16.6 s
- Cost
- $0.0041
- Output tokens
- 8,023
- Spent thinking
- 2,138 tokens
- Speed
- 598.7 tok/s
- Console errors
- 0
- First token after
- 3.2 s
- First visible text after
- 8.8 s
- Prompt tokens
- 574
- Code size
- 17.0 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"medium"}- 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 · 369 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Midnight Campfire — Low-Poly Forest Clearing</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html,body { width:100%; height:100%; overflow:hidden; background:#070d1a; font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
#app { position:fixed; inset:0; }
canvas { display:block; }
.vignette { position:fixed; inset:0; pointer-events:none; background:radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,.42) 100%); }
.panel { position:fixed; top:18px; left:18px; pointer-events:none; color:#eaf2ff; text-shadow:0 2px 12px rgba(0,0,0,.9); max-width:min(420px, calc(100vw - 36px)); }
.panel h1 { font-size:clamp(22px,3vw,34px); letter-spacing:.02em; line-height:1.05; font-weight:800; }
.panel h1 span { color:#ffb15e; }
.panel p { margin-top:8px; font-size:13px; line-height:1.45; color:rgba(226,238,255,.82); }
.badge { display:inline-block; margin-top:10px; padding:5px 10px; border-radius:999px; border:1px solid rgba(255,255,255,.22); background:rgba(8,15,30,.45); backdrop-filter:blur(6px); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:#cfe2ff; }
.hint { position:fixed; left:50%; bottom:16px; transform:translateX(-50%); pointer-events:none; white-space:nowrap; padding:9px 14px; border-radius:999px; background:rgba(5,10,20,.55); border:1px solid rgba(255,255,255,.14); color:rgba(235,244,255,.92); font-size:12px; backdrop-filter:blur(8px); box-shadow:0 10px 30px rgba(0,0,0,.35); }
.hint b { color:#ffc06e; font-weight:700; }
.hint i { font-style:normal; opacity:.55; margin:0 7px; }
@media (max-width:520px){ .panel{ top:12px; left:12px; } .panel p{ font-size:12px; } .hint{ font-size:11px; bottom:10px; max-width:calc(100vw - 20px); white-space:normal; text-align:center; } }
</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 id="app"></div>
<div class="vignette"></div>
<div class="panel">
<h1>🌲 Midnight <span>Campfire</span></h1>
<p>A low-poly forest clearing at night — flickering firelight, rising sparks, pine trees in the fog, stars and a full moon.</p>
<span class="badge">Three.js • 45 trees • live shadows</span>
</div>
<div class="hint"><b>Drag</b> to orbit <i>•</i> <b>Scroll / pinch</b> to zoom <i>•</i> Camera stays above ground</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
/* ---------- deterministic random so composition is always good ---------- */
let _seed = 1234567;
function rand(){ _seed |= 0; _seed = _seed + 0x6D2B79F5 | 0; let t = Math.imul(_seed ^ _seed >>> 15, 1 | _seed); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }
function rr(min,max){ return min + rand()*(max-min); }
function pick(arr){ return arr[Math.floor(rand()*arr.length)]; }
/* ---------- renderer / scene / camera ---------- */
const container = document.getElementById('app');
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.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
renderer.outputColorSpace = THREE.SRGBColorSpace;
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x070d1a);
scene.fog = new THREE.Fog(0x0a1526, 16, 58);
const camera = new THREE.PerspectiveCamera(55, 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.enablePan = false;
controls.minDistance = 4.5;
controls.maxDistance = 24;
controls.minPolarAngle = 0.18;
controls.maxPolarAngle = Math.PI * 0.49; // strictly above ground plane
controls.autoRotate = true;
controls.autoRotateSpeed = 0.55;
controls.update();
/* ---------- lights ---------- */
scene.add(new THREE.AmbientLight(0x334466, 0.55));
const hemi = new THREE.HemisphereLight(0x28406e, 0x0c1a12, 0.65);
scene.add(hemi);
const moonLight = new THREE.DirectionalLight(0x9db8ff, 0.45);
moonLight.position.set(-18, 24, -20);
scene.add(moonLight);
const fireLight = new THREE.PointLight(0xff8a2a, 34, 26, 1.6);
fireLight.position.set(0, 1.35, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.25;
fireLight.shadow.camera.far = 26;
fireLight.shadow.bias = -0.004;
scene.add(fireLight);
const fireLight2 = new THREE.PointLight(0xffc266, 9, 9, 1.8);
fireLight2.position.set(0, 0.6, 0);
scene.add(fireLight2);
/* ---------- helpers ---------- */
function glowTexture(){
const c = document.createElement('canvas'); c.width = c.height = 128;
const g = c.getContext('2d');
const grad = g.createRadialGradient(64,64,0,64,64,64);
grad.addColorStop(0,'rgba(255,255,255,1)');
grad.addColorStop(0.25,'rgba(255,220,150,0.55)');
grad.addColorStop(1,'rgba(255,160,60,0)');
g.fillStyle = grad; g.fillRect(0,0,128,128);
const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t;
}
const sharedGlow = glowTexture();
/* ---------- ground ---------- */
const groundMat = new THREE.MeshStandardMaterial({ color:0x182f21, roughness:0.95, flatShading:true });
const ground = new THREE.Mesh(new THREE.CircleGeometry(55, 64), groundMat);
ground.rotation.x = -Math.PI/2; ground.receiveShadow = true; scene.add(ground);
/* dirt clearing under the fire */
const dirt = new THREE.Mesh(new THREE.CircleGeometry(3.4, 24), new THREE.MeshStandardMaterial({ color:0x3b2a18, roughness:1, flatShading:true }));
dirt.rotation.x = -Math.PI/2; dirt.position.y = 0.015; dirt.receiveShadow = true; scene.add(dirt);
/* dark ground patches */
for (let i=0;i<14;i++){
const p = new THREE.Mesh(new THREE.CircleGeometry(rr(1.2,3.2), 8), new THREE.MeshStandardMaterial({ color: pick([0x122418,0x1e3a28,0x142a1e]), roughness:1, flatShading:true }));
p.rotation.x = -Math.PI/2; p.rotation.z = rand()*Math.PI;
const a = rand()*Math.PI*2, d = rr(4,26);
p.position.set(Math.cos(a)*d, 0.01+i*0.0005, Math.sin(a)*d);
p.receiveShadow = true; scene.add(p);
}
/* grass tufts */
const tuftGeo = new THREE.ConeGeometry(0.16, 0.5, 5);
const tuftMat = new THREE.MeshStandardMaterial({ color:0x2c5a36, roughness:1, flatShading:true });
for (let i=0;i<70;i++){
const a = rand()*Math.PI*2, d = rr(3.6, 30);
const t = new THREE.Mesh(tuftGeo, tuftMat);
t.position.set(Math.cos(a)*d, 0.2, Math.sin(a)*d);
t.scale.setScalar(rr(0.7,1.6)); t.rotation.y = rand()*Math.PI;
t.castShadow = true; scene.add(t);
}
/* ---------- rocks ---------- */
const rockMat = new THREE.MeshStandardMaterial({ color:0x6b7280, roughness:0.95, flatShading:true });
const darkRockMat = new THREE.MeshStandardMaterial({ color:0x4b5563, roughness:0.95, flatShading:true });
for (let i=0;i<18;i++){
const a = rand()*Math.PI*2, d = rr(4.2, 30);
const r = new THREE.Mesh(new THREE.DodecahedronGeometry(rr(0.35,0.95),0), rand()>0.5?rockMat:darkRockMat);
r.position.set(Math.cos(a)*d, rr(0.05,0.2), Math.sin(a)*d);
r.scale.y = rr(0.5,0.8); r.rotation.set(rand()*3, rand()*3, rand()*3);
r.castShadow = true; r.receiveShadow = true; scene.add(r);
}
/* ---------- trees (45+) ---------- */
const trunkMat = new THREE.MeshStandardMaterial({ color:0x5a3d24, roughness:1, flatShading:true });
const leafMats = [0x1e4d2f,0x265c36,0x17402a,0x2a6b3c,0x1a5a3a].map(c=>new THREE.MeshStandardMaterial({ color:c, roughness:1, flatShading:true }));
const treePositions = [];
function makeTree(x,z){
const g = new THREE.Group();
const h = rr(0.85, 1.55);
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.13,0.2,1.1,6), trunkMat);
trunk.position.y = 0.55; trunk.castShadow = true; g.add(trunk);
const mat = pick(leafMats);
const layers = 3;
for (let i=0;i<layers;i++){
const cone = new THREE.Mesh(new THREE.ConeGeometry(1.25 - i*0.32, 1.7, 6), mat);
cone.position.y = 1.35 + i*0.85;
cone.rotation.y = rand()*Math.PI;
cone.castShadow = true; cone.receiveShadow = true;
g.add(cone);
}
g.position.set(x,0,z); g.scale.setScalar(h); g.rotation.y = rand()*Math.PI*2;
scene.add(g); treePositions.push(new THREE.Vector2(x,z));
}
let attempts = 0;
while (treePositions.length < 48 && attempts < 600){
attempts++;
const a = rand()*Math.PI*2;
const d = 6.5 + Math.sqrt(rand()) * 30; // clearing radius 6.5, forest to ~36
const x = Math.cos(a)*d, z = Math.sin(a)*d;
if (treePositions.some(p => p.distanceTo(new THREE.Vector2(x,z)) < 1.9)) continue;
makeTree(x,z);
}
/* ---------- campfire ---------- */
const fireGroup = new THREE.Group(); scene.add(fireGroup);
/* stone ring */
const ringGeo = new THREE.DodecahedronGeometry(0.28,0);
for (let i=0;i<11;i++){
const a = (i/11)*Math.PI*2 + rr(-0.1,0.1);
const s = new THREE.Mesh(ringGeo, pick([rockMat,darkRockMat]));
s.position.set(Math.cos(a)*1.15, 0.14, Math.sin(a)*1.15);
s.scale.set(rr(0.8,1.3), rr(0.55,0.85), rr(0.8,1.3));
s.rotation.set(rand()*3, rand()*3, rand()*3);
s.castShadow = true; s.receiveShadow = true; fireGroup.add(s);
}
/* burning logs */
const logMat = new THREE.MeshStandardMaterial({ color:0x6b4423, roughness:1, flatShading:true });
const logEndMat = new THREE.MeshStandardMaterial({ color:0xb98a52, roughness:1, flatShading:true });
const logGeo = new THREE.CylinderGeometry(0.09,0.11,1.25,7);
for (let i=0;i<5;i++){
const a = (i/5)*Math.PI*2;
const log = new THREE.Mesh(logGeo, [logEndMat, logMat, logEndMat]);
log.position.set(Math.cos(a)*0.32, 0.22, Math.sin(a)*0.32);
log.rotation.set(Math.sin(a)*0.7, 0, Math.cos(a)*0.7);
log.castShadow = true; fireGroup.add(log);
}
for (let i=0;i<3;i++){
const log = new THREE.Mesh(logGeo, [logEndMat, logMat, logEndMat]);
log.position.set(rr(-0.2,0.2), 0.12, rr(-0.2,0.2));
log.rotation.set(Math.PI/2, rand()*Math.PI, 0.15);
log.castShadow = true; fireGroup.add(log);
}
/* glowing embers */
const emberGeo = new THREE.DodecahedronGeometry(0.09,0);
const emberMat = new THREE.MeshStandardMaterial({ color:0x2a1206, emissive:0xff5a00, emissiveIntensity:2.6, roughness:1, flatShading:true });
for (let i=0;i<9;i++){
const e = new THREE.Mesh(emberGeo, emberMat);
const a = rand()*Math.PI*2, d = rand()*0.5;
e.position.set(Math.cos(a)*d, 0.1, Math.sin(a)*d);
e.scale.setScalar(rr(0.6,1.4)); fireGroup.add(e);
}
/* flames — layered low-poly cones, animated */
function flameCone(radius,height,color,opacity){
const geo = new THREE.ConeGeometry(radius,height,7);
geo.translate(0,height/2,0);
const mat = new THREE.MeshStandardMaterial({ color:color, emissive:color, emissiveIntensity:1.6, transparent:true, opacity:opacity, roughness:0.4, flatShading:true, depthWrite:false });
const m = new THREE.Mesh(geo,mat); m.position.y = 0.18; return m;
}
const flameOuter = flameCone(0.58,1.55,0xff6a00,0.88);
const flameMid = flameCone(0.40,1.15,0xffa318,0.92);
const flameInner = flameCone(0.23,0.72,0xffe07a,0.97);
fireGroup.add(flameOuter, flameMid, flameInner);
/* fire glow sprite */
const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map:sharedGlow, color:0xff9a33, transparent:true, opacity:0.55, blending:THREE.AdditiveBlending, depthWrite:false }));
glow.scale.setScalar(4.2); glow.position.y = 0.85;火花GroupFix: fireGroup.add(glow);
/* ---------- sitting logs & stumps ---------- */
const seatGeo = new THREE.CylinderGeometry(0.24,0.24,2.3,8);
[[2.6,0.6],[ -1.8,2.4],[0.4,-2.8]].forEach(([x,z],i)=>{
const s = new THREE.Mesh(seatGeo, [logEndMat, logMat, logEndMat]);
s.position.set(x,0.24,z);
s.rotation.set(0, Math.atan2(z,x)+Math.PI/2, Math.PI/2);
s.castShadow = true; s.receiveShadow = true; scene.add(s);
});
for (let i=0;i<3;i++){
const a = rr(0,Math.PI*2), d = rr(4,9);
const st = new THREE.Mesh(new THREE.CylinderGeometry(0.42,0.48,0.55,8), [logEndMat, logMat, logEndMat]);
st.position.set(Math.cos(a)*d, 0.27, Math.sin(a)*d);
st.castShadow = true; st.receiveShadow = true; scene.add(st);
}
/* ---------- sparks particle system ---------- */
const SPARKS = 90;
const sparkPos = new Float32Array(SPARKS*3);
const sparkVel = new Float32Array(SPARKS*3);
const sparkLife = new Float32Array(SPARKS);
function resetSpark(i, randomY){
sparkPos[i*3] = rr(-0.25,0.25);
sparkPos[i*3+1] = randomY ? rr(0.3,3.2) : rr(0.35,0.6);
sparkPos[i*3+2] = rr(-0.25,0.25);
sparkVel[i*3] = rr(-0.15,0.15);
sparkVel[i*3+1] = rr(0.55,1.35);
sparkVel[i*3+2] = rr(-0.15,0.15);
sparkLife[i] = rr(0.6,1);
}
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:0xffc266, size:0.09, transparent:true, opacity:0.95, blending:THREE.AdditiveBlending, depthWrite:false, sizeAttenuation:true }));
scene.add(sparks);
/* ---------- fireflies ---------- */
const FLIES = 22;
const flyPos = new Float32Array(FLIES*3);
const flySeed = new Float32Array(FLIES*3);
for (let i=0;i<FLIES;i++){
const a = rand()*Math.PI*2, d = rr(3.5,18);
flyPos[i*3]=Math.cos(a)*d; flyPos[i*3+1]=rr(0.5,2.2); flyPos[i*3+2]=Math.sin(a)*d;
flySeed[i*3]=rand()*10; flySeed[i*3+1]=rand()*10; flySeed[i*3+2]=rand()*10;
}
const flyGeo = new THREE.BufferGeometry();
flyGeo.setAttribute('position', new THREE.BufferAttribute(flyPos,3));
const flies = new THREE.Points(flyGeo, new THREE.PointsMaterial({ color:0xbfff9a, size:0.07, transparent:true, opacity:0.8, blending:THREE.AdditiveBlending, depthWrite:false }));
scene.add(flies);
/* ---------- stars & moon ---------- */
const STAR_COUNT = 900;
const starPos = new Float32Array(STAR_COUNT*3);
for (let i=0;i<STAR_COUNT;i++){
const a = rand()*Math.PI*2;
const y = rr(0.06,1);
const horiz = Math.sqrt(1-y*y);
const r = 85;
starPos[i*3] = Math.cos(a)*horiz*r;
starPos[i*3+1] = y*r;
starPos[i*3+2] = Math.sin(a)*horiz*r;
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos,3));
const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color:0xdbeafe, size:0.35, sizeAttenuation:false, fog:false, transparent:true, opacity:0.95, depthWrite:false }));
scene.add(stars);
const moon = new THREE.Mesh(new THREE.SphereGeometry(3.2, 20, 20), new THREE.MeshBasicMaterial({ color:0xf2f5ff, fog:false }));
moon.position.set(-30, 26, -36); scene.add(moon);
const moonGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map:sharedGlow, color:0xbfd4ff, transparent:true, opacity:0.5, blending:THREE.AdditiveBlending, depthWrite:false, fog:false }));
moonGlow.scale.setScalar(16); moonGlow.position.copy(moon.position); scene.add(moonGlow);
/* ---------- animation ---------- */
const clock = new THREE.Clock();
let t = 0;
function animate(){
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
t += dt;
/* flicker — layered sines + small random jitter */
const flick = Math.sin(t*11)*0.5 + Math.sin(t*23+1.3)*0.3 + Math.sin(t*5.1)*0.2;
const jitter = (Math.random()-0.5)*0.12;
const level = 1 + flick*0.22 + jitter;
fireLight.intensity = 34 * level;
fireLight2.intensity = 9 * level;
fireLight.position.x = Math.sin(t*7)*0.08;
fireLight.position.z = Math.cos(t*6)*0.08;
/* flame dance */
flameOuter.scale.set(1 + Math.sin(t*9)*0.08, 1 + Math.sin(t*12.3)*0.18, 1 + Math.cos(t*8)*0.08);
flameMid.scale.set(1 + Math.cos(t*10.5)*0.1, 1 + Math.sin(t*14.1+2)*0.22, 1 + Math.sin(t*9.4)*0.1);
flameInner.scale.set(1 + Math.sin(t*13.7)*0.12, 1 + Math.cos(t*16.2)*0.2, 1);
flameOuter.rotation.y = t*1.4; flameMid.rotation.y = -t*1.9; flameInner.rotation.y = t*2.6;
glow.material.opacity = 0.42 + flick*0.1;
const gs = 4.1 + flick*0.35; glow.scale.set(gs,gs,1);
/* sparks rise & sway */
for (let i=0;i<SPARKS;i++){
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;
sparkLife[i] -= dt*0.45;
if (sparkPos[i*3+1] > 3.6 || sparkLife[i] <= 0) resetSpark(i,false);
}
sparkGeo.attributes.position.needsUpdate = true;
/* fireflies wander */
for (let i=0;i<FLIES;i++){
flyPos[i*3] += Math.sin(t*0.7+flySeed[i*3])*0.35*dt;
flyPos[i*3+1] += Math.sin(t*1.1+flySeed[i*3+1])*0.22*dt;
flyPos[i*3+2] += Math.cos(t*0.6+flySeed[i*3+2])*0.35*dt;
flyPos[i*3+1] = THREE.MathUtils.clamp(flyPos[i*3+1], 0.35, 3.2);
}
flyGeo.attributes.position.needsUpdate = true;
controls.update();
renderer.render(scene, camera);
}
animate();
/* ---------- resize ---------- */
window.addEventListener('resize', ()=>{
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Reasoning trace (2,138 tokens)
Building a self-contained Threejs low-poly night campfire scene with lighting, particles, fog, and camera controls.Adding camera constraints, low-poly ground, stone ring, logs, flickering flame geometry, and point light.Configuring shadow settings, instanced trees, particles, lights, and camera controls for performance and visuals.