Claude Sonnet 5.5
by Anthropicreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 23 s
- Cost
- $0.134
- Output tokens
- 13,235
- Spent thinking
- 3,345 tokens
- Speed
- 163.4 tok/s
- Console errors
- 0
- First token after
- 2.3 s
- First visible text after
- 32.8 s
- Prompt tokens
- 855
- Code size
- 17.5 KB
- Finish reason
- stop
- Served by
- Claude Platform on AWS
- Model id
anthropic/claude-sonnet-5.5- 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 · 430 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Low-poly Campfire</title>
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #050810; }
canvas { display: block; }
#hint {
position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%);
color: #e9c9a0; font: 13px/1.3 system-ui, sans-serif; letter-spacing: .04em;
background: rgba(10,12,20,.55); padding: 6px 14px; border-radius: 14px;
pointer-events: none; white-space: nowrap; opacity: .85;
}
</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="hint">Drag to orbit · scroll to zoom · right-drag to pan</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// ---------- helpers ----------
let seed = 1337;
function rand() { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }
const rr = (a, b) => a + (b - a) * rand();
const smooth = (a, b, x) => { const t = Math.min(1, Math.max(0, (x - a) / (b - a))); return t * t * (3 - 2 * t); };
function groundH(x, z) {
const r = Math.hypot(x, z);
const n = Math.sin(x * 0.28) * 0.5 + Math.cos(z * 0.23) * 0.5 + Math.sin((x + z) * 0.13) * 0.8 + Math.sin(x * 0.7 + z * 0.5) * 0.12;
return n * 0.55 * smooth(5, 16, r);
}
function glowTexture(inner = 'rgba(255,255,255,1)', mid = 'rgba(255,255,255,0.35)') {
const c = document.createElement('canvas'); c.width = c.height = 64;
const g = c.getContext('2d');
const gr = g.createRadialGradient(32, 32, 0, 32, 32, 32);
gr.addColorStop(0, inner); gr.addColorStop(0.35, mid); gr.addColorStop(1, 'rgba(255,255,255,0)');
g.fillStyle = gr; g.fillRect(0, 0, 64, 64);
const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t;
}
// ---------- renderer / scene ----------
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.1;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const FOG = 0x0b1222;
scene.background = new THREE.Color(0x060a16);
scene.fog = new THREE.Fog(FOG, 8, 62);
const camera = new THREE.PerspectiveCamera(62, innerWidth / innerHeight, 0.1, 700);
camera.position.set(7, 3, 9);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.8, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.minDistance = 3;
controls.maxDistance = 32;
controls.maxPolarAngle = Math.PI / 2 - 0.06;
controls.minPolarAngle = 0.2;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.35;
controls.addEventListener('start', () => controls.autoRotate = false);
controls.update();
// ---------- lights ----------
scene.add(new THREE.HemisphereLight(0x3a4f88, 0x0d0a10, 0.75));
const moonDir = new THREE.Vector3(-0.55, 0.38, -0.75).normalize();
const moonLight = new THREE.DirectionalLight(0x7f9bdc, 0.9);
moonLight.position.copy(moonDir).multiplyScalar(60);
scene.add(moonLight);
const fireLight = new THREE.PointLight(0xff7a2a, 120, 45, 2);
fireLight.position.set(0, 1.3, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.4;
fireLight.shadow.camera.far = 45;
fireLight.shadow.bias = -0.003;
fireLight.shadow.radius = 3;
scene.add(fireLight);
// ---------- sky: stars & moon ----------
{
const N = 1800, pos = new Float32Array(N * 3), col = new Float32Array(N * 3);
for (let i = 0; i < N; i++) {
const u = rand(), v = 0.02 + rand() * 0.98;
const th = u * Math.PI * 2, y = v, s = Math.sqrt(1 - y * y);
pos[i * 3] = Math.cos(th) * s * 400; pos[i * 3 + 1] = y * 400; pos[i * 3 + 2] = Math.sin(th) * s * 400;
const b = 0.5 + rand() * 0.5, tint = rand();
col[i * 3] = b * (0.8 + 0.2 * tint); col[i * 3 + 1] = b * 0.9; col[i * 3 + 2] = b;
}
const g = new THREE.BufferGeometry();
g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
g.setAttribute('color', new THREE.BufferAttribute(col, 3));
const stars = new THREE.Points(g, new THREE.PointsMaterial({ size: 2, sizeAttenuation: false, vertexColors: true, fog: false, depthWrite: false }));
stars.frustumCulled = false;
scene.add(stars);
const moon = new THREE.Mesh(new THREE.IcosahedronGeometry(16, 1), new THREE.MeshBasicMaterial({ color: 0xf2f0dc, fog: false }));
moon.position.copy(moonDir).multiplyScalar(380);
scene.add(moon);
const halo = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTexture('rgba(190,210,255,0.9)', 'rgba(120,150,230,0.25)'), blending: THREE.AdditiveBlending, depthWrite: false, fog: false, transparent: true }));
halo.scale.set(150, 150, 1);
halo.position.copy(moon.position);
scene.add(halo);
}
// ---------- ground ----------
{
let g = new THREE.PlaneGeometry(160, 160, 80, 80);
g.rotateX(-Math.PI / 2);
const p = g.attributes.position;
for (let i = 0; i < p.count; i++) {
const x = p.getX(i) + (rand() - 0.5) * 0.9, z = p.getZ(i) + (rand() - 0.5) * 0.9;
p.setX(i, x); p.setZ(i, z); p.setY(i, groundH(x, z));
}
g = g.toNonIndexed();
g.computeVertexNormals();
const P = g.attributes.position, colors = new Float32Array(P.count * 3);
const cA = new THREE.Color(0x1d3322), cB = new THREE.Color(0x2b4a2a), cD = new THREE.Color(0x3b2c1e), c = new THREE.Color();
for (let i = 0; i < P.count; i += 3) {
const cx = (P.getX(i) + P.getX(i + 1) + P.getX(i + 2)) / 3, cz = (P.getZ(i) + P.getZ(i + 1) + P.getZ(i + 2)) / 3;
const r = Math.hypot(cx, cz);
c.copy(cA).lerp(cB, rand());
c.lerp(cD, 1 - smooth(3, 7.5, r + rand() * 1.5));
for (let k = 0; k < 3; k++) { colors[(i + k) * 3] = c.r; colors[(i + k) * 3 + 1] = c.g; colors[(i + k) * 3 + 2] = c.b; }
}
g.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const ground = new THREE.Mesh(g, new THREE.MeshStandardMaterial({ vertexColors: true, flatShading: true, roughness: 1 }));
ground.receiveShadow = true;
scene.add(ground);
}
// ---------- rocks ----------
const rockMats = [0x6b6e78, 0x585b66, 0x7a7770].map(c => new THREE.MeshStandardMaterial({ color: c, flatShading: true, roughness: 0.95 }));
function makeRock(size) {
const g = new THREE.IcosahedronGeometry(1, 1);
const p = g.attributes.position, off = rand() * 100;
for (let i = 0; i < p.count; i++) {
const x = p.getX(i), y = p.getY(i), z = p.getZ(i);
const k = 0.78 + 0.32 * (Math.sin(x * 4.1 + off) * Math.cos(z * 3.7 + off) * 0.5 + 0.5) + 0.1 * Math.sin(y * 5 + off);
p.setXYZ(i, x * k, y * k * 0.75, z * k);
}
g.computeVertexNormals();
const m = new THREE.Mesh(g, rockMats[Math.floor(rand() * rockMats.length)]);
m.scale.set(size * rr(0.8, 1.3), size * rr(0.7, 1.1), size * rr(0.8, 1.3));
m.rotation.y = rand() * 6.28;
m.castShadow = m.receiveShadow = true;
return m;
}
// fire ring stones
for (let i = 0; i < 11; i++) {
const a = i / 11 * Math.PI * 2 + rr(-0.1, 0.1), r = 1.35 + rr(-0.08, 0.1);
const m = makeRock(rr(0.28, 0.4));
m.position.set(Math.cos(a) * r, 0.1, Math.sin(a) * r);
scene.add(m);
}
// scattered rocks
for (let i = 0; i < 16; i++) {
const a = rand() * Math.PI * 2, r = rr(3.5, 22);
const m = makeRock(rr(0.4, 1.4));
m.position.set(Math.cos(a) * r, groundH(Math.cos(a) * r, Math.sin(a) * r) + 0.1, Math.sin(a) * r);
scene.add(m);
}
// ---------- logs ----------
const barkMat = new THREE.MeshStandardMaterial({ color: 0x4a3222, flatShading: true, roughness: 1 });
const endMat = new THREE.MeshStandardMaterial({ color: 0x8a6a45, flatShading: true, roughness: 1 });
function makeLog(len, rad) {
const g = new THREE.CylinderGeometry(rad, rad * 1.05, len, 7, 1);
const m = new THREE.Mesh(g, [barkMat, endMat, endMat]);
m.castShadow = m.receiveShadow = true;
return m;
}
// seating logs
for (let i = 0; i < 3; i++) {
const a = i / 3 * Math.PI * 2 + 0.5, r = 3.6;
const l = makeLog(2.4, 0.3);
l.rotation.z = Math.PI / 2;
const holder = new THREE.Group();
holder.add(l);
holder.position.set(Math.cos(a) * r, 0.3, Math.sin(a) * r);
holder.rotation.y = -a + Math.PI / 2 + rr(-0.15, 0.15);
scene.add(holder);
}
// logs in the fire (teepee)
const fireGroup = new THREE.Group();
scene.add(fireGroup);
for (let i = 0; i < 6; i++) {
const a = i / 6 * Math.PI * 2 + 0.3;
const l = makeLog(1.9, 0.12);
const h = new THREE.Group();
l.position.y = 0.85;
h.add(l);
h.rotation.set(0, -a, 0);
const tilt = new THREE.Group();
tilt.add(h);
h.rotation.y = 0;
tilt.position.set(Math.cos(a) * 0.55, 0.05, Math.sin(a) * 0.55);
tilt.rotation.set(0, 0, 0);
// tilt towards center
tilt.lookAt(0, 3, 0);
tilt.rotateX(Math.PI / 2 - 0.0);
fireGroup.add(tilt);
tilt.rotation.order = 'YXZ';
}
// two flat base logs
for (let i = 0; i < 2; i++) {
const l = makeLog(1.8, 0.14);
l.rotation.z = Math.PI / 2;
const h = new THREE.Group(); h.add(l);
h.position.y = 0.16; h.rotation.y = i * 1.6 + 0.4;
fireGroup.add(h);
}
// coals
{
const coal = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 1.1, 0.12, 8),
new THREE.MeshStandardMaterial({ color: 0x220a04, emissive: 0xff3a08, emissiveIntensity: 1.3, flatShading: true }));
coal.position.y = 0.06;
scene.add(coal);
const glow = new THREE.Mesh(new THREE.CircleGeometry(4.2, 24).rotateX(-Math.PI / 2),
new THREE.MeshBasicMaterial({ map: glowTexture('rgba(255,140,50,0.9)', 'rgba(255,90,20,0.3)'), transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, opacity: 0.55 }));
glow.position.y = 0.04;
scene.add(glow);
}
// ---------- trees ----------
const leafMats = [0x16301f, 0x1b3a28, 0x12281d, 0x203f26].map(c => new THREE.MeshStandardMaterial({ color: c, flatShading: true, roughness: 0.9 }));
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x3a281b, flatShading: true, roughness: 1 });
const trunkGeo = new THREE.CylinderGeometry(0.16, 0.28, 1.6, 6);
const coneGeos = [
new THREE.ConeGeometry(1.5, 2.2, 7),
new THREE.ConeGeometry(1.15, 2.0, 7),
new THREE.ConeGeometry(0.8, 1.8, 7)
];
function makeTree() {
const t = new THREE.Group();
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.y = 0.8; trunk.castShadow = true; trunk.receiveShadow = true;
t.add(trunk);
const mat = leafMats[Math.floor(rand() * leafMats.length)];
const ys = [1.9, 3.1, 4.2];
for (let i = 0; i < 3; i++) {
const c = new THREE.Mesh(coneGeos[i], mat);
c.position.y = ys[i] + 0.2; c.rotation.y = rand() * 6.28;
c.castShadow = true; c.receiveShadow = true;
t.add(c);
}
return t;
}
const placed = [];
let count = 0, tries = 0;
while (count < 90 && tries < 3000) {
tries++;
const a = rand() * Math.PI * 2;
const r = 8.5 + Math.pow(rand(), 0.8) * 48;
const x = Math.cos(a) * r, z = Math.sin(a) * r;
if (placed.some(q => Math.hypot(q[0] - x, q[1] - z) < 2.6)) continue;
placed.push([x, z]);
const t = makeTree();
const s = rr(0.9, 1.9);
t.scale.set(s * rr(0.9, 1.1), s * rr(0.9, 1.2), s * rr(0.9, 1.1));
t.position.set(x, groundH(x, z) - 0.1, z);
t.rotation.y = rand() * 6.28;
scene.add(t);
count++;
}
// a few stumps/fallen logs near the clearing edge
for (let i = 0; i < 4; i++) {
const a = rand() * Math.PI * 2, r = rr(6, 9);
const l = makeLog(rr(2, 3.5), 0.28);
const x = Math.cos(a) * r, z = Math.sin(a) * r;
l.position.set(x, groundH(x, z) + 0.25, z);
l.rotation.set(0, rand() * 6.28, Math.PI / 2);
l.rotation.order = 'YXZ';
scene.add(l);
}
// ---------- flames ----------
const flames = [];
function addFlame(r, h, color, opacity, px, pz, speed) {
const g = new THREE.ConeGeometry(r, h, 6, 3, true);
g.translate(0, h / 2, 0);
// jitter for low-poly irregularity
const p = g.attributes.position;
for (let i = 0; i < p.count; i++) {
const y = p.getY(i) / h;
if (y > 0.05 && y < 0.95) { p.setX(i, p.getX(i) * (1 + (rand() - 0.5) * 0.3)); p.setZ(i, p.getZ(i) * (1 + (rand() - 0.5) * 0.3)); }
}
const m = new THREE.Mesh(g, new THREE.MeshBasicMaterial({ color, transparent: true, opacity, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide, fog: false }));
m.position.set(px, 0.2, pz);
scene.add(m);
flames.push({ m, h, phase: rand() * 100, speed, bx: px, bz: pz });
}
for (let i = 0; i < 7; i++) {
const a = i / 7 * Math.PI * 2;
addFlame(0.32, rr(1.1, 1.5), 0xff5a10, 0.55, Math.cos(a) * 0.4, Math.sin(a) * 0.4, rr(5, 8));
}
for (let i = 0; i < 4; i++) {
const a = i / 4 * Math.PI * 2 + 0.4;
addFlame(0.28, rr(1.0, 1.4), 0xff9a20, 0.6, Math.cos(a) * 0.2, Math.sin(a) * 0.2, rr(6, 9));
}
addFlame(0.3, 1.9, 0xff7a18, 0.55, 0, 0, 4.5);
addFlame(0.18, 1.0, 0xffe27a, 0.8, 0, 0, 7);
addFlame(0.14, 0.7, 0xfff2b0, 0.9, 0.08, -0.05, 9);
const fireGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTexture('rgba(255,170,80,0.8)', 'rgba(255,100,30,0.25)'), blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, fog: false }));
fireGlow.position.y = 1.0; fireGlow.scale.set(5, 5, 1);
scene.add(fireGlow);
// ---------- sparks ----------
const SN = 160;
const sPos = new Float32Array(SN * 3), sCol = new Float32Array(SN * 3);
const sVel = new Float32Array(SN * 3), sLife = new Float32Array(SN), sMax = new Float32Array(SN);
function respawn(i, initial) {
const a = rand() * Math.PI * 2, r = Math.sqrt(rand()) * 0.5;
sPos[i * 3] = Math.cos(a) * r; sPos[i * 3 + 1] = 0.5 + rand() * 0.6; sPos[i * 3 + 2] = Math.sin(a) * r;
sVel[i * 3] = rr(-0.35, 0.35); sVel[i * 3 + 1] = rr(1.2, 3.2); sVel[i * 3 + 2] = rr(-0.35, 0.35);
sMax[i] = rr(1.5, 4.0);
sLife[i] = initial ? rand() * sMax[i] : 0;
if (initial) { sPos[i * 3 + 1] += sVel[i * 3 + 1] * sLife[i] * 0.6; }
}
for (let i = 0; i < SN; i++) respawn(i, true);
const sGeo = new THREE.BufferGeometry();
sGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3));
sGeo.setAttribute('color', new THREE.BufferAttribute(sCol, 3));
const sparks = new THREE.Points(sGeo, new THREE.PointsMaterial({
size: 0.2, map: glowTexture('rgba(255,255,255,1)', 'rgba(255,255,255,0.5)'), vertexColors: true,
transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true, fog: false
}));
sparks.frustumCulled = false;
scene.add(sparks);
// ---------- fireflies ----------
const FN = 25, ff = [];
const ffGeo = new THREE.BufferGeometry();
const ffPos = new Float32Array(FN * 3);
for (let i = 0; i < FN; i++) {
const a = rand() * 6.28, r = rr(5, 18);
ff.push({ a, r, y: rr(0.6, 3), s: rr(0.1, 0.3), p: rand() * 10 });
}
ffGeo.setAttribute('position', new THREE.BufferAttribute(ffPos, 3));
const fireflies = new THREE.Points(ffGeo, new THREE.PointsMaterial({
size: 0.16, color: 0xbfff70, map: glowTexture(), transparent: true, blending: THREE.AdditiveBlending, depthWrite: false
}));
fireflies.frustumCulled = false;
scene.add(fireflies);
// ---------- animate ----------
const clock = new THREE.Clock();
const tmpC = new THREE.Color();
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05), t = clock.elapsedTime;
// flames
for (const f of flames) {
const n = Math.sin(t * f.speed + f.phase) * 0.5 + Math.sin(t * f.speed * 2.3 + f.phase * 1.7) * 0.3 + 0.8;
const sy = 0.75 + 0.35 * n;
f.m.scale.set(1 - (sy - 1) * 0.4, sy, 1 - (sy - 1) * 0.4);
f.m.rotation.y += dt * 1.5;
f.m.rotation.x = Math.sin(t * 3 + f.phase) * 0.1;
f.m.rotation.z = Math.cos(t * 2.6 + f.phase * 2) * 0.1;
f.m.position.x = f.bx + Math.sin(t * 4 + f.phase) * 0.04;
f.m.position.z = f.bz + Math.cos(t * 3.5 + f.phase) * 0.04;
}
// light flicker
const fl = Math.sin(t * 13) * 0.5 + Math.sin(t * 23.7 + 1) * 0.35 + Math.sin(t * 7.3) * 0.5 + (Math.random() - 0.5) * 0.5;
fireLight.intensity = 115 + fl * 22;
fireLight.position.set(Math.sin(t * 9) * 0.1, 1.3 + Math.sin(t * 11) * 0.08, Math.cos(t * 8) * 0.1);
fireLight.color.setHSL(0.07 + fl * 0.008, 1, 0.55);
fireGlow.scale.setScalar(4.6 + fl * 0.5);
fireGlow.material.opacity = 0.8 + fl * 0.1;
// sparks
for (let i = 0; i < SN; i++) {
sLife[i] += dt;
if (sLife[i] > sMax[i]) respawn(i, false);
const l = sLife[i] / sMax[i];
sVel[i * 3] += (Math.sin(t * 2 + i) * 0.6) * dt;
sVel[i * 3 + 2] += (Math.cos(t * 1.7 + i * 1.3) * 0.6) * dt;
sPos[i * 3] += sVel[i * 3] * dt;
sPos[i * 3 + 1] += sVel[i * 3 + 1] * dt;
sPos[i * 3 + 2] += sVel[i * 3 + 2] * dt;
sVel[i * 3 + 1] *= 1 - 0.1 * dt;
const fade = Math.pow(1 - l, 1.5);
tmpC.setRGB(1, 0.75 - l * 0.45, 0.25 - l * 0.2).multiplyScalar(fade * (0.7 + 0.3 * Math.sin(t * 30 + i)));
sCol[i * 3] = tmpC.r; sCol[i * 3 + 1] = tmpC.g; sCol[i * 3 + 2] = tmpC.b;
}
sGeo.attributes.position.needsUpdate = true;
sGeo.attributes.color.needsUpdate = true;
// fireflies
for (let i = 0; i < FN; i++) {
const f = ff[i], a = f.a + t * f.s * 0.3;
ffPos[i * 3] = Math.cos(a) * f.r + Math.sin(t + f.p) * 0.8;
ffPos[i * 3 + 1] = f.y + Math.sin(t * 0.8 + f.p) * 0.5;
ffPos[i * 3 + 2] = Math.sin(a) * f.r + Math.cos(t * 0.9 + f.p) * 0.8;
}
ffGeo.attributes.position.needsUpdate = true;
controls.update();
// keep camera above the ground
const minY = groundH(camera.position.x, camera.position.z) + 0.6;
if (camera.position.y < minY) camera.position.y = minY;
renderer.render(scene, camera);
}
animate();
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
});
</script>
</body>
</html>Reasoning trace (3,345 tokens)
I'm planning a single HTML file that runs a Three.js scene, with shadows, fog, and a background color set up on the renderer. The ground will be a circular low-poly plane with flat shading and displaced vertices, leaving a clear area in the center. For the rocks I'm going with icosahedrons at low detail, randomly scaled and jittered. Now I'm working through the trees and flames: pine trees as cone layers over a trunk, about 60 of them at random radii, with shadows only near the center. The flames are emissive cones that scale and sway, and the point light flickers with a warm orange color and decay. Next I'm setting up the sky: a star field on a large sphere that ignores fog, a dim moon with a glow sprite, and some faint bluish moonlight. The fog will be dark blue so distant trees fade out. I'm also looking at the orbit controls, though that part stops right at the polar angle setting. I'm setting the orbit controls' polar limit just under the horizon, with the target raised to about 1.2 and a camera height clamp so it never dips below the ground. Then I'm deciding the ground displacement stays small outside the clearing, and I'm starting to think about slow auto-rotation for the view. Now I'm placing the ground glow, embers, and trees: a fire-stone ring and a flat orange disc under the fire, dark red emissive coals, and rocks and trees set to cast or receive shadows. I'll use a seeded random so the layout stays the same each run, with trees spread between radius 8 and 50, kept away from the fire and the clearing, and spaced apart by rejection. The ground height uses a sine-based noise that only rises beyond radius 5 to 15. Still setting up the ground, which will be a 140-unit plane with vertices nudged for a low-poly look and its height driven by the noise function. I'm converting it to non-indexed geometry so each triangle can take its own grass color, with a darker green-blue overall and brownish tones near the fire. Now I'm writing the flames as a group of cones built from a function that takes radius, height, color, and opacity, with additive blending and no depth writes. Seven outer flames sit in a ring with a few taller central ones, each pulsing in scale and swaying with a noise-driven phase. Sparks come after that: about 150 points that respawn near the fire base and fade from orange to dark as their life runs down. Now I'm adding the sparks as points with a radial gradient texture, and I'm skipping smoke since it's optional. For the trees, I'm building each one from a short trunk with three stacked cones, using a few shared green materials and random scale and rotation for variety. The moonlight will come from a dim bluish directional light. I'm now sorting out the rocks: low-detail icosahedrons whose vertices get a deterministic, position-based scale so the jitter stays consistent across shared corners. Then I'll place a ring of about ten small stones around the fire, with larger rocks further out and three logs seated at roughly radius 3. Now I'm adding the fire's light and the logs: a flickering point light with shadows enabled, flames that don't cast shadows, and a glow sprite at the fire's base. Next I'm placing the moon as a low-poly sphere far off in the sky, and checking whether the starting camera view will show it. I'm checking the moon's placement against the camera's view, and the elevation at 0.33 looks marginal, so I'm lowering the camera and moving its target up to see if the moon falls within the frame. I'm settling the camera so the moon lands in view: with the target at y 1.8 and the moon about 22° up, it sits inside the frame, and the camera's azimuth lines up with the moon's direction.