GLM 5.3 Flash
by Z.aiReasoning1 console error
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 60 s
- Cost
- $0.0059
- Output tokens
- 11,596
- Spent thinking
- 7,144 tokens
- Speed
- 97.1 tok/s
- Console errors
- 1
- First token after
- 479 ms
- First visible text after
- 1 min 34 s
- Prompt tokens
- 559
- Code size
- 12.8 KB
- Finish reason
- stop
- Served by
- Cohere
- Model id
z-ai/glm-5.3-flash- Reasoning sent
{"effort":"max"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 2026
Console errors
Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.
Failed to load resource: net::ERR_NAME_NOT_RESOLVED
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 · 380 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Night Campfire — Low Poly Forest</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #070d1a; }
canvas { display: block; }
#hint {
position: fixed;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
color: rgba(255, 214, 170, 0.75);
font-family: system-ui, sans-serif;
font-size: 13px;
letter-spacing: 0.08em;
pointer-events: none;
text-shadow: 0 1px 4px rgba(0,0,0,0.8);
white-space: nowrap;
}
</style>
</head>
<body>
<div id="hint">Drag to orbit · Scroll to zoom</div>
<script type="importmap">
{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
/* ---------- Renderer / Scene / Camera ---------- */
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x070d1a);
scene.fog = new THREE.Fog(0x0d1526, 22, 90);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 600);
camera.position.set(12, 6.5, 15);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.8, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.minDistance = 4;
controls.maxDistance = 42;
controls.maxPolarAngle = Math.PI / 2 - 0.1; // keep camera above ground
controls.minPolarAngle = 0.15;
controls.enablePan = false;
controls.update();
/* ---------- Lights ---------- */
scene.add(new THREE.AmbientLight(0x33415e, 0.55));
const moonLight = new THREE.DirectionalLight(0x93a7d8, 0.35);
moonLight.position.set(-40, 45, -70);
scene.add(moonLight);
const fireLight = new THREE.PointLight(0xff9040, 70, 45, 2);
fireLight.position.set(0, 1.7, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.3;
fireLight.shadow.camera.far = 45;
fireLight.shadow.bias = -0.004;
scene.add(fireLight);
/* ---------- Terrain ---------- */
function groundH(x, z) {
const d = Math.hypot(x, z);
const f = THREE.MathUtils.smoothstep(d, 5, 13);
return f * (Math.sin(x * 0.3) * Math.cos(z * 0.24) * 0.7 + Math.sin(x * 0.11 + z * 0.17) * 0.5);
}
const groundGeo = new THREE.PlaneGeometry(150, 150, 50, 50);
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);
p.setY(i, groundH(x, z));
}
groundGeo.computeVertexNormals();
}
const ground = new THREE.Mesh(
groundGeo,
new THREE.MeshStandardMaterial({ color: 0x2a3b26, flatShading: true, roughness: 1 })
);
ground.receiveShadow = true;
scene.add(ground);
/* ---------- Trees ---------- */
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x4a3320, flatShading: true, roughness: 1 });
const treePositions = [];
function makeTree() {
const tree = new THREE.Group();
const trunkH = 1.2 + Math.random() * 1.6;
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.3, trunkH, 6), trunkMat);
trunk.position.y = trunkH / 2;
trunk.castShadow = true;
tree.add(trunk);
const foliageMat = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(0.33 + (Math.random() - 0.5) * 0.04, 0.45, 0.16 + Math.random() * 0.06),
flatShading: true,
roughness: 1
});
let y = trunkH * 0.55;
let r = 1.4 + Math.random() * 0.9;
const layers = 3;
for (let i = 0; i < layers; i++) {
const h = 1.7 + Math.random() * 0.8 - i * 0.25;
const cone = new THREE.Mesh(new THREE.ConeGeometry(r, h, 6), foliageMat);
cone.position.y = y + h * 0.4;
cone.rotation.y = Math.random() * Math.PI;
cone.castShadow = true;
tree.add(cone);
y += h * 0.45;
r *= 0.68;
}
return tree;
}
let placed = 0, attempts = 0;
while (placed < 42 && attempts < 600) {
attempts++;
const a = Math.random() * Math.PI * 2;
const rad = 10 + 50 * Math.sqrt(Math.random());
const x = Math.cos(a) * rad, z = Math.sin(a) * rad;
let ok = true;
for (const p of treePositions) {
if ((p.x - x) ** 2 + (p.z - z) ** 2 < 16) { ok = false; break; }
}
if (!ok) continue;
const tree = makeTree();
const s = 0.8 + Math.random() * 0.9;
tree.scale.setScalar(s);
tree.position.set(x, groundH(x, z) - 0.15, z);
tree.rotation.y = Math.random() * Math.PI * 2;
scene.add(tree);
treePositions.push({ x, z });
placed++;
}
/* ---------- Rocks ---------- */
const rockMat = new THREE.MeshStandardMaterial({ color: 0x565d66, flatShading: true, roughness: 1 });
const rockGeo = new THREE.IcosahedronGeometry(1, 0);
function addRock(x, z, size, sy) {
const m = new THREE.Mesh(rockGeo, rockMat);
m.scale.set(size, size * (sy || 0.6), size * (0.7 + Math.random() * 0.4));
m.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
m.position.set(x, groundH(x, z) + size * 0.25, z);
m.castShadow = true;
m.receiveShadow = true;
scene.add(m);
}
for (let i = 0; i < 10; i++) {
const a = Math.random() * Math.PI * 2;
const rad = 7 + Math.random() * 24;
addRock(Math.cos(a) * rad, Math.sin(a) * rad, 0.7 + Math.random() * 1.4, 0.55 + Math.random() * 0.3);
}
// stone ring around the fire
for (let i = 0; i < 10; i++) {
const a = (i / 10) * Math.PI * 2 + Math.random() * 0.2;
addRock(Math.cos(a) * 1.6, Math.sin(a) * 1.6, 0.26 + Math.random() * 0.12, 0.6);
}
/* ---------- Logs ---------- */
const logMat = new THREE.MeshStandardMaterial({ color: 0x5a3d24, flatShading: true, roughness: 1 });
// fallen logs scattered around the clearing
for (let i = 0; i < 4; i++) {
const a = Math.random() * Math.PI * 2;
const rad = 5 + Math.random() * 8;
const x = Math.cos(a) * rad, z = Math.sin(a) * rad;
const log = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.36, 2.6 + Math.random() * 1.4, 7), logMat);
const dir = new THREE.Vector3(Math.random() - 0.5, 0.05, Math.random() - 0.5).normalize();
log.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
log.position.set(x, groundH(x, z) + 0.32, z);
log.castShadow = true;
log.receiveShadow = true;
scene.add(log);
}
// teepee of logs at the fire
{
const logGeo = new THREE.CylinderGeometry(0.14, 0.18, 2.4, 6);
logGeo.translate(0, 1.2, 0); // base at origin
for (let i = 0; i < 5; i++) {
const angle = (i / 5) * Math.PI * 2 + 0.3;
const base = new THREE.Vector3(Math.cos(angle) * 0.55, 0.05, Math.sin(angle) * 0.55);
const top = new THREE.Vector3(Math.cos(angle + 0.6) * 0.12, 1.9, Math.sin(angle + 0.6) * 0.12);
const mesh = new THREE.Mesh(logGeo, logMat);
mesh.position.copy(base);
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), top.sub(base).normalize());
mesh.castShadow = true;
scene.add(mesh);
}
}
/* ---------- Embers glow ---------- */
const emberMat = new THREE.MeshBasicMaterial({
color: 0xff5a1a, transparent: true, opacity: 0.5,
blending: THREE.AdditiveBlending, depthWrite: false
});
const ember = new THREE.Mesh(new THREE.CircleGeometry(0.6, 8), emberMat);
ember.rotation.x = -Math.PI / 2;
ember.position.y = 0.1;
scene.add(ember);
/* ---------- Flames ---------- */
const flames = [];
const flameGroup = new THREE.Group();
flameGroup.position.y = 0.35;
scene.add(flameGroup);
function addFlame(radius, height, color, opacity) {
const geo = new THREE.ConeGeometry(radius, height, 5);
geo.translate(0, height / 2, 0);
const mat = new THREE.MeshBasicMaterial({
color, transparent: true, opacity,
blending: THREE.AdditiveBlending, depthWrite: false, fog: false
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set((Math.random() - 0.5) * 0.5, 0, (Math.random() - 0.5) * 0.5);
flameGroup.add(mesh);
flames.push({
mesh, baseH: height, baseR: radius,
phase: Math.random() * Math.PI * 2,
speed: 4 + Math.random() * 4,
baseY: mesh.position.y
});
}
for (let i = 0; i < 6; i++) {
addFlame(0.22 + Math.random() * 0.32, 1.1 + Math.random() * 1.0,
new THREE.Color().setHSL(0.06 + Math.random() * 0.05, 1, 0.5 + Math.random() * 0.1), 0.8);
}
addFlame(0.28, 1.0, 0xfff3b0, 0.95); // hot core
/* ---------- Sparks (particles) ---------- */
const SPARKS = 140;
const sPos = new Float32Array(SPARKS * 3);
const sVel = new Float32Array(SPARKS * 3);
const sLife = new Float32Array(SPARKS);
const sMax = new Float32Array(SPARKS);
const sCol = new Float32Array(SPARKS * 3);
function respawnSpark(i) {
const a = Math.random() * Math.PI * 2, r = Math.random() * 0.25;
sPos[i * 3] = Math.cos(a) * r;
sPos[i * 3 + 1] = 0.8 + Math.random() * 0.6;
sPos[i * 3 + 2] = Math.sin(a) * r;
sVel[i * 3] = (Math.random() - 0.5) * 1.2;
sVel[i * 3 + 1] = 1.6 + Math.random() * 2.4;
sVel[i * 3 + 2] = (Math.random() - 0.5) * 1.2;
sMax[i] = 0.9 + Math.random() * 1.1;
sLife[i] = Math.random() * sMax[i];
}
for (let i = 0; i < SPARKS; i++) respawnSpark(i);
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3));
sparkGeo.setAttribute('color', new THREE.BufferAttribute(sCol, 3));
const sparks = new THREE.Points(sparkGeo, new THREE.PointsMaterial({
size: 0.16, vertexColors: true, transparent: true, opacity: 0.95,
blending: THREE.AdditiveBlending, depthWrite: false
}));
scene.add(sparks);
/* ---------- Stars & Moon ---------- */
function makeStars(count, size, color, opacity) {
const arr = new Float32Array(count * 3);
const v = new THREE.Vector3();
for (let i = 0; i < count; i++) {
v.randomDirection();
if (v.y < 0.08) v.y = 0.08 + Math.abs(v.y);
v.normalize().multiplyScalar(190 + Math.random() * 60);
arr[i * 3] = v.x; arr[i * 3 + 1] = v.y; arr[i * 3 + 2] = v.z;
}
const g = new THREE.BufferGeometry();
g.setAttribute('position', new THREE.BufferAttribute(arr, 3));
return new THREE.Points(g, new THREE.PointsMaterial({
color, size, sizeAttenuation: true, fog: false, transparent: true, opacity
}));
}
const starsDim = makeStars(350, 0.7, 0xffffff, 0.8);
const starsBright = makeStars(90, 1.5, 0xdde8ff, 1.0);
scene.add(starsDim, starsBright);
const moonPos = new THREE.Vector3(-80, 75, -140);
const moon = new THREE.Mesh(
new THREE.IcosahedronGeometry(6, 1),
new THREE.MeshBasicMaterial({ color: 0xe8f0ff, fog: false })
);
moon.position.copy(moonPos);
scene.add(moon);
const moonGlow = new THREE.Mesh(
new THREE.IcosahedronGeometry(11, 1),
new THREE.MeshBasicMaterial({
color: 0xcfe0ff, transparent: true, opacity: 0.13,
blending: THREE.AdditiveBlending, depthWrite: false, fog: false
})
);
moonGlow.position.copy(moonPos);
scene.add(moonGlow);
/* ---------- Animation ---------- */
const clock = new THREE.Clock();
let t = 0;
renderer.setAnimationLoop(() => {
const dt = Math.min(clock.getDelta(), 0.05);
t += dt;
// flickering fire light
fireLight.intensity = 62
+ Math.sin(t * 9) * 8
+ Math.sin(t * 23 + 1.7) * 6
+ Math.sin(t * 5.3) * 5;
fireLight.position.x = Math.sin(t * 13) * 0.09;
fireLight.position.z = Math.cos(t * 11) * 0.09;
// flames
for (const f of flames) {
const s = 1 + 0.25 * Math.sin(t * f.speed + f.phase) + 0.15 * Math.sin(t * f.speed * 2.3 + f.phase * 1.7);
f.mesh.scale.set(1.2 - 0.22 * s, s, 1.2 - 0.22 * s);
f.mesh.rotation.y += dt * (0.6 + f.speed * 0.08);
f.mesh.position.y = f.baseY + 0.06 * Math.sin(t * f.speed * 1.7 + f.phase);
}
// ember pulse
emberMat.opacity = 0.35 + 0.25 * (Math.sin(t * 7) * 0.5 + 0.5);
// sparks
for (let i = 0; i < SPARKS; i++) {
sLife[i] -= dt;
if (sLife[i] <= 0) { respawnSpark(i); continue; }
sVel[i * 3 + 1] -= dt * 1.6;
sVel[i * 3] += (Math.random() - 0.5) * dt * 3;
sVel[i * 3 + 2] += (Math.random() - 0.5) * dt * 3;
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;
const l = sLife[i] / sMax[i];
sCol[i * 3] = 1.0 * Math.pow(l, 0.6);
sCol[i * 3 + 1] = (0.15 + 0.6 * l) * Math.pow(l, 0.6);
sCol[i * 3 + 2] = (0.05 + 0.25 * l * l) * Math.pow(l, 0.6);
}
sparkGeo.attributes.position.needsUpdate = true;
sparkGeo.attributes.color.needsUpdate = true;
// star twinkle
starsBright.material.opacity = 0.75 + 0.25 * Math.sin(t * 2.2);
controls.update();
renderer.render(scene, camera);
});
/* ---------- Resize ---------- */
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Reasoning trace (7,144 tokens)
Let me build a single HTML file with Three.js 0.170 via the given import map. Requirements:
- Ground plane, low-poly rocks and logs around campfire.
- ≥30 low-poly trees randomly placed, not on fire.
- Campfire with animated flames, warm point light flickering with shadows.
- Sparks particle system rising.
- Night sky with stars and moon, fog fading distant trees.
- OrbitControls with maxPolarAngle to keep camera above ground.
- Renderer fills window, resize handler.
I'll write a module script. No errors. Let me write carefully.
Key parts:
1. Scene, fog (new THREE.FogExp2 or Fog), renderer with shadowMap enabled.
2. Camera positioned, OrbitControls with maxPolarAngle ~ Math.PI/2 - 0.05, minDistance/maxDistance.
3. Ground: large circle/plane with slight vertex noise for low-poly feel, flatShading material, dark green/brown color.
4. Trees: cone geometry stacked (2-3 cones) + cylinder trunk, low poly segments (5-6). Random positions with min distance from center (e.g., > 8) and from each other. 40 trees.
5. Rocks: IcosahedronGeometry with random detail 0, flattened, gray.
6. Logs: cylinders rotated, brown, arranged around fire like a teepee or crossed.
7. Fire: cone meshes with animated scale/rotation, colors orange/yellow, additive blending maybe; use custom shader or simple animated cones with noise. Simple approach: several cones with MeshBasicMaterial, animate scale and position with sine functions; also could use vertex shader via onBeforeCompile — keep it simple but nice. I'll do a small shader material for flames: a cone with vertex displacement using time. Actually simpler: 5-8 cone meshes each with random flicker phase, MeshBasicMaterial with color from yellow to red, transparent additive. Animate y scale, x/z scale jitter, rotation.y.
Also a point light with flicker intensity, castShadow true, shadow map 1024.
Sparks: THREE.Points with BufferGeometry, positions updated, velocity upward with random drift, reset when above lifetime. Additive material, orange, small size, sizeAttenuation.
Stars: Points on a large sphere, random positions, white with varying size. Moon: sphere with emissive, plus a light? A dim directional light for moonlight maybe (cool color, low intensity) casting shadows? Fire already casts shadows; moonlight directional low intensity, no shadow or shadow optional. Keep shadows from point light mainly. Actually a directional moonlight with shadows could look nice but point light shadows are the requirement. I'll add moon directional light low intensity, no shadow to save perf, plus fire point light castShadow.
Sky: dark background color; fog color matching horizon. Use scene.background = dark blue; fog = new THREE.Fog(color, near, far) so distant trees fade. Fog color like #0a1020. Moon as mesh placed far away, plus glow sprite? Simple emissive sphere with a slightly larger transparent sphere for glow.
Campfire stones ring: small rocks circle around fire.
Also embers glow: point light + maybe a glowing "coal" mesh.
Resize: window resize listener updates camera aspect and renderer size.
Flames animation details: create flame group; each flame: ConeGeometry(radius, height, 5), material MeshBasicMaterial({color, transparent:true, opacity, blending: THREE.AdditiveBlending, depthWrite:false}). Animate: scale.y = base + sin(t*speed+phase)*amp; scale.x/z slight jitter; rotation.y += ; position.y small bob. Also opacity flicker.
Sparks: N=120 particles; each has pos, vel; update: vel.y += small upward? Actually sparks rise: vel.y positive ~1-3, gravity pulls down eventually: vel.y -= dt*2 after initial burst? Simple: give upward velocity with random horizontal drift; life decreases; when life<0 reset at fire position with new random vel. Points material size 0.15, color 0xffaa33, additive, transparent, opacity handled globally; could vary via vertex colors and fade by life — use colors attribute updated each frame.
Keep camera auto slow orbit? Not required; OrbitControls enableDamping.
Firewood logs: 4-6 cylinders leaning teepee style around center, plus a couple lying logs around clearing.
Ground low-poly: PlaneGeometry(120,120, 40,40), displace vertices randomly small except near center flatten (radius < 6 -> height 0). computeVertexNormals, flatShading.
Also add subtle ambient light (very low) so scene isn't pitch black.
Colors: fog 0x0b1220? Let's use sky background 0x070b18, fog same color, near 20 far 90. Trees dark green 0x2d4a2a-ish with variation.
Tree placement: random in ring radius 9..55, avoid too close to each other (min dist 3.5). Count 45.
Shadow: pointLight.castShadow = true; shadow.camera near 0.5 far 60; bias -0.002. Renderer shadowMap type PCFSoft.
Moon: position (40, 50, -60) relative, sphere radius 4, MeshBasicMaterial color 0xdfe8ff; glow sphere radius 6 opacity 0.15 additive.
Stars: 600 points on sphere radius ~180, sizes random via attribute? PointsMaterial single size; use size 0.7 with sizeAttenuation true, opacity 0.9. To vary size, could use two Points objects. Fine.
Also ground receiveShadow, trees castShadow (only trunk? cones cast too, but 45 trees × shadows from point light — 6 shadow faces; could be heavy but ok. Actually PointLight shadow renders cube map 6 passes; 45 trees with low-poly cones fine.
Set renderer.setPixelRatio(Math.min(devicePixelRatio,2)).
Fog affects Points? Stars far away would be fogged out. Set star material fog:false. Moon material fog:false too. Fog far ~90, stars at 180 — with fog:false they're visible. But then distant trees at 55 fade nicely.
Sparks material fog true fine.
Frame the shot: camera at (10, 6, 14) looking at (0,2,0). Controls target (0,2,0).
Auto-rotate slowly until user interacts? Not needed. But "something meaningful visible on first paint" — fine, the scene renders immediately. No keyboard needed, so no start message needed (no game). Good.
Also add subtle fire sound? No, skip.
Handle: logs casting shadows near fire.
Let me also animate light intensity: intensity = 2.5 + noise. Use sum of sines + random. Also light position slight jitter.
Add "embers" under logs: small icosahedron flattened with emissive orange material, pulsing.
Also faint blue ambient: new THREE.AmbientLight(0x223355, 0.35) plus hemisphere maybe.
Moon directional light: color 0x8899cc intensity 0.25 from moon direction, no shadows.
Write the code.
Vertex jitter on ground: iterate position attribute; height = noise-ish using pseudo-random from sin combos: h = (Math.sin(x*0.15)+Math.cos(z*0.13)) etc plus random small. Keep center flat: factor = smoothstep(6, 14, dist). Also raise slight rim? Fine.
Trees: make function createTree(scale). Trunk: CylinderGeometry(0.25,0.35,1.5,6). Foliage: 2-3 cones ConeGeometry(r,h,6) stacked, flatShading. Random hue variation via color.offsetHSL.
Rocks: IcosahedronGeometry(1, 0) scaled (1,0.6,0.8), random rotation, gray 0x6a6f76 with flat shading. Some near fire ring small radius 0.3 in a circle of 8 stones radius 1.6. Larger rocks scattered radius 6-14.
Logs: fallen logs: cylinder radius 0.35 length 3, lying, random place 5-12 from center, plus 2 near fire as seats. Fire logs: teepee: 5 cylinders radius 0.18 length 2.2 leaning, positioned around center tilt ~0.5 rad. Compute: for i in 5: angle = i/5*2π; position x = cos*0.5, z = sin*0.5; rotate to lean inward: rotation... Use group: log cylinder along Y; rotate around axis so top converges. Simple: set log.position y=0.8, log.rotation.z = 0.4? Easier: create log, then log.lookAt? Let's do: for each i, log with geometry translated so base at origin (translate geometry y + length/2), then rotate: rotate around Z by tilt then rotate group by angle. Implementation: const g = new THREE.Group(); per log: mesh cylinder (0.15,0.18,2.2,6) translated geometry up by 1.1; mesh.rotation.x = -0.55? Hmm.
Alternative simpler: build each log, set rotation via: mesh.rotation.set(Math.sin(angle)*tilt... Easiest: create mesh, mesh.rotation.y = angle; mesh.rotation.z = tilt (lean). With geometry extending up from origin, rotating around z leans it in x direction after y-rotation... Order of Euler: default XYZ. Setting rotation.y then rotation.z: rotation applies Z first? Three.js Euler order 'XYZ' means rotations applied... The matrix is R = Rz? Actually order XYZ: rotateX then Y then Z applied as R = Rx * Ry * Rz? In three, Euler order 'XYZ' means the rotations are applied in the order X, then Y, then Z, in intrinsic? Simply: set rotation order 'YZX': first Y (spin), then Z (tilt), then X. With rotation order 'YZX', rotation.z tilts the log away from its local... Let me just use quaternion approach: dir = (cos(angle)*sin(tilt)...
Simpler: position base points on circle radius 0.6, top points converge near (0, 1.6, 0) + small offset. Use mesh.lookAt: create cylinder along +Y with translate so center at length/2, then use quaternion setFromUnitVectors(up, dirVector) where dir = normalize(top - base), and position at base. That's clean.
For fallen logs similar: horizontal dir random.
Flames: group at y ~0.4. Create 7 flames: cone radius random 0.25-0.5, height 1-1.8, radialSegments 5 (low poly). Material per flame with color lerp between 0xff5500, 0xff8800, 0xffcc33. Additive, transparent opacity 0.8, depthWrite false. Store phase, speed, baseScale.
Animate flames each frame:
- f.scale.y = baseY*(0.75+0.35*sin(t*speed+phase)+0.1*sin(t*speed*2.7+phase*2))
- f.scale.x = f.scale.z = baseXZ*(0.85+0.2*sin(t*speed*1.3+phase))
- f.rotation.y += dt*0.5
- position.y bob.
Inner bright core flame smaller, hotter color 0xffdd66.
Light flicker: intensity = 30? In three r155+ lights use physical units? PointLight intensity default... With renderer.useLegacyLights removed in newer versions; intensity in candela when physicallyCorrectLights default. In r170, PointLight intensity is in candela and decay 2 default. So a fire light: intensity ~ 60-120 with distance ~40. Let's set pointLight = new THREE.PointLight(0xff8844, 80, 40, 2). Hmm to be safe, test-ish values: with decay 2 and intensity I, illumination at distance d = I/d². At d=5, 80/25 = 3.2 — quite bright. OK use ~70 base flickering 55-85.
Ambient: AmbientLight(0x1a2440, 0.6)? With physical lights ambient intensity works linearly. Use AmbientLight(0x202a44, 0.5) and moon DirectionalLight(0x99aaff, 0.35).
Star material: PointsMaterial({color:0xffffff, size:0.8, sizeAttenuation:true, fog:false, transparent:true, opacity:0.9}). Star sphere radius 170 — within camera far (set far 500).
Sparks: count 150. Data arrays. geometry position attribute dynamic. velocities in Float32Array. life array. update: life -= dt; if life<=0 respawn at fire (rand small radius, y=0.5) vel: (rand*0.6, 1.5+rand*2.2, rand*0.6). Then vel.y -= dt*0.8 (gravity mild); pos += vel*dt; add horizontal wiggle. Fade: material opacity global 0.9; could set size fixed. To fade per particle use color attribute: color = mix orange->red by life. PointsMaterial vertexColors true. Set color each frame.
Set geometry.setDrawRange? All active always since respawn instantly. Fine.
Also add smoke? Not required; skip.
Moon glow: two meshes.
Also maybe subtle vignette? skip.
Let's write the HTML.
Also OrbitControls: enableDamping true, dampingFactor 0.05, maxPolarAngle = Math.PI/2 - 0.08, minDistance 5, maxDistance 45, maxTargetRadius... also limit target? fine. controls.target.set(0,2,0). Also minPolarAngle maybe 0.15. Also pan: screenSpacePanning false and limit? Keep enablePan true but it's fine.
Handle resize: camera.aspect, renderer.setSize.
Animation loop with clock, clamp dt.
Check console errors: careful with module import syntax: import * as THREE from 'three'; import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
Everything inline in one HTML file.
Ground: receiveShadow true; material MeshStandardMaterial({color:0x2a3b26, flatShading:true, roughness:1}). Trees castShadow.
Point light shadow: light.shadow.mapSize.set(1024,1024); light.shadow.camera.near=0.5; far=50; bias -0.005? For point light use bias small negative.
Also enable renderer.shadowMap.enabled = true; type PCFSoftShadowMap.
Log material MeshStandardMaterial brown 0x5a3d24 flatShading.
Fire ring stones: 9 rocks radius 0.25 around radius 1.5.
Ember glow: small sphere or plane at y=0.1 emissive? Use MeshBasicMaterial 0xff6600 circle geometry radius 0.5 at y=0.05, pulsing opacity. Additive.
Write it now. Estimate ~250 lines. Let me be thorough but compact.
Ground vertex displacement: geometry = new THREE.PlaneGeometry(140,140,50,50); rotate -PI/2. Then positions: for each vertex, x,z (after rotation, y is up? After rotateX applied to geometry, position attribute transformed; so attribute x,y,z where y is up). Do displacement before or after rotate: easier rotate first, then read x,z set y.
height = dist-based: let d = sqrt(x²+z²); if d<5 → 0; else factor = min(1,(d-5)/8); h = factor * (sin(x*0.35)*cos(z*0.3)*0.6 + rand? Deterministic pseudo-random: use Math.sin(x*12.9898+z*78.233)*43758.5453 fract. Combine small.
computeVertexNormals; flatShading anyway recompute per face in shader? flatShading uses computed normals? With flatShading true, normals computed via derivatives, fine.
Trees on ground: place at y = terrain height approx — simpler: keep tree base slightly sunk y=-0.2 so they sit regardless. Ground displacement amplitude 0.6 max, so sink 0.3. Or compute ground height function shared: define function groundH(x,z) used both for geometry and object placement. Nice: define noise function, use for both. Do that.
function groundH(x,z){ const d=Math.hypot(x,z); const f=THREE.MathUtils.smoothstep(d,5,13); return f*(Math.sin(x*0.3)*Math.cos(z*0.24)*0.7 + Math.sin(x*0.11+z*0.17)*0.5); }
smoothstep(x, min, max) in MathUtils: (x-min)/(max-min) clamped — signature MathUtils.smoothstep(x, min, max). Yes.
Trees: position.y = groundH(x,z)-0.15.
Also rocks/logs use groundH.
Camera limits: maxPolarAngle so above ground; also since ground slightly uneven, subtract margin: Math.PI/2 - 0.1. Also add controls.maxTargetRadius? not existing. Fine.
Sparks Points: material size 0.18, sizeAttenuation true, vertexColors true, blending Additive, depthWrite false, transparent true, map? none.
Colors per particle: from life fraction l: c = lerp(0xffdd55, 0xff3300, 1-l)? Actually start bright yellow, fade to red & dim. color.multiplyScalar(l) to fade brightness.
Now write the file.
Also flame glow light second smaller light? one is fine.
Moon: also make directional light shadows? Skip (fire shadows suffice, requirement says fire light casts shadows).
Double-check additive flames against fog: MeshBasicMaterial fog default true — flames near center, fog near 20 so unaffected.
Fog: new THREE.Fog(0x0a1120, 25, 95). Sky background 0x0a1120? Slightly different: background darker 0x060a16, fog color between: 0x0b1322 so distant trees fade into haze matching horizon; background above horizon darker. It's fine.
Stars must not be fogged: fog:false. Moon fog:false.
Star twinkle: skip or subtle opacity oscillation.
One more requirement: "renderer fills the window and handles resizing correctly" — done.
Write code now.
Count trees: 42.
Tree creation:
function makeTree(){
const g = new THREE.Group();
const trunkH = 1.2+Math.random()*1.5;
trunk = Mesh(CylinderGeometry(0.18,0.3,trunkH,6), trunkMat); trunk.position.y=trunkH/2; castShadow.
foliage: 3 cones: let y = trunkH*0.7; let r = 1.4+rand*0.8; for(i=0..2){ h=1.6+rand; cone=ConeGeometry(r,h,6); mesh pos y = y + h*0.4? Stack: cone base overlapping. y positions: y0=trunkH*0.6; each cone position y += h*0.35; r *= 0.65; }
}
Material color variation: base 0x1e3d24; mesh material per-tree clone with hue offset. Use one material per tree (shared foliage color varied): mat = foliageMat.clone(); mat.color.offsetHSL((Math.random()-0.5)*0.04, 0.1, (Math.random()-0.5)*0.08). Cloning 42 materials fine.
Placement loop with attempts: pos random radius 10..60 uniform area: r = 10+50*sqrt(rand), angle random. Check min dist 4 from existing. Store positions.
Snow? no.
Fire group: logs teepee 5, small inner kindling.
Also a couple of standing stones... fine.
Final code:
<!DOCTYPE html>
<html>
<head> meta charset, viewport, title, style: body margin 0 overflow hidden; canvas display block. Maybe an overlay hint "Drag to look around" small text bottom. Add subtle UI: title text top-left "Night Campfire" with hint. Keep minimal, non-blocking, pointer-events none.
</head>
<body>
importmap script
module script
</body>
</html>
Now write module script fully.
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x070d1a);
scene.fog = new THREE.Fog(0x0d1526, 22, 90);
const camera = PerspectiveCamera(60, aspect, 0.1, 600); camera.position.set(11, 6, 15);
renderer: antialias true, setSize, pixelRatio min(dpr,2), shadowMap enabled PCFSoft.
Controls: OrbitControls(camera, renderer.domElement); target (0,1.8,0); enableDamping; dampingFactor .06; maxPolarAngle Math.PI/2-0.1; minDistance 4; maxDistance 40; enablePan false maybe (keep target centered; but allow pan? requirement just limits camera above ground — polar limit + min distance ensures that mostly; with pan target could go below ground. Disable pan for safety? I'll keep pan disabled to guarantee above-ground: enablePan=false.)
Lights:
ambient = AmbientLight(0x2a3a55, 0.5)
moonLight = DirectionalLight(0x8fa5d8, 0.4); position (40,60,-50);
fireLight = PointLight(0xff9040, 70, 45, 2); position (0,1.6,0); castShadow; shadow mapSize 1024; near .3 far 45; bias -0.01? use -0.004.
Ground function and mesh.
Rocks scattered: 10 big rocks at radius 7..30.
Stones ring: 10 small.
Fallen logs: 3 around.
Teepee logs.
Ember disc.
Flames.
Sparks.
Stars.
Moon.
Animate loop:
const dt = Math.min(clock.getDelta(), 0.05); t += dt.
Update flames, light flicker: fireLight.intensity = 60 + Math.sin(t*9)*8 + Math.sin(t*23+1.7)*6 + Math.sin(t*5.3)*5; also fireLight.position.x = Math.sin(t*13)*0.08 etc.
Ember opacity pulse.
Sparks update.
controls.update(); renderer.render.
Ensure no console errors: check geometry.translate exists — yes BufferGeometry.translate.
Sparks respawn uses arrays; positions attribute needsUpdate true.
Colors attribute: create Float32Array colors; material vertexColors: true.
Also render sparks with fog true fine.
Moon glow: Mesh sphere radius 5 basic material color 0xcfe0ff transparent opacity .12 additive, fog false; core radius 3.5 color 0xe8f0ff fog:false. Position (-60, 70, -120)? Directional light should roughly match: moonLight.position.set(-60,80,-120)? Directional light position direction toward target (0,0,0). Set moon at (-70, 80, -140), light same normalized *100: light.position.set(-35,40,-70). Fine.
Camera far 600 covers stars at 180? Stars radius 180 < 600 ok. Put stars radius 200, moon distance ~ sqrt(70²+80²+140²) ≈ 172. OK.
Star positions: random on sphere with y > -0.05*radius (above horizon): pick random direction, if y<0.02 re-roll or take abs. Use: theta random, phi such that y positive: y = Math.random()*0.9+0.05 → then radius in xz accordingly. Simpler: v = random unit vector via THREE.Vector3().randomDirection() then if v.y<0.05 v.y = Math.abs(v.y)+0.05; normalize; multiply by (190+rand*40). randomDirection exists in r170. Yes.
Two star layers for size variety: one size 0.9 (300 stars), one size 1.6 (80 brighter stars). Fine.
Star twinkle: vary opacity of bright layer: mat.opacity = 0.7+0.3*sin(t*2)? Subtle, ok skip complexity — do simple sin on bright layer.
Check: OrbitControls import path 'three/addons/controls/OrbitControls.js' with importmap "three/addons/" mapping. Good.
Now UI overlay: small hint bottom-center: "Drag to orbit · Scroll to zoom". pointer-events:none; font from system-ui. Fine (no external font).
Write final answer as single html code block. Careful: only code block, no text.
Let me write out the whole code.
Details for teepee logs:
const teepee = new THREE.Group();
const logGeo = new THREE.CylinderGeometry(0.14, 0.18, 2.4, 6);
logGeo.translate(0, 1.2, 0); // base at origin
for(let i=0;i<5;i++){
const angle = (i/5)*Math.PI*2 + 0.3;
const base = new THREE.Vector3(Math.cos(angle)*0.55, 0.05, Math.sin(angle)*0.55);
const top = new THREE.Vector3(Math.cos(angle+0.6)*0.12, 1.9, Math.sin(angle+0.6)*0.12);
const mesh = new THREE.Mesh(logGeo, logMat);
mesh.position.copy(base);
const dir = top.clone().sub(base).normalize();
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), dir);
mesh.castShadow = true;
teepee.add(mesh);
}
scene.add(teepee);
Fallen logs: cylinder (0.3,0.35,3,7) rotated to lie: quaternion from up to horizontal dir; y = 0.3+groundH.
Rocks: geometry Icosahedron(1,0), per rock scale non-uniform, rotation random, material gray 0x585f68 flat, position y = groundH + small.
Stones ring: 10 at radius 1.5, size 0.28, y ~ groundH(≈0 near center) 0.15.
Fire flame group at y=0.35.
Sparks origin y=1.2.
Ember disc: CircleGeometry(0.55, 8) rotated flat, MeshBasicMaterial({color:0xff5a1a, transparent, opacity .6, blending additive? just emissive-like basic; blending Additive, depthWrite false}), y=0.12. Pulse opacity 0.35+0.3*sin.
Flames list creation:
const flameColors = [0xff3d00, 0xff7a1a, 0xffb020, 0xffd94d];
7 flames: radius 0.22+rand*0.35, height 1.1+rand*1.0. Cone geometry (radius, height, 5). translate geometry y +height/2 so base at 0. mesh.position random small offset within 0.35 radius. material MeshBasicMaterial({color, transparent:true, opacity:0.85, blending:AdditiveBlending, depthWrite:false, side:DoubleSide? not needed}).
Store {mesh, baseH:height, baseR, phase, speed:4+rand*4, wobble}.
Animate:
flame.mesh.rotation.y += dt*(0.6+...);
const s = 1 + 0.25*Math.sin(t*speed+phase) + 0.15*Math.sin(t*speed*2.3+phase*1.7);
mesh.scale.set(1/Math.sqrt(s)?? keep simple: scale.y = s; scale.x=scale.z = 1.15 - 0.15*s? So taller flames thinner. set xz = 1.2 - 0.25*s.
position.y = baseY + 0.05*Math.sin(t*speed*1.7+phase).
Ok. Also one inner core flame: color 0xfff3b0 small.
Now finalize. Also trees castShadow: cones castShadow true, receiveShadow ground. Trunk cast too. That's ~126 cones shadow-rendered per cube face ×6 = heavy-ish but fine at 1024. Could set only point light shadow. OK.
Also fire logs cast shadows.
Sparks count 140.
Write the HTML now, carefully complete.
Also guard: if WebGL fails? skip.
Edge: MathUtils.smoothstep(x, min, max) — verify signature: THREE.MathUtils.smoothstep(x, min, max). Yes.
Code:
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
... all code ...
renderer.domElement appended to body.
resize handler.
animate via renderer.setAnimationLoop(animate).
Full code below. Let me write spark update logic concretely:
const SPARKS = 140;
const sPos = new Float32Array(SPARKS*3);
const sVel = new Float32Array(SPARKS*3);
const sLife = new Float32Array(SPARKS);
const sMax = new Float32Array(SPARKS);
const sCol = new Float32Array(SPARKS*3);
function respawn(i){
const a = Math.random()*Math.PI*2, r = Math.random()*0.25;
sPos[i*3]=Math.cos(a)*r; sPos[i*3+1]=0.8+Math.random()*0.6; sPos[i*3+2]=Math.sin(a)*r;
sVel[i*3]=(Math.random()-0.5)*1.2; sVel[i*3+1]=1.6+Math.random()*2.4; sVel[i*3+2]=(Math.random()-0.5)*1.2;
sMax[i]=0.9+Math.random()*1.1; sLife[i]=sMax[i];
}
for i respawn; also randomize initial life so not synchronized: sLife[i]=Math.random()*sMax[i] and advance positions? just leave; they'll all start at fire, fine within a second. Better: initially scatter by simulating? Just set life random; positions start near fire — acceptable.
Update per frame:
for i: sLife[i]-=dt; if <=0 respawn(i) and continue... after respawn set position immediately.
vel.y -= dt*1.6; vel.x += (Math.random()-0.5)*dt*2; etc wiggle.
pos += vel*dt.
l = sLife/sMax; color: r=1, g=0.35+0.5*l, b=0.05+0.25*l*l? Simple: from (1.0,0.75,0.3) at full life to (0.8,0.15,0.0): c = lerp. Also fade with l^1.5 multiply.
positions/needsUpdate, colors needsUpdate.
Material: new THREE.PointsMaterial({size:0.16, vertexColors:true, transparent:true, opacity:0.95, blending:THREE.AdditiveBlending, depthWrite:false});
Add to scene at fire group position (0,0,0) since absolute coords used.
Stars:
function makeStars(count, size, opacity){
positions; for: dir random; if dir.y<0.08 dir.y=0.08+Math.abs(dir.y); dir.normalize(); r=190+Math.random()*60; pos=dir*r... hmm dir.y min clamp then normalize keeps above horizon.
Points with PointsMaterial({color:0xffffff,size,sizeAttenuation:true,fog:false,transparent:true,opacity}).
}
stars1 = makeStars(350, 0.7, 0.85); stars2 = makeStars(90, 1.4, 1.0) bright bluish-white 0xdde8ff.
Moon at (-80, 75, -140)? distance ~178, radius 6. Add glow radius 11 opacity 0.15.
Directional moonlight position (-40, 45, -70) intensity 0.35 color 0x93a7d8.
Ambient 0x33415e intensity 0.55.
Now check "low-poly" everywhere — yes.
One more: fog fades distant trees — Fog far 90; trees out to 60 → heavily fogged. Good.
Write final HTML. Also meta viewport for mobile. Style: html,body{margin:0;height:100%;overflow:hidden;background:#070d1a} canvas{display:block}.
Hint div: position fixed bottom 14px left 50% translateX(-50%), color rgba(255,220,180,0.75), font 13px system-ui, letter-spacing. Also small title top-left? Keep just hint.
Now produce final answer.