Claude Haiku 5.5
by AnthropicReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 11 s
- Cost
- $0.0086
- Output tokens
- 17,048
- Spent thinking
- 8,114 tokens
- Speed
- 248.9 tok/s
- Console errors
- 0
- First token after
- 2.5 s
- First visible text after
- 44.1 s
- Prompt tokens
- 855
- Code size
- 16.4 KB
- Finish reason
- stop
- Served by
- Claude Platform on AWS
- Model id
anthropic/claude-haiku-5.5- Reasoning sent
{"effort":"medium"}- Max output
- 32,000 tokens
- Generated
- Nov 10, 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 · 536 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Low-Poly Campfire at Night</title>
<style>
html, body {
margin: 0;
height: 100%;
overflow: hidden;
background: #060a14;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
canvas { display: block; }
#hud {
position: fixed;
left: 16px;
top: 14px;
color: #e9dcc4;
pointer-events: none;
text-shadow: 0 1px 6px rgba(0,0,0,0.8);
user-select: none;
}
#hud h1 {
margin: 0;
font-size: 20px;
font-weight: 600;
letter-spacing: 0.5px;
}
#hud p {
margin: 4px 0 0;
font-size: 13px;
opacity: 0.75;
}
#hint {
position: fixed;
left: 50%;
bottom: 14px;
transform: translateX(-50%);
color: #cdbf9f;
font-size: 13px;
opacity: 0.7;
pointer-events: none;
white-space: nowrap;
text-shadow: 0 1px 6px rgba(0,0,0,0.9);
user-select: none;
}
@media (max-width: 480px) {
#hud h1 { font-size: 16px; }
#hud p { font-size: 11px; }
#hint { font-size: 11px; white-space: normal; text-align: center; width: 90%; }
}
</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="hud">
<h1>Campfire Clearing</h1>
<p>Low-poly night scene</p>
</div>
<div id="hint">Drag to look around · Scroll to zoom · Camera stays above the ground</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// ---------- Deterministic random ----------
function mulberry32(a) {
return function () {
a |= 0; a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
const rand = mulberry32(1337);
function smooth(e0, e1, x) {
const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
return t * t * (3 - 2 * t);
}
// ---------- 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.PCFShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const skyColor = 0x070c18;
scene.background = new THREE.Color(skyColor);
scene.fog = new THREE.Fog(0x0b1224, 16, 70);
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 3.2, 13);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.target.set(0, 1.3, 0);
controls.minDistance = 3;
controls.maxDistance = 40;
controls.minPolarAngle = 0.2;
controls.maxPolarAngle = Math.PI * 0.47; // never dips below the ground
controls.autoRotate = true;
controls.autoRotateSpeed = 0.35;
controls.addEventListener('start', () => { controls.autoRotate = false; });
controls.update();
// ---------- Lights ----------
const hemi = new THREE.HemisphereLight(0x3a4f7a, 0x1a1a14, 0.55);
scene.add(hemi);
const moonDir = new THREE.Vector3(-120, 200, -180);
const moonLight = new THREE.DirectionalLight(0x8fa6d6, 0.35);
moonLight.position.copy(moonDir);
scene.add(moonLight);
const fireLight = new THREE.PointLight(0xff8a3c, 42, 24, 2);
fireLight.position.set(0, 1.4, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.1;
fireLight.shadow.camera.far = 30;
fireLight.shadow.bias = -0.002;
fireLight.shadow.normalBias = 0.02;
scene.add(fireLight);
// ---------- Ground ----------
function heightAt(x, z) {
const d = Math.hypot(x, z);
const amp = smooth(8, 26, d) * 0.9;
return Math.sin(x * 0.31) * Math.cos(z * 0.27) * amp;
}
const groundGeo = new THREE.PlaneGeometry(240, 240, 80, 80);
groundGeo.rotateX(-Math.PI / 2);
{
const p = groundGeo.attributes.position;
for (let i = 0; i < p.count; i++) {
const x = p.getX(i), z = p.getZ(i);
const d = Math.hypot(x, z);
const jitter = (rand() - 0.5) * 0.25 * smooth(7, 16, d);
p.setY(i, heightAt(x, z) + jitter);
}
groundGeo.computeVertexNormals();
}
const groundMat = new THREE.MeshStandardMaterial({
color: 0x1f3320, roughness: 1, metalness: 0, flatShading: true
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.receiveShadow = true;
scene.add(ground);
// Dirt clearing around the fire
const dirtGeo = new THREE.CircleGeometry(7.5, 24);
dirtGeo.rotateX(-Math.PI / 2);
const dirt = new THREE.Mesh(dirtGeo, new THREE.MeshStandardMaterial({
color: 0x2b2119, roughness: 1, flatShading: true
}));
dirt.position.y = 0.03;
dirt.receiveShadow = true;
scene.add(dirt);
// ---------- Shared materials ----------
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x3b2a1c, roughness: 0.95, flatShading: true });
const foliageMats = [0x1c3d22, 0x23482a, 0x172f1d, 0x2a5030].map(c =>
new THREE.MeshStandardMaterial({ color: c, roughness: 0.9, flatShading: true })
);
const rockMat = new THREE.MeshStandardMaterial({ color: 0x5a5d62, roughness: 0.95, flatShading: true });
const logMat = new THREE.MeshStandardMaterial({ color: 0x4a3420, roughness: 0.9, flatShading: true });
// ---------- Trees ----------
const trunkGeo = new THREE.CylinderGeometry(0.18, 0.28, 2.4, 5);
const foliageGeoA = new THREE.ConeGeometry(1.7, 3.0, 6);
const foliageGeoB = new THREE.ConeGeometry(1.3, 2.4, 6);
const foliageGeoC = new THREE.ConeGeometry(0.9, 1.8, 6);
const trees = [];
const treePositions = [];
let attempts = 0;
while (trees.length < 42 && attempts < 4000) {
attempts++;
const a = rand() * Math.PI * 2;
const r = 11 + Math.sqrt(rand()) * 36;
const x = Math.cos(a) * r, z = Math.sin(a) * r;
let ok = true;
for (const q of treePositions) {
if (Math.hypot(q.x - x, q.z - z) < 2.8) { ok = false; break; }
}
if (!ok) continue;
treePositions.push({ x, z });
const s = 0.8 + rand() * 0.6;
const g = new THREE.Group();
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.y = 1.2;
trunk.castShadow = true;
g.add(trunk);
const fm = foliageMats[Math.floor(rand() * foliageMats.length)];
const cA = new THREE.Mesh(foliageGeoA, fm); cA.position.y = 3.3; cA.castShadow = true;
const cB = new THREE.Mesh(foliageGeoB, fm); cB.position.y = 4.6; cB.castShadow = true;
const cC = new THREE.Mesh(foliageGeoC, fm); cC.position.y = 5.8; cC.castShadow = true;
g.add(cA, cB, cC);
g.position.set(x, heightAt(x, z) - 0.1, z);
g.scale.setScalar(s);
g.rotation.y = rand() * Math.PI * 2;
scene.add(g);
trees.push(g);
}
// ---------- Rocks ----------
function makeRock(radius, squash) {
const geo = new THREE.IcosahedronGeometry(radius, 0);
const p = geo.attributes.position;
for (let i = 0; i < p.count; i++) {
const k = 0.78 + rand() * 0.44;
p.setXYZ(i, p.getX(i) * k, p.getY(i) * k * squash, p.getZ(i) * k);
}
geo.computeVertexNormals();
const m = new THREE.Mesh(geo, rockMat);
m.castShadow = true;
m.receiveShadow = true;
return m;
}
// Stone ring around the fire pit
for (let i = 0; i < 10; i++) {
const a = (i / 10) * Math.PI * 2 + rand() * 0.2;
const rock = makeRock(0.28 + rand() * 0.12, 0.6);
rock.position.set(Math.cos(a) * 1.25, 0.1, Math.sin(a) * 1.25);
rock.rotation.set(rand(), rand() * 3, rand());
scene.add(rock);
}
// Scattered larger rocks in the clearing and beyond
for (let i = 0; i < 16; i++) {
const a = rand() * Math.PI * 2;
const r = 4 + rand() * 9;
const rock = makeRock(0.35 + rand() * 0.6, 0.55 + rand() * 0.3);
rock.position.set(Math.cos(a) * r, heightAt(Math.cos(a) * r, Math.sin(a) * r) + 0.1, Math.sin(a) * r);
rock.rotation.set(rand(), rand() * 3, rand());
scene.add(rock);
}
// ---------- Logs ----------
const logGeo = new THREE.CylinderGeometry(0.2, 0.23, 2.2, 7);
// Seating logs around the fire
for (let i = 0; i < 5; i++) {
const a = (i / 5) * Math.PI * 2 + 0.4 + rand() * 0.3;
const log = new THREE.Mesh(logGeo, logMat);
log.position.set(Math.cos(a) * 3.2, 0.2, Math.sin(a) * 3.2);
log.rotation.set(0, -a - Math.PI / 2, Math.PI / 2);
log.castShadow = true;
log.receiveShadow = true;
scene.add(log);
}
// Fallen logs further out
for (let i = 0; i < 5; i++) {
const a = rand() * Math.PI * 2;
const r = 9 + rand() * 8;
const x = Math.cos(a) * r, z = Math.sin(a) * r;
const log = new THREE.Mesh(logGeo, logMat);
log.position.set(x, heightAt(x, z) + 0.2, z);
log.rotation.set(0, rand() * Math.PI, Math.PI / 2);
log.castShadow = true;
log.receiveShadow = true;
scene.add(log);
}
// Fire pit logs (crossed stack)
const fireLogs = new THREE.Group();
for (let k = 0; k < 4; k++) {
const log = new THREE.Mesh(logGeo, logMat);
log.scale.set(0.8, 0.8, 0.8);
log.position.set(0, k < 2 ? 0.16 : 0.34, 0);
log.rotation.set(0, k * Math.PI / 4 + 0.25, Math.PI / 2 + (k % 2 ? 0.08 : -0.08));
log.castShadow = true;
fireLogs.add(log);
}
scene.add(fireLogs);
// ---------- Campfire flames ----------
const flameDefs = [
{ r: 0.42, h: 1.5, c: 0xff4a10, o: 0.55 },
{ r: 0.32, h: 1.25, c: 0xff8a1e, o: 0.65 },
{ r: 0.22, h: 1.0, c: 0xffc23a, o: 0.75 },
{ r: 0.12, h: 0.7, c: 0xfff1a0, o: 0.9 }
];
const flames = [];
const flameGroup = new THREE.Group();
flameGroup.position.set(0, 0.3, 0);
scene.add(flameGroup);
for (let c = 0; c < 3; c++) {
const ang = (c / 3) * Math.PI * 2 + 0.5;
const ox = Math.cos(ang) * 0.12, oz = Math.sin(ang) * 0.12;
for (const def of flameDefs) {
const geo = new THREE.ConeGeometry(def.r, def.h, 5, 1, true);
geo.translate(0, def.h / 2, 0);
const mat = new THREE.MeshBasicMaterial({
color: def.c,
transparent: true,
opacity: def.o,
blending: THREE.AdditiveBlending,
depthWrite: false,
side: THREE.DoubleSide,
toneMapped: false
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(ox, 0, oz);
mesh.rotation.y = rand() * Math.PI;
flameGroup.add(mesh);
flames.push({
mesh, ox, oz,
sp: 5 + rand() * 4,
ph: rand() * Math.PI * 2
});
}
}
// Soft glow sprite around the fire
function makeGlowTexture() {
const cv = document.createElement('canvas');
cv.width = cv.height = 128;
const ctx = cv.getContext('2d');
const grad = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
grad.addColorStop(0, 'rgba(255,170,80,1)');
grad.addColorStop(0.35, 'rgba(255,120,40,0.45)');
grad.addColorStop(1, 'rgba(255,80,20,0)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 128, 128);
const tex = new THREE.CanvasTexture(cv);
tex.colorSpace = THREE.SRGBColorSpace;
return tex;
}
const glowMat = new THREE.SpriteMaterial({
map: makeGlowTexture(),
color: 0xffffff,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
opacity: 0.8,
toneMapped: false
});
const glow = new THREE.Sprite(glowMat);
glow.position.set(0, 1.0, 0);
glow.scale.set(5, 5, 1);
scene.add(glow);
// ---------- Sparks (particle system) ----------
const SPARK_N = 160;
const sPos = new Float32Array(SPARK_N * 3);
const sCol = new Float32Array(SPARK_N * 3);
const sVel = new Float32Array(SPARK_N * 3);
const sLife = new Float32Array(SPARK_N);
const sMax = new Float32Array(SPARK_N);
function spawnSpark(i) {
sPos[i * 3] = (rand() - 0.5) * 0.5;
sPos[i * 3 + 1] = 0.7 + rand() * 0.3;
sPos[i * 3 + 2] = (rand() - 0.5) * 0.5;
sVel[i * 3] = (rand() - 0.5) * 0.6;
sVel[i * 3 + 1] = 1.2 + rand() * 2.0;
sVel[i * 3 + 2] = (rand() - 0.5) * 0.6;
sMax[i] = 1.8 + rand() * 2.2;
sLife[i] = sMax[i];
}
for (let i = 0; i < SPARK_N; i++) {
spawnSpark(i);
sLife[i] = rand() * sMax[i]; // stagger so they don't all start together
sPos[i * 3 + 1] += sVel[i * 3 + 1] * (sMax[i] - sLife[i]) * 0.5;
}
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3).setUsage(THREE.DynamicDrawUsage));
sparkGeo.setAttribute('color', new THREE.BufferAttribute(sCol, 3).setUsage(THREE.DynamicDrawUsage));
const sparkMat = new THREE.PointsMaterial({
size: 0.1,
vertexColors: true,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
sizeAttenuation: true,
toneMapped: false
});
const sparks = new THREE.Points(sparkGeo, sparkMat);
sparks.frustumCulled = false;
scene.add(sparks);
function updateSparks(dt, t) {
const wind = Math.sin(t * 0.4) * 0.3 + 0.15;
for (let i = 0; i < SPARK_N; i++) {
sLife[i] -= dt;
if (sLife[i] <= 0) {
spawnSpark(i);
} else {
const ix = i * 3, iy = ix + 1, iz = ix + 2;
sVel[ix] += wind * dt;
sVel[iz] += Math.sin(t * 2 + i) * 0.2 * dt;
sVel[iy] *= (1 - 0.25 * dt);
sPos[ix] += sVel[ix] * dt;
sPos[iy] += sVel[iy] * dt;
sPos[iz] += sVel[iz] * dt;
}
const f = Math.max(0, sLife[i] / sMax[i]);
// Hot yellow near birth, fading to deep orange/red
sCol[i * 3] = 1.0 * f;
sCol[i * 3 + 1] = (0.35 + 0.55 * f) * f;
sCol[i * 3 + 2] = 0.1 * f * f;
}
sparkGeo.attributes.position.needsUpdate = true;
sparkGeo.attributes.color.needsUpdate = true;
}
// ---------- Sky: stars and moon ----------
const starCount = 1600;
const starPos = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const y = 0.03 + rand() * 0.95;
const r = Math.sqrt(1 - y * y);
const th = rand() * Math.PI * 2;
const R = 380;
starPos[i * 3] = Math.cos(th) * r * R;
starPos[i * 3 + 1] = y * R;
starPos[i * 3 + 2] = Math.sin(th) * r * R;
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const starMat = new THREE.PointsMaterial({
color: 0xffffff,
size: 1.6,
sizeAttenuation: false,
fog: false,
transparent: true,
opacity: 0.9
});
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
const moonPos = moonDir.clone().normalize().multiplyScalar(360);
const moon = new THREE.Mesh(
new THREE.SphereGeometry(12, 24, 16),
new THREE.MeshBasicMaterial({ color: 0xdfe6f2, fog: false })
);
moon.position.copy(moonPos);
scene.add(moon);
const moonHalo = new THREE.Mesh(
new THREE.SphereGeometry(22, 24, 16),
new THREE.MeshBasicMaterial({
color: 0x7f93c4, transparent: true, opacity: 0.12,
depthWrite: false, blending: THREE.AdditiveBlending, fog: false
})
);
moonHalo.position.copy(moonPos);
scene.add(moonHalo);
// Subtle moon craters (flat dark patches)
for (let i = 0; i < 5; i++) {
const crater = new THREE.Mesh(
new THREE.CircleGeometry(1.5 + rand() * 2, 7),
new THREE.MeshBasicMaterial({ color: 0xb4bfd2, fog: false })
);
const dir = new THREE.Vector3(rand() - 0.5, rand() - 0.5, rand() - 0.5).normalize();
crater.position.copy(moonPos).addScaledVector(dir, 11.9);
crater.lookAt(0, 0, 0);
crater.lookAt(moonPos.clone().multiplyScalar(2).sub(crater.position));
scene.add(crater);
}
// ---------- Resize ----------
function onResize() {
const w = window.innerWidth, h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
window.addEventListener('resize', onResize);
// ---------- Animation loop ----------
const clock = new THREE.Clock();
let time = 0;
function animate() {
const dt = Math.min(clock.getDelta(), 0.05);
time += dt;
const t = time;
// Flame animation
for (const f of flames) {
const s = 1 + 0.12 * Math.sin(t * f.sp + f.ph) + 0.06 * Math.sin(t * f.sp * 2.7 + f.ph * 1.3);
f.mesh.scale.set(
1 + 0.08 * Math.sin(t * f.sp * 1.3 + f.ph),
s,
1 + 0.08 * Math.cos(t * f.sp * 1.1 + f.ph)
);
f.mesh.position.x = f.ox + 0.04 * Math.sin(t * f.sp * 0.6 + f.ph);
f.mesh.position.z = f.oz + 0.04 * Math.cos(t * f.sp * 0.5 + f.ph);
}
// Light flicker (irregular, multi-frequency)
const flick = 0.88
+ 0.12 * Math.sin(t * 9.1)
+ 0.06 * Math.sin(t * 17.3 + 1.0)
+ (Math.random() - 0.5) * 0.06;
fireLight.intensity = 42 * flick;
// Glow pulse
glow.scale.setScalar(4.6 + 0.4 * Math.sin(t * 7.0) + 0.2 * Math.sin(t * 13.0));
glowMat.opacity = 0.7 + 0.15 * Math.sin(t * 9.0) * flick;
// Sparks
updateSparks(dt, t);
// Slow twinkle on the moon halo
moonHalo.material.opacity = 0.1 + 0.02 * Math.sin(t * 0.8);
controls.update(dt);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
</script>
</body>
</html>Reasoning trace (8,114 tokens)
I'm setting up a single HTML file that loads Three.js 0.170.0 through an import map, and I want it to run without console errors. The plan is a renderer with shadows and tone mapping that fills the window, a dark blue fog and background, and a ground plane with jittered vertices and flat shading in dark green. Still writing the scene's geometry, the rocks get randomly displaced icosahedrons, the logs are rotated cylinders around the fire, and the trees are trunk cylinders topped with stacked low-poly cones. I'm working through the sky and lighting details now: the sparks as a small set of recycled additive particles, stars on a large sphere that ignore fog, and a moon with a flat light-gray material. I'm also setting the camera position, orbit limits so the view stays above ground, and a resize handler. I'm settling the HUD overlay and the hint text, then I'm turning on slow auto-rotation that stops once the user starts dragging. Next I'm weighing a seeded random generator so the layout stays the same every load, and I'm sketching how the flames will pulse in scale with some noise each frame. Still working through the flame geometry, where the cone gets shifted so its base sits at the origin. For lighting, I'm planning shadows on the ground and trees, with the flames and logs handled separately, and a warm point light plus a dim blue hemisphere fill. I'm lining up the directional moon light at low intensity without shadows, since the point light already handles the shadowed look and I don't want the extra cost. For the point light's shadow map, I'm settling on a 1024 size with a small negative bias to cut acne, keeping the default PCF filtering. Then the ground: a 200-unit plane with 60 segments, rotated flat and jittered with hash noise, with the displacement kept smaller near the fire so the clearing stays level. I'm sizing the ground with a dark mossy green and adding a dirt clearing around the fire. For the trees, I'm planning a trunk with two stacked low-poly cones, varied in scale and rotation, and I'm placing about 35 of them at random radii while keeping clear of the center. Still placing the rocks and logs around the fire: rocks at a radius of roughly 3.5 to 7, and logs laid on the ground pointing toward the center. Still placing the logs and the sparks: the four crossed logs sit at the center on the ground, and the sparks are a 150-particle pool that respawns from the fire base each time its life runs out. Still writing the sparks: each one fades as its life runs down, so I'm scaling its color by the remaining life fraction. Then the stars go on a 380-radius upper hemisphere with fog off, and the camera's far plane has to reach past that, so I'm setting it to 1000. The moon is a small pale sphere placed up and back in the sky, and I'm starting on its halo. I'm settling the moon's halo as a larger, faint translucent sphere behind it. Then I'm setting the orbit controls to target the fire, cap the polar angle so the view stays above ground, and limit how far the camera can zoom. The fog and background go to a dark navy, and the flames will use bright basic materials that skip tone mapping so they stay luminous. Still writing the flame cluster and its glow, so I'm going with a small radial gradient sprite that flickers in opacity and sits behind the fire. Still writing the ground, I'm building a 220-unit plane with 70 segments and rotating it flat. Each vertex gets a height from a sine pattern plus some random jitter, scaled up with distance from the fire so the clearing stays level. I'm making the ground height a function of position so the trees and rocks can sit on the same surface. Instead of random jitter, I'll use sine and cosine patterns, so every placement is repeatable. The height only starts rising about 8 units from the fire and stays fairly low, so the clearing stays level. Still working out the tree placement, using random angles and a square-root radius spread so they cluster evenly. I'm also keeping a minimum spacing between trunks and checking each one stays clear of the fire. Still writing the scene, I'm laying out the trees and flame groups, using shared geometries and materials so the scene doesn't get heavy. The trunks are brown and the foliage picks from three greens. Then I'm starting the fire group with the log geometry. Still writing the fire, I'm placing the logs as crossed pairs at the center and then building the flame cones around a base at the origin. Now I'm setting each flame cone so its base sits at the origin, then animating its scale and sway with a couple of sine waves per flame. Still writing the flames, I'm giving them an orange outer layer, a yellow middle, and a near-white core, all additive, unaffected by tone mapping, and double-sided so they glow without depth artifacts. Then I'm adding a soft radial-gradient sprite at the fire's center to bloom the light around it. Still writing the fire, I'm placing the point light at the flame center with shadow settings tuned to cut acne. Flames won't cast shadows, but the light's shadow still falls on the ground. For the stars, I'm picking points on an upper hemisphere and nudging their height so none sit too close to the horizon. Still writing the sparks: I'm sizing the particle pool at 160 entries and setting up its position, velocity, and life arrays. Still writing the sparks: each particle gets a random start near the fire base, an upward velocity, and a random lifetime, so they stagger out of the pool. Still writing the sparks, I'm finishing the stagger by randomizing each spark's starting life. The update step drops each lifetime, respawns it from the fire when it runs out, and otherwise moves it along with a slight drift and slowing. Its color fades with the remaining life, and the points geometry gets dynamic position and color buffers, all anchored at the fire's center. Next I'm setting up the sparks' glow with additive blending and a 0.09 point size, then adding the HUD overlay with its title and control hint, styled to ignore pointer input. Last, I'm configuring the orbit controls to target the fire with damping, a close and far zoom limit, and a polar angle cap. Still tuning the camera's polar limit so it stays above ground. Auto-rotate turns on at a slow speed and stops once the user starts dragging. The starting camera position is set high and back, looking toward the fire. Now I'm finishing the scene's animation loop and making the trees cast shadows, and I'm checking that the shadow setup won't be too heavy. Still writing the fire's log ring, I'm placing five seat logs at radius 2.8 and turning each one to lie tangent to the circle. Still working out the orientation of the log seats so each one lies tangent to the ring. I'm checking the rotation math for a cylinder that starts along Y and needs its axis turned to the tangent direction. Still writing the rocks, I'm jittering each icosahedron's vertices for an irregular shape and squashing them a bit on Y with flat shading. Next are the ring stones around the fire at radius 1.3. Still writing the rocks and ring stones, I'm placing nine of them at radius 1.3 with sizes in a small range, then adding the flame cones around the base. Still writing the flames, I'm setting up twelve cone meshes in three copies around each layer, offset slightly from the center. Each one gets a random rotation and its base position stored so its scale can animate with sine waves. Still writing the flames, I'm jittering their positions with a small sine offset and animating their scale with sine waves. The point light flickers around 42 with a few sine terms and a bit of random noise, and the glow sprite pulses in size and opacity.