GPT-6.1 Sol
by OpenAIreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 40 s
- Cost
- $0.067
- Output tokens
- 6,620
- Spent thinking
- 747 tokens
- Speed
- 73.1 tok/s
- Console errors
- 0
- First token after
- 9.8 s
- First visible text after
- 33.6 s
- Prompt tokens
- 555
- Code size
- 18.2 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-sol- Reasoning sent
model default- Max output
- 128,000 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 · 315 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wildlight — The Clearing</title>
<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>
<style>
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#09131d;color:#eee9db;font-family:Arial,Helvetica,sans-serif}canvas{display:block;width:100%;height:100%;outline:none}
.overlay{position:fixed;inset:0;pointer-events:none;background:linear-gradient(180deg,rgba(4,12,19,.3),transparent 23%,transparent 65%,rgba(4,12,19,.65))}
header{position:absolute;left:42px;right:42px;top:32px;display:flex;justify-content:space-between;align-items:center}
.brand{display:flex;gap:12px;align-items:center;font-size:15px;letter-spacing:4px;font-weight:600}.brand svg{color:#dfad76}
.coordinates{font-size:10px;color:#9caeb7;letter-spacing:2px;line-height:1.8;text-align:right}.coordinates b{font-weight:400;color:#deded4}
.story{position:absolute;left:44px;bottom:68px}.eyebrow{font-size:10px;letter-spacing:3px;color:#dca775;display:flex;align-items:center;gap:10px}.eyebrow:before{content:"";width:24px;height:1px;background:#dca775}
h1{font-family:Georgia,'Times New Roman',serif;font-size:46px;line-height:1.12;letter-spacing:-1.2px;font-weight:400;margin:17px 0 13px}
.description{font-size:12px;line-height:1.8;color:#9aa9ad;letter-spacing:.2px;margin:0}
.panel{pointer-events:auto;position:absolute;right:42px;bottom:65px;width:224px;padding:19px 20px 15px;background:rgba(12,23,28,.65);border:1px solid rgba(185,205,200,.15);backdrop-filter:blur(12px);border-radius:3px}
.panel-title{display:flex;justify-content:space-between;align-items:center;font-size:9px;letter-spacing:2.3px;color:#a8bab9;margin-bottom:20px}.live{width:5px;height:5px;border-radius:50%;background:#d5a778;box-shadow:0 0 10px #e39b54}
.slider-label{font-size:11px;color:#d4d7cd;display:flex;justify-content:space-between;margin-bottom:10px}.slider-label span{color:#a7a899;font-size:10px}
input[type=range]{appearance:none;width:100%;height:2px;background:#58605b;accent-color:#e8b782;display:block;margin:15px 0 22px;cursor:pointer}
input[type=range]::-webkit-slider-thumb{appearance:none;width:9px;height:9px;border-radius:50%;background:#edc59c;box-shadow:0 0 8px #e0a56866}
.buttons{display:flex;gap:10px}button{font:10px Arial;letter-spacing:.4px;color:#bdc6c1;border:1px solid #63706a55;background:transparent;border-radius:2px;padding:9px 10px;cursor:pointer;flex:1;white-space:nowrap}button:hover,button.active{color:#f2cda0;background:#daa26712;border-color:#d4ac7666}button:focus-visible,input:focus-visible{outline:2px solid #e8b782;outline-offset:4px}
footer{position:absolute;left:44px;right:42px;bottom:25px;display:flex;justify-content:space-between;font-size:9px;letter-spacing:1.6px;color:#7f929b}.hint{color:#a5b0b0;letter-spacing:.8px}.hint span{padding:0 9px;color:#5f787d}
@media(max-width:700px){header{left:23px;right:23px;top:23px}.brand{font-size:12px;letter-spacing:3px}.coordinates{font-size:8px;letter-spacing:1px}.story{left:24px;bottom:135px}h1{font-size:34px}.description{font-size:11px}.panel{right:23px;bottom:57px;width:calc(100% - 46px);padding:13px 15px;display:flex;align-items:center;gap:15px}.panel-title{display:none}.sliders{flex:1}.slider-label{margin:0;font-size:10px}input[type=range]{margin:13px 0 5px}.buttons{gap:6px}button{font-size:9px;padding:10px 8px}footer{left:24px;right:24px;bottom:22px;font-size:8px}footer .edition{display:none}.hint{margin:auto}.eyebrow{font-size:8px;letter-spacing:2px}}
</style>
</head>
<body>
<div id="scene"></div>
<div class="overlay">
<header>
<div class="brand"><svg width="24" height="29" viewBox="0 0 24 29" fill="none" aria-hidden="true"><path d="M12 1 22 20H2L12 1Z" stroke="currentColor"/><path d="m12 9 10 18H2L12 9Z" stroke="currentColor"/><path d="M12 19v10" stroke="currentColor"/></svg>WILDLIGHT</div>
<div class="coordinates">45° 31′ N 122° 40′ W<br><b>02:34 AM / 08°C</b></div>
</header>
<section class="story">
<div class="eyebrow">FIELD NOTES / NO. 004</div>
<h1>The world is quiet.<br>The fire is not.</h1>
<p class="description">A little warmth, beneath a thousand stars.<br>Stay for a while.</p>
</section>
<section class="panel" aria-label="Scene controls">
<div class="panel-title">THE CLEARING <span class="live"></span></div>
<div class="sliders"><label class="slider-label" for="warmth">Firelight <span id="strength">70%</span></label><input id="warmth" type="range" min="15" max="100" value="70" aria-label="Firelight intensity"></div>
<div class="buttons"><button id="orbit" aria-pressed="false">↻ Auto orbit</button><button id="reset">↺ Reset view</button></div>
</section>
<footer><span class="edition">AN INTERACTIVE WILDERNESS</span><span class="hint">Drag to look around <span>·</span> Scroll to move closer</span><span class="edition">LEAVE NO TRACE</span></footer>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color('#091423');
scene.fog = new THREE.FogExp2('#101e29', .022);
const camera = new THREE.PerspectiveCamera(46, innerWidth / innerHeight, .1, 220);
const home = new THREE.Vector3(16, 10.8, 20);
camera.position.copy(home);
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.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.22;
document.getElementById('scene').appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 2.6, 0);
controls.enableDamping = true;
controls.dampingFactor = .055;
controls.minDistance = 10;
controls.maxDistance = 40;
controls.maxPolarAngle = Math.PI / 2 - .09;
controls.minPolarAngle = .25;
controls.enablePan = false;
controls.autoRotateSpeed = .35;
controls.update();
let seed = 97231;
function rand(){seed = (seed * 16807) % 2147483647; return (seed-1)/2147483646;}
const between=(a,b)=>a+rand()*(b-a);
const mat = (color, roughness=1)=>new THREE.MeshStandardMaterial({color,roughness,flatShading:true});
const groundMat = mat('#222e2a');
const dirtMat = mat('#443c30');
const barkMats = [mat('#3b302a'),mat('#45362d'),mat('#302b29')];
const pineMats = [mat('#193d35'),mat('#23473b'),mat('#1c3934'),mat('#284d3f'),mat('#213b38')];
const rockMats = [mat('#555d59'),mat('#67716c'),mat('#454c49'),mat('#6d7066')];
function mesh(geo, material, x=0,y=0,z=0) {
const m = new THREE.Mesh(geo,material);m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;scene.add(m);return m;
}
mesh(new THREE.CylinderGeometry(85,85,.4,96),groundMat,0,-.27,0);
const patchGeo = new THREE.CircleGeometry(8.6,40);
const pp=patchGeo.attributes.position;
for(let i=1;i<pp.count;i++){const s=between(.92,1.08);pp.setXY(i,pp.getX(i)*s,pp.getY(i)*s);}
patchGeo.computeVertexNormals();
const patch=mesh(patchGeo,dirtMat,0,-.055,0);patch.rotation.x=-Math.PI/2;
const moonLight=new THREE.DirectionalLight('#a6c9ee',2.3);
moonLight.position.set(-18,30,-18);scene.add(moonLight);
scene.add(new THREE.HemisphereLight('#7898bc','#25251b',.7));
const fireLight = new THREE.PointLight('#ff9a38',100,30,1.8);
fireLight.position.set(0,2.3,0);fireLight.castShadow=true;
fireLight.shadow.mapSize.set(2048,2048);
fireLight.shadow.camera.near=.3;fireLight.shadow.camera.far=32;
fireLight.shadow.bias=-.001;fireLight.shadow.normalBias=.04;
fireLight.shadow.radius=4;scene.add(fireLight);
const fill = new THREE.PointLight('#ffb251',18,13,2);
fill.position.set(0,.6,1);scene.add(fill);
// Each tree is a distinct, angular silhouette.
const trunkGeo=new THREE.CylinderGeometry(.16,.32,1,6);
for(let i=0;i<100;i++){
const angle=rand()*Math.PI*2;
const radius=i<36?between(10,19):between(19,52);
const x=Math.cos(angle)*radius,z=Math.sin(angle)*radius;
const height=between(7,13.5);
const group=new THREE.Group();group.position.set(x,0,z);group.rotation.y=rand()*6.28;
const trunk=new THREE.Mesh(trunkGeo,barkMats[i%3]);
trunk.scale.set(1,height*.83,1);trunk.position.y=height*.415;
trunk.castShadow=true;trunk.receiveShadow=true;group.add(trunk);
for(let j=0;j<3;j++){
const h=height*(.47-j*.035);
const r=height*(.24-j*.052);
const cone=new THREE.Mesh(new THREE.ConeGeometry(r,h,5+(i%2)),pineMats[(i+j)%5]);
cone.position.y=height*(.40+j*.205);cone.rotation.y=j*.6;
cone.castShadow=true;cone.receiveShadow=true;group.add(cone);
}
scene.add(group);
}
// Forest-floor stones and a ring of river rock.
const stoneGeo=new THREE.IcosahedronGeometry(1,0);
for(let i=0;i<16;i++){
const a=i/16*Math.PI*2;
const rock=mesh(stoneGeo,rockMats[i%4],Math.cos(a)*1.6,.21,Math.sin(a)*1.6);
rock.scale.set(between(.32,.47),between(.23,.38),between(.3,.43));
rock.rotation.set(rand(),rand()*3,rand());
}
for(let i=0;i<55;i++){
const a=rand()*6.28,r=between(5.5,35),s=between(.12,.65);
const rock=mesh(stoneGeo,rockMats[i%4],Math.cos(a)*r,s*.3,Math.sin(a)*r);
rock.scale.set(s*1.3,s*.7,s);rock.rotation.set(rand(),rand()*6,rand());
}
// Logs have pale, inset end grain and dark bark.
const cutMat=mat('#a27c4c'),endMat=mat('#674e33'),charMat=mat('#241c19');
function log(length,radius,position,rotation, burnt=false){
const g=new THREE.Group();
const body=new THREE.Mesh(new THREE.CylinderGeometry(radius*.86,radius,length,8),burnt?charMat:barkMats[1]);
body.castShadow=true;body.receiveShadow=true;g.add(body);
for(const side of [-1,1]){
const end=new THREE.Mesh(new THREE.CircleGeometry(radius*.83,8),burnt?endMat:cutMat);
end.rotation.x=side===1?-Math.PI/2:Math.PI/2;end.position.y=side*(length/2+.005);g.add(end);
if(!burnt){
const ring=new THREE.Mesh(new THREE.RingGeometry(radius*.44,radius*.48,8),endMat);
ring.rotation.copy(end.rotation);ring.position.y=side*(length/2+.009);g.add(ring);
}
}
g.position.copy(position);g.rotation.set(...rotation);scene.add(g);return g;
}
log(2.45,.25,new THREE.Vector3(0,.35,0),[Math.PI/2,0,.5],true);
log(2.3,.27,new THREE.Vector3(0,.48,0),[Math.PI/2,0,-.7],true);
log(1.85,.19,new THREE.Vector3(0,.65,0),[Math.PI/2,.25,1.65],true);
log(3.8,.42,new THREE.Vector3(-4,.45,1.5),[Math.PI/2,0,.4]);
log(3.3,.38,new THREE.Vector3(3.6,.4,-2.5),[Math.PI/2,0,-.65]);
log(2.8,.32,new THREE.Vector3(-2.8,.35,-4.4),[Math.PI/2,0,1.2]);
// Thousands of small faceted blades, combined into one mesh.
const grassPositions=[],grassColors=[];
const grassColor = new THREE.Color();
for(let i=0;i<2200;i++){
const a=rand()*6.283,r=between(6.6,43);
const x=Math.cos(a)*r,z=Math.sin(a)*r;
const h=between(.14,.55),w=between(.045,.12),angle=rand()*6.28;
const dx=Math.cos(angle)*w,dz=Math.sin(angle)*w;
grassPositions.push(x-dx,0,z-dz,x+dx,0,z+dz,x+between(-.14,.14),h,z+between(-.14,.14));
grassColor.setHSL(between(.22,.34),.22,between(.11,.22));
for(let j=0;j<3;j++)grassColors.push(grassColor.r,grassColor.g,grassColor.b);
}
const grassGeo=new THREE.BufferGeometry();
grassGeo.setAttribute('position',new THREE.Float32BufferAttribute(grassPositions,3));
grassGeo.setAttribute('color',new THREE.Float32BufferAttribute(grassColors,3));
grassGeo.computeVertexNormals();
mesh(grassGeo,new THREE.MeshStandardMaterial({vertexColors:true,side:THREE.DoubleSide,roughness:1}));
// A shallow bed of coals.
const coalMat=new THREE.MeshStandardMaterial({color:'#672615',emissive:'#ff480a',emissiveIntensity:1.8,flatShading:true});
for(let i=0;i<24;i++){
const a=rand()*6.28,r=rand()*1.1;
const coal=mesh(stoneGeo,coalMat,Math.cos(a)*r,.12,Math.sin(a)*r);
coal.scale.set(between(.07,.2),.07,between(.07,.2));
}
// Low-poly tongues of flame: animated vertices, not a flat billboard.
const flames=[];
function makeFlame(x,z,height,radius,color,phase){
const vertices=[],indices=[],sides=5;
const rings=[{y:0,r:.65},{y:.28,r:1},{y:.63,r:.5},{y:1,r:.015}];
for(let k=0;k<4;k++)for(let j=0;j<sides;j++){
const a=j/sides*Math.PI*2;
vertices.push(Math.cos(a)*radius*rings[k].r,rings[k].y*height,Math.sin(a)*radius*rings[k].r);
}
for(let k=0;k<3;k++)for(let j=0;j<sides;j++){
const a=k*sides+j,b=k*sides+(j+1)%sides;
indices.push(a,b,a+sides,b,b+sides,a+sides);
}
const geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.Float32BufferAttribute(vertices,3));geo.setIndex(indices);geo.computeVertexNormals();
const material=new THREE.MeshBasicMaterial({color,transparent:true,opacity:.85,side:THREE.DoubleSide,depthWrite:false,blending:THREE.AdditiveBlending});
const m=new THREE.Mesh(geo,material);m.position.set(x,.45,z);scene.add(m);
flames.push({mesh:m,base:vertices.slice(),height,phase});
}
makeFlame(0,0,2.65,.58,'#fa5e0c',0);
makeFlame(-.43,.1,1.7,.41,'#ff8b16',2);
makeFlame(.44,-.15,1.95,.4,'#ff7511',4);
makeFlame(.06,.34,1.75,.36,'#ffb72d',1);
makeFlame(0,.1,1.35,.32,'#ffe6a0',3);
makeFlame(-.2,-.3,1.65,.3,'#ffc43c',5);
const sparkCount=95,sparkPositions=new Float32Array(sparkCount*3),sparkColors=new Float32Array(sparkCount*3);
const sparkData=[];
for(let i=0;i<sparkCount;i++)sparkData.push({age:rand()*4,life:between(1.5,4),x:between(-.6,.6),z:between(-.6,.6),speed:between(.65,1.5),phase:rand()*6.28});
const sparkGeo=new THREE.BufferGeometry();
sparkGeo.setAttribute('position',new THREE.BufferAttribute(sparkPositions,3));
sparkGeo.setAttribute('color',new THREE.BufferAttribute(sparkColors,3));
const sparks=new THREE.Points(sparkGeo,new THREE.PointsMaterial({size:.065,vertexColors:true,transparent:true,blending:THREE.AdditiveBlending,depthWrite:false}));
scene.add(sparks);
function radialTexture(){
const c=document.createElement('canvas');c.width=c.height=64;
const ctx=c.getContext('2d'),g=ctx.createRadialGradient(32,32,0,32,32,32);
g.addColorStop(0,'rgba(255,255,255,.6)');g.addColorStop(.35,'rgba(255,255,255,.18)');g.addColorStop(1,'rgba(255,255,255,0)');
ctx.fillStyle=g;ctx.fillRect(0,0,64,64);return new THREE.CanvasTexture(c);
}
const glowTexture=radialTexture();
const fireGlow=new THREE.Sprite(new THREE.SpriteMaterial({map:glowTexture,color:'#ff8b24',transparent:true,opacity:.2,blending:THREE.AdditiveBlending,depthWrite:false}));
fireGlow.position.set(0,1.5,0);fireGlow.scale.set(6,6,1);scene.add(fireGlow);
const smoke=[];
for(let i=0;i<10;i++){
const sprite=new THREE.Sprite(new THREE.SpriteMaterial({map:glowTexture,color:'#9aabb3',transparent:true,opacity:.06,depthWrite:false}));
scene.add(sprite);smoke.push({sprite,offset:i/10});
}
// Stars sit beyond the fog; the moon hangs between the crowns.
const stars=[];
for(let i=0;i<1200;i++){
const a=rand()*Math.PI*2,y=between(.12,1),r=Math.sqrt(1-y*y);
stars.push(Math.cos(a)*r*100,y*100,Math.sin(a)*r*100);
}
const starGeo=new THREE.BufferGeometry();starGeo.setAttribute('position',new THREE.Float32BufferAttribute(stars,3));
const starField=new THREE.Points(starGeo,new THREE.PointsMaterial({color:'#c5d4e6',size:.13,sizeAttenuation:true,fog:false,transparent:true,opacity:.8}));
scene.add(starField);
const moon = new THREE.Mesh(new THREE.IcosahedronGeometry(1.15,3),new THREE.MeshBasicMaterial({color:'#e8ebce',fog:false}));
moon.position.set(-15,23,-31);scene.add(moon);
const moonGlow=new THREE.Sprite(new THREE.SpriteMaterial({map:glowTexture,color:'#b4cfe4',transparent:true,opacity:.22,blending:THREE.AdditiveBlending,fog:false,depthWrite:false}));
moonGlow.position.copy(moon.position);moonGlow.scale.set(12,12,1);scene.add(moonGlow);
// Small subdued lunar facets.
const craterMat=new THREE.MeshBasicMaterial({color:'#bfc8b8',transparent:true,opacity:.18,fog:false});
for(let i=0;i<7;i++){
const crater=new THREE.Mesh(new THREE.IcosahedronGeometry(between(.1,.23),1),craterMat);
const v=new THREE.Vector3(between(-.6,.6),between(-.6,.6),1).normalize().multiplyScalar(1.09);
crater.position.copy(moon.position).add(v);scene.add(crater);
}
let warmth=.7;
document.getElementById('warmth').addEventListener('input',e=>{
warmth=Number(e.target.value)/100;document.getElementById('strength').textContent=e.target.value+'%';
});
const orbitButton=document.getElementById('orbit');
orbitButton.addEventListener('click',()=>{
controls.autoRotate=!controls.autoRotate;
orbitButton.classList.toggle('active',controls.autoRotate);
orbitButton.setAttribute('aria-pressed',String(controls.autoRotate));
});
document.getElementById('reset').addEventListener('click',()=>{
camera.position.copy(home);controls.target.set(0,2.6,0);controls.autoRotate=false;
orbitButton.classList.remove('active');orbitButton.setAttribute('aria-pressed','false');controls.update();
});
window.addEventListener('resize',()=>{
camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);renderer.setPixelRatio(Math.min(devicePixelRatio,2));
});
const clock=new THREE.Clock();
function animate(){
const dt=Math.min(clock.getDelta(),.05),t=clock.elapsedTime;
const flicker=1+Math.sin(t*13)*.075+Math.sin(t*23.7)*.045+Math.sin(t*7.1)*.08;
fireLight.intensity=(35+warmth*100)*flicker;fill.intensity=warmth*23;
fireGlow.material.opacity=(.1+warmth*.17)*flicker;
for(const f of flames){
const p=f.mesh.geometry.attributes.position;
for(let i=0;i<p.count;i++){
const k=i*3,y=f.base[k+1],ratio=y/f.height;
p.setXYZ(i,f.base[k]+Math.sin(t*4+f.phase+ratio*3)*.24*ratio,
y*(.91+Math.sin(t*6+f.phase)*.1),
f.base[k+2]+Math.cos(t*3.3+f.phase+ratio*2)*.18*ratio);
}
p.needsUpdate=true;
f.mesh.scale.setScalar(.8+warmth*.3);
f.mesh.material.opacity=.72+Math.sin(t*9+f.phase)*.1;
}
for(let i=0;i<sparkCount;i++){
const d=sparkData[i];d.age+=dt;
if(d.age>d.life){d.age=0;d.x=between(-.6,.6);d.z=between(-.6,.6);}
const f=d.age/d.life,k=i*3;
sparkPositions[k]=d.x+Math.sin(t*1.7+d.phase)*f*.65+f*f*.7;
sparkPositions[k+1]=.8+d.age*d.speed;
sparkPositions[k+2]=d.z+Math.cos(t*1.3+d.phase)*f*.5;
sparkColors[k]=(1-f)*1.3;sparkColors[k+1]=(1-f)*.52;sparkColors[k+2]=(1-f)*.09;
}
sparkGeo.attributes.position.needsUpdate=true;sparkGeo.attributes.color.needsUpdate=true;
for(const s of smoke){
const a=(t*.1+s.offset)%1;
s.sprite.position.set(Math.sin(a*4+t*.2)*a*.7+a*a*2,1.5+a*6,Math.cos(a*4)*a*.5);
s.sprite.scale.setScalar(1+a*3);s.sprite.material.opacity=Math.sin(a*Math.PI)*.075;
}
controls.update();renderer.render(scene,camera);requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>Reasoning trace (747 tokens)
**Polishing the scene design** I'm considering creating a polished scene with a budget around $6k. The idea is to design a full-screen, stylized 3D forest with overlay typography saying "EMBER / NIGHT...". I want to include subtle panel toggles for field notes and sound, while avoiding anything unnecessary. A large campfire with a rock ring and animated flame in cone shapes should be visually centered. The camera position would be (15,10,19) looking at (0,2,0), with a clearing surrounded by 90 trees featuring pine trunks and some bare branches under a visible moon in the sky.**Designing forest lighting and environment** I'm focusing on the lighting for the scene, with a main atmospheric brightness set to a dark blue, but still visible. The warm light will create shadows on the ground and trees, with directional moonlight at an intensity of 1.8 and a blue ambient light of 0.55. I'll adjust the tone using ACES with an exposure of 1.3. Fog in the forest will have an exposure of 0.023, and I’ll create a circular ground with a radius of 65 in dark green, and a clearing with a radius of 8 in brown. I’m thinking of using irregular mesh geometry and adding instanced grass tufts, scattered stones, and decorative elements like mushrooms and ferns. For the campfire, I’ll set it to a scale of 1.3, with logs positioned around it, and place a sitting log at coordinates x4 z2, shifting left to -4, -1 with cut ends visible. Stars will be points, and the moon will be a sphere at (-17, 27, -30), creating a matching directional glow. For the flame shader, I’m considering a custom translucent low-poly geometry with an uneven shape, featuring a yellow core and an orange outer layer, along with standard emissive mesh.**Creating flame and smoke effects** I'm designing a custom flame mesh geometry that tapers with rings, each having 5 vertices. The radius will vary to give the flame a dynamic shape, animating the vertices based on base positions over time. I’ll use a MeshBasic material with a transparent opacity of 0.85 and apply additive blending without depth writing, incorporating yellow inner meshes. For lighting, I plan a point light with an intensity of 85, with a distance of 25 and decay set to 2. Adding in a secondary orange light with an intensity of 15 will enhance the scene. The fire shape will be 2.2 high atop the logs, and the low-poly design will be visible. I'll also update the Sparks BufferGeometry. For smoke effects, I’ll generate thin particle sprites with a subtle gray radial texture, creating a gentle drift without being too overpowering.**Designing a scene layout** I'm thinking about how to set up the scene. I should position the time and temperature in the top right corner and use controls for features like fire intensity and auto orbit. The instructions should go in the center bottom. I want to start rendering the scene with the fire in the center and ensure the camera captures the moon properly. I also need to use a pleasing background and add stars, with considerations for silhouette elements.**Deciding on code output size** I’m considering how much code to output. It seems like I need to decide whether to go for about 500 lines or trim it down to around 300 lines. That's quite a range, so I should think about the effectiveness and clarity of the code as well. I want to make sure that whatever I provide is valuable and not overwhelming, so maybe sticking to 300 lines would be the better choice overall.