Models 44

GPT-5 Nano

by OpenAIReasoning1 console error
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
31.3 s
Cost
$0.0017
Output tokens
4,256
Spent thinking
0 tokens
Speed
140.1 tok/s
Console errors
1
First token after
911 ms
First visible text after
911 ms
Prompt tokens
555
Code size
14.6 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
{"effort":"minimal"}
Max output
128,000 tokens
Generated
Nov 12, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

geo.computeFlatVertexNormals is not a function

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 · 432 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Low-Poly Campfire in a Forest - Three.js Demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>
    :root {
      --bg: #0b1020;
      --ground: #2a2a2a;
      --rock: #5a5a5a;
      --log: #7a4b2a;
      --fire: #ff7a2e;
    }
    html, body {
      height: 100%;
      margin: 0;
      background: #000;
      font-family: system-ui, -apple-system, "Segoe UI", Roboto;
      color: #ddd;
      overflow: hidden;
    }
    /* Canvas fills window without scrollbars */
    canvas { display: block; width: 100%; height: 100%; }
    /* Overlay instructions and status */
    .overlay {
      position: fixed;
      left: 16px;
      bottom: 16px;
      padding: 10px 14px;
      background: rgba(0,0,0,0.5);
      border-radius: 8px;
      font-size: 13px;
      color: #ddd;
      z-index: 10;
      user-select: none;
    }
    .overlay.hidden { display: none; }
    .shadow {
      text-shadow: 0 2px 6px rgba(0,0,0,0.6);
    }
    @media (max-width: 700px) {
      .overlay { font-size: 12px; left: 8px; right: 8px; bottom: 12px; }
    }
  </style>
</head>
<body>
  <div id="start" class="overlay shadow" aria-label="start instruction">
    Click or press any key to start
  </div>

  <!-- Import Map for Three.js -->
  <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>

  <script type="module">
    // Scene setup using Three.js via import map
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    // Simple bloom-like glow via additive sprites
    // We'll implement a minimal animated flame with shader-like color cycling using a mesh and particles.

    let renderer, scene, camera, controls;
    let ground, campfireGroup;
    let flameMesh;
    let fireGlow;
    let sparks;
    let moon, stars;
    let fog;
    let started = false;

    const CLOCK = new THREE.Clock();

    function createGround() {
      // Ground plane with gentle hills (low-poly feel)
      const size = 120;
      const geo = new THREE.PlaneGeometry(size, size, 32, 32);
      // Simple shading with per-vertex color variations for a low-poly look
      const colors = [];
      for (let i = 0; i < geo.attributes.position.count; i++) {
        const y = (geo.attributes.position.getY(i) || 0);
        // small height variation
        const h = (Math.random() * 0.25) - 0.125;
        // shift vertex height a bit by editing position would be heavy; skip for performance.
        // We'll color based on x,z to give variation
        const x = geo.attributes.position.getX(i);
        const z = geo.attributes.position.getZ(i);
        const c = new THREE.Color(0.25 + Math.abs(x) * 0.003, 0.25 + Math.abs(z) * 0.003, 0.15);
        colors.push(c.r, c.g, c.b);
      }
      geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));

      const mat = new THREE.MeshStandardMaterial({
        vertexColors: true,
        roughness: 0.9,
        metalness: 0.0
      });
      const mesh = new THREE.Mesh(geo, mat);
      mesh.rotation.x = -Math.PI / 2;
      mesh.receiveShadow = true;
      return mesh;
    }

    function makeRock(size = 1) {
      // Low-poly rock as a simple extruded icosahedron-like blob
      const geo = new THREE.DodecahedronGeometry(size, 1);
      // scale to look flat-ish
      geo.computeFlatVertexNormals();
      const mat = new THREE.MeshStandardMaterial({ color: 0x5a5a5a, roughness: 0.9, metalness: 0.0 });
      const m = new THREE.Mesh(geo, mat);
      m.castShadow = true;
      m.receiveShadow = true;
      return m;
    }

    function makeLog(length = 3) {
      const geo = new THREE.CylinderGeometry(0.12, 0.2, length, 6, 1, false);
      const mat = new THREE.MeshStandardMaterial({ color: 0x7a4b2a, roughness: 0.95 });
      const log = new THREE.Mesh(geo, mat);
      log.rotation.z = Math.random() * 0.4 - 0.2;
      log.rotation.y = Math.random() * Math.PI;
      log.castShadow = true;
      log.receiveShadow = true;
      return log;
    }

    function makeTree(baseRadius=0.8, height=6) {
      // Simple low-poly tree: trunk + conical foliage
      const group = new THREE.Group();

      const trunkGeo = new THREE.CylinderGeometry(0.25, 0.28, height*0.6, 6);
      const trunkMat = new THREE.MeshStandardMaterial({ color: 0x7a4b2a, roughness: 0.95 });
      const trunk = new THREE.Mesh(trunkGeo, trunkMat);
      trunk.castShadow = true;
      trunk.receiveShadow = true;
      trunk.position.y = height*0.3;

      // foliage as stacked cones (low-poly)
      const coneMat = new THREE.MeshStandardMaterial({ color: 0x2b6f2b, roughness: 0.95 });
      const levels = 3;
      for (let i=0; i<levels; i++) {
        const r = baseRadius * (1 - i*0.25);
        const h = height * 0.25;
        const cone = new THREE.Mesh(new THREE.ConeGeometry(r, h, 6), coneMat);
        cone.position.y = height*0.35 + i*h;
        cone.rotation.y = Math.PI/6 * i;
        cone.castShadow = true;
        cone.receiveShadow = true;
        group.add(cone);
      }

      trunk.position.y = height*0.3;
      group.add(trunk);
      return group;
    }

    function createNightSky() {
      // Moon
      const moonGeo = new THREE.SphereGeometry(2, 20, 20);
      const moonMat = new THREE.MeshBasicMaterial({ color: 0xddddff, emissive: 0x333355, emissiveIntensity: 0.6 });
      moon = new THREE.Mesh(moonGeo, moonMat);
      moon.position.set(30, 40, -60);
      scene.add(moon);

      // Stars
      const starsGeo = new THREE.BufferGeometry();
      const starCount = 280;
      const positions = new Float32Array(starCount * 3);
      for (let i = 0; i < starCount; i++) {
        positions[i*3+0] = (Math.random() - 0.5) * 120;
        positions[i*3+1] = Math.random() * 60 + 10;
        positions[i*3+2] = (Math.random() - 0.5) * 120;
      }
      starsGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      const starsMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.6, sizeAttenuation: true });
      stars = new THREE.Points(starsGeo, starsMat);
      scene.add(stars);
    }

    function createFogAndLights() {
      // Fog
      fog = new THREE.FogExp2(0x0a1020, 0.005);
      scene.fog = fog;

      // Directional ambient night glow
      const ambient = new THREE.AmbientLight(0x404060, 0.25);
      scene.add(ambient);
    }

    function createCampfire() {
      campfireGroup = new THREE.Group();

      // Fire pit base
      const pitGeom = new THREE.CylinderGeometry(1.6, 1.6, 0.6, 12);
      const pitMat = new THREE.MeshStandardMaterial({ color: 0x2b1e12, roughness: 0.9 });
      const pit = new THREE.Mesh(pitGeom, pitMat);
      pit.position.y = 0.3;
      pit.castShadow = true;
      pit.receiveShadow = true;
      campfireGroup.add(pit);

      // Rocks around pit
      for (let i = 0; i < 8; i++) {
        const rock = makeRock(THREE.MathUtils.randFloat(0.4, 0.8));
        const radius = THREE.MathUtils.randFloat(1.8, 2.8);
        const a = (i / 8) * Math.PI * 2 + Math.random() * 0.4;
        rock.position.set(Math.cos(a) * radius, 0.35, Math.sin(a) * radius);
        rock.rotation.y = Math.random() * Math.PI;
        rock.scale.setScalar(THREE.MathUtils.randFloat(0.8, 1.4));
        campfireGroup.add(rock);
      }

      // Logs around pit
      for (let i=0; i<6; i++) {
        const log = makeLog(THREE.MathUtils.randFloat(2.0, 3.5));
        const a = (i / 6) * Math.PI * 2;
        log.position.set(Math.cos(a)*1.5, 0.2, Math.sin(a)*1.5);
        log.rotation.y = a;
        campfireGroup.add(log);
      }

      // Flames: a small animated cone with gradient and slight flicker
      const flameGeo = new THREE.ConeGeometry(0.8, 2.0, 12, 1, true);
      const flameMat = new THREE.MeshBasicMaterial({ color: 0xffa056, transparent: true, opacity: 0.9, depthWrite: false });
      flameMesh = new THREE.Mesh(flameGeo, flameMat);
      flameMesh.position.y = 1.2;
      flameMesh.rotation.x = Math.PI; // point up
      flameMesh.castShadow = false;
      flameMesh.receiveShadow = false;
      campfireGroup.add(flameMesh);

      // Fire glow (point light)
      const glow = new THREE.PointLight(0xffa056, 1.2, 18, 2);
      glow.position.set(0, 1.6, 0);
      glow.castShadow = true;
      campfireGroup.add(glow);
      fireGlow = glow;
      // dark orange halo sprite-like light:
      const haloGeo = new THREE.SphereGeometry(2.0, 8, 8);
      const haloMat = new THREE.MeshBasicMaterial({ color: 0xffa056, transparent: true, opacity: 0.25, depthWrite: false });
      const halo = new THREE.Mesh(haloGeo, haloMat);
      halo.position.y = 1.6;
      halo.renderOrder = 0;
      campfireGroup.add(halo);

      // Sparks particle system (simple points)
      const sparksGeo = new THREE.BufferGeometry();
      const sparksCount = 120;
      const sPos = new Float32Array(sparksCount * 3);
      for (let i = 0; i < sparksCount; i++) {
        sPos[i*3+0] = 0;
        sPos[i*3+1] = 1.2;
        sPos[i*3+2] = 0;
      }
      sparksGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3));
      const sparksMat = new THREE.PointsMaterial({ color: 0xffd77a, size: 0.25, sizeAttenuation: true, transparent: true, opacity: 0.9 });
      sparks = new THREE.Points(sparksGeo, sparksMat);
      sparks.renderOrder = 2;
      campfireGroup.add(sparks);
    }

    function updateSparks(dt) {
      // Simple upward drift with random radial spread
      const pos = sparks.geometry.attributes.position;
      const count = pos.count;
      for (let i = 0; i < count; i++) {
        const idx = i * 3;
        pos.array[idx+1] += dt * (0.8 + Math.random() * 0.8); // rise
        pos.array[idx+0] += (Math.random() - 0.5) * dt * 0.4;
        pos.array[idx+2] += (Math.random() - 0.5) * dt * 0.4;
        // respawn near fire when too high
        if (pos.array[idx+1] > 4.0) {
          pos.array[idx+0] = 0;
          pos.array[idx+1] = 1.2;
          pos.array[idx+2] = 0;
        }
      }
      pos.needsUpdate = true;
    }

    function animateFlame(dt) {
      // Simple flicker: scale and color slightly
      const t = performance.now() * 0.001;
      flameMesh.scale.setScalar(0.9 + Math.sin(t * 6) * 0.04);
      const base = 0.9 + Math.sin(t * 8) * 0.05;
      flameMesh.material.opacity = base;
      // color shift
      const hue = 0.08 + Math.sin(t * 5) * 0.02;
      flameMesh.material.color.setHSL(0.07, 1.0, 0.6 + hue * 0.2);
      // light intensity flicker
      fireGlow.intensity = 1.2 + Math.abs(Math.sin(t * 6)) * 0.8;
    }

    function init() {
      // Renderer
      const canvas = document.createElement('canvas');
      document.body.appendChild(canvas);
      renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: false });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      renderer.setSize(window.innerWidth, window.innerHeight, false);
      document.body.style.background = '#000';

      // Scene
      scene = new THREE.Scene();

      // Camera
      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(-12, 8, 18);
      camera.lookAt(0, 0, 0);

      // Controls
      controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.08;
      controls.minDistance = 6;
      controls.maxDistance = 60;
      controls.maxPolarAngle = Math.PI * 0.75; // stay above ground
      controls.keys = {};
      // Disable WASD movement in case
      controls.enablePan = false;

      // Lights
      const ambient = new THREE.AmbientLight(0x111133);
      scene.add(ambient);

      // Ground
      ground = createGround();
      ground.position.y = 0;
      scene.add(ground);

      // Fog & sky
      createFogAndLights();
      createNightSky();
      createCampfire();

      // Random trees around clearing
      const treeGroup = new THREE.Group();
      scene.add(treeGroup);
      const treeCount = 30 + Math.floor(Math.random() * 10);
      for (let i = 0; i < treeCount; i++) {
        const t = makeTree(0.9 + Math.random() * 0.6, 5 + Math.random() * 2.5);
        // Position around the clearing, avoid central area
        let rx = (Math.random() - 0.5) * 40;
        let rz = (Math.random() - 0.5) * 40;
        // Ensure not too close to campfire (radius ~6)
        const dist = Math.sqrt(rx*rx + rz*rz);
        if (dist < 6) {
          rx = (Math.random() > 0.5 ? 1 : -1) * (6 + Math.random() * 14);
          rz = (Math.random() > 0.5 ? 1 : -1) * (6 + Math.random() * 14);
        }
        t.position.set(rx, 0, rz);
        t.traverse((child) => {
          if (child instanceof THREE.Mesh) {
            child.castShadow = true;
            child.receiveShadow = true;
          }
        });
        treeGroup.add(t);
      }

      // Ground shadows
      ground.receiveShadow = true;
      campfireGroup.traverse((c) => {
        if (c.isMesh) c.castShadow = true;
      });

      // Start hidden until user interaction
      document.getElementById('start').classList.remove('hidden');
      // Kick initial render
      renderer.render(scene, camera);
    }

    function onResize() {
      const w = window.innerWidth;
      const h = window.innerHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h, false);
    }

    function startIfNeeded() {
      if (started) return;
      started = true;
      document.getElementById('start').style.display = 'none';
      // Start animation loop
      animate();
    }

    // Interaction to start: click or key
    window.addEventListener('mousedown', startIfNeeded, { once: true });
    window.addEventListener('keydown', startIfNeeded, { once: true });
    window.addEventListener('resize', onResize);

    function animate() {
      const dt = CLOCK.getDelta();
      // Update controls
      controls.update();

      // Flames and sparks
      animateFlame(dt);
      updateSparks(dt);

      // Gentle star twinkle via moon position slight orbit
      const t = performance.now() * 0.00008;
      if (moon) {
        moon.position.x = Math.cos(t) * 30;
        moon.position.z = Math.sin(t) * -60;
      }

      // Render
      renderer.render(scene, camera);

      // Schedule next frame
      requestAnimationFrame(animate);
    }

    // Kick off init after module loads
    init();
  </script>

  <!-- The sandbox requires that the demo be self-contained and start in a "ready" state.
       The user must click or press a key to start the action, as implemented above. -->
</body>
</html>