Models 44
Train snakeOne-line

Gemini 3.8 Flash

by GoogleReasoningClean
Open in new window

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

How it went

Total time
1 min 49 s
Cost
$0.067
Output tokens
17,671
Spent thinking
3,319 tokens
Speed
166.3 tok/s
Console errors
0
First token after
2.6 s
First visible text after
34.0 s
Prompt tokens
367
Code size
41.8 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
{"effort":"high"}
Max output
65,536 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
“make me a snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform”
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 · 1470 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>The Little Steam Train - Railway Snake</title>
  <style>
    :root {
      --bg-wood: #2c1d11;
      --brass: #e5b158;
      --brass-dark: #9a6f27;
      --cream: #fbf5e6;
      --rail-green: #2e5339;
      --panel-bg: rgba(28, 18, 12, 0.88);
    }

    * {
      box-sizing: border-box;
      user-select: none;
      -webkit-user-select: none;
      margin: 0;
      padding: 0;
    }

    body {
      background: radial-gradient(circle at center, #3d291a 0%, #19100a 100%);
      color: var(--cream);
      font-family: 'Trebuchet MS', 'Segoe UI', system-ui, sans-serif;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      overflow-x: hidden;
      padding: 10px;
    }

    #game-container {
      position: relative;
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 960px;
      background: var(--panel-bg);
      border: 4px solid var(--brass);
      border-radius: 12px;
      box-shadow: 0 16px 40px rgba(0, 0, 0, 0.8), inset 0 0 15px rgba(0, 0, 0, 0.6);
      padding: 12px;
    }

    /* Header & HUD */
    header {
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 4px 12px 10px 12px;
      border-bottom: 2px solid var(--brass-dark);
      margin-bottom: 8px;
    }

    .title-group {
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .title-group h1 {
      font-size: 1.4rem;
      letter-spacing: 2px;
      color: var(--brass);
      text-transform: uppercase;
      text-shadow: 1px 2px 4px rgba(0,0,0,0.8);
    }

    .station-badge {
      font-size: 0.75rem;
      background: #142a1b;
      color: #9cd4a7;
      padding: 2px 8px;
      border-radius: 10px;
      border: 1px solid #3c704a;
      letter-spacing: 1px;
      display: inline-block;
    }

    .hud-stats {
      display: flex;
      align-items: center;
      gap: 16px;
    }

    .stat-box {
      background: #120b07;
      border: 1px solid var(--brass-dark);
      border-radius: 6px;
      padding: 4px 10px;
      text-align: center;
      min-width: 80px;
    }

    .stat-label {
      font-size: 0.65rem;
      text-transform: uppercase;
      color: #b59f84;
      display: block;
      letter-spacing: 1px;
    }

    .stat-value {
      font-size: 1.2rem;
      font-weight: bold;
      color: var(--brass);
      font-variant-numeric: tabular-nums;
    }

    .btn-sound {
      background: #22140c;
      border: 1px solid var(--brass);
      color: var(--brass);
      padding: 6px 12px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 0.8rem;
      transition: all 0.2s;
    }
    .btn-sound:hover {
      background: var(--brass);
      color: #22140c;
    }

    /* Canvas Area */
    .canvas-wrap {
      position: relative;
      width: 100%;
      aspect-ratio: 22 / 14;
      border-radius: 6px;
      overflow: hidden;
      border: 3px solid #1c120a;
      box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.7);
      background-color: #3b5a32;
    }

    canvas {
      width: 100%;
      height: 100%;
      display: block;
      image-rendering: auto;
    }

    /* Overlays */
    .overlay {
      position: absolute;
      inset: 0;
      background: rgba(14, 8, 4, 0.75);
      backdrop-filter: blur(3px);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 20px;
      z-index: 10;
      transition: opacity 0.3s ease;
    }

    .overlay.hidden {
      opacity: 0;
      pointer-events: none;
    }

    .demo-banner {
      position: absolute;
      top: 14px;
      background: linear-gradient(90deg, transparent, rgba(229, 177, 88, 0.95), transparent);
      color: #201206;
      width: 100%;
      padding: 6px 0;
      font-weight: 900;
      text-transform: uppercase;
      letter-spacing: 2px;
      font-size: 0.9rem;
      box-shadow: 0 4px 12px rgba(0,0,0,0.5);
      animation: pulse 2s infinite ease-in-out;
      pointer-events: none;
      z-index: 5;
    }

    @keyframes pulse {
      0%, 100% { opacity: 0.85; transform: scale(1); }
      50% { opacity: 1; transform: scale(1.02); }
    }

    .overlay-card {
      background: #1f140c;
      border: 2px solid var(--brass);
      border-radius: 12px;
      padding: 24px 32px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.9);
      max-width: 440px;
      animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }

    @keyframes popIn {
      from { transform: scale(0.85); opacity: 0; }
      to { transform: scale(1); opacity: 1; }
    }

    .overlay-card h2 {
      color: var(--brass);
      font-size: 1.8rem;
      margin-bottom: 8px;
      text-transform: uppercase;
      letter-spacing: 2px;
    }

    .overlay-card p {
      color: #dfcdb7;
      font-size: 0.95rem;
      line-height: 1.4;
      margin-bottom: 16px;
    }

    .start-btn {
      background: linear-gradient(180deg, #f3ca7e 0%, #c48c2c 100%);
      color: #241407;
      border: none;
      border-radius: 30px;
      padding: 12px 28px;
      font-size: 1.05rem;
      font-weight: bold;
      cursor: pointer;
      text-transform: uppercase;
      letter-spacing: 1.5px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.4);
      transition: transform 0.1s, filter 0.2s;
    }

    .start-btn:hover {
      filter: brightness(1.1);
      transform: translateY(-2px);
    }
    .start-btn:active {
      transform: translateY(1px);
    }

    /* Mobile Controls */
    .controls-bar {
      margin-top: 10px;
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 8px;
      font-size: 0.8rem;
      color: #a48c74;
    }

    .keyboard-legend {
      display: flex;
      gap: 12px;
    }

    .key-badge {
      background: #1c1109;
      border: 1px solid #4a3424;
      padding: 2px 6px;
      border-radius: 4px;
      color: var(--cream);
      font-family: monospace;
    }

    .touch-dpad {
      display: none;
      grid-template-columns: repeat(3, 48px);
      grid-template-rows: repeat(2, 44px);
      gap: 4px;
      justify-content: center;
      margin-top: 8px;
    }

    .touch-btn {
      background: #2a1b12;
      border: 1px solid var(--brass-dark);
      border-radius: 8px;
      color: var(--brass);
      font-size: 1.2rem;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      box-shadow: 0 3px 6px rgba(0,0,0,0.5);
    }
    .touch-btn:active {
      background: var(--brass-dark);
      color: var(--cream);
      transform: translateY(2px);
    }

    @media (max-width: 680px) {
      .touch-dpad { display: grid; }
      .keyboard-legend { display: none; }
      .title-group h1 { font-size: 1.1rem; }
      .station-badge { display: none; }
      .hud-stats { gap: 8px; }
      .stat-box { min-width: 64px; padding: 2px 6px; }
      .stat-value { font-size: 1rem; }
    }
  </style>
</head>
<body>

  <div id="game-container">
    <header>
      <div class="title-group">
        <h1>Grand Valley Express</h1>
        <span class="station-badge" id="station-name">Station: Sunny Meadow</span>
      </div>

      <div class="hud-stats">
        <div class="stat-box">
          <span class="stat-label">Passengers</span>
          <span class="stat-value" id="score-val">0</span>
        </div>
        <div class="stat-box">
          <span class="stat-label">Best Route</span>
          <span class="stat-value" id="best-val">0</span>
        </div>
        <button class="btn-sound" id="btn-sound" aria-label="Toggle Sound">🔊 Whistle</button>
      </div>
    </header>

    <div class="canvas-wrap" id="canvas-wrap">
      <div class="demo-banner" id="demo-banner">🚂 DEMO AUTOPILOT — CLICK TO TAKE CONTROLS</div>
      <canvas id="railway-canvas" width="924" height="588"></canvas>

      <div class="overlay" id="start-overlay">
        <div class="overlay-card">
          <h2>All Aboard!</h2>
          <p>Guide the miniature locomotive along the railway. Stop at platforms to collect waiting passengers and couple colorful wagons to your train!</p>
          <button class="start-btn" id="start-btn">Take The Controls</button>
        </div>
      </div>

      <div class="overlay hidden" id="gameover-overlay">
        <div class="overlay-card">
          <h2>Derailed!</h2>
          <p>The train collided with the track perimeter or wagons! All passengers made it out safely with their tea intact.</p>
          <p style="font-weight: bold; color: var(--brass);">Final Passenger Count: <span id="final-score">0</span></p>
          <button class="start-btn" id="restart-btn">New Departure</button>
        </div>
      </div>
    </div>

    <div class="controls-bar">
      <div class="keyboard-legend">
        <span>Steer: <span class="key-badge">W A S D</span> / <span class="key-badge">Arrows</span></span>
        <span>Sound: <span class="key-badge">M</span></span>
        <span>Whistle: <span class="key-badge">H</span></span>
      </div>
      <div>Railway Dispatch: Keep wheels on iron!</div>
    </div>

    <!-- Onscreen D-pad for mobile / touch devices -->
    <div class="touch-dpad">
      <div style="grid-column: 2" class="touch-btn" id="btn-up">▲</div>
      <div style="grid-column: 1; grid-row: 2;" class="touch-btn" id="btn-left">◀</div>
      <div style="grid-column: 2; grid-row: 2;" class="touch-btn" id="btn-down">▼</div>
      <div style="grid-column: 3; grid-row: 2;" class="touch-btn" id="btn-right">▶</div>
    </div>
  </div>

  <script>
    /* =========================================================================
       AUDIO SYNTHESIZER (Pure Web Audio API)
       ========================================================================= */
    class TrainAudio {
      constructor() {
        this.ctx = null;
        this.muted = false;
        this.chuffTimer = 0;
      }

      init() {
        if (!this.ctx) {
          const AudioContext = window.AudioContext || window.webkitAudioContext;
          if (AudioContext) {
            this.ctx = new AudioContext();
          }
        }
        if (this.ctx && this.ctx.state === 'suspended') {
          this.ctx.resume();
        }
      }

      playWhistle() {
        if (this.muted || !this.ctx) return;
        this.init();
        const now = this.ctx.currentTime;

        // Classic dual chime harmonic whistle (e.g., ~520Hz and ~650Hz)
        [520, 655].forEach(freq => {
          const osc = this.ctx.createOscillator();
          const gain = this.ctx.createGain();

          osc.type = 'sawtooth';
          osc.frequency.setValueAtTime(freq, now);
          osc.frequency.exponentialRampToValueAtTime(freq * 1.03, now + 0.15);
          osc.frequency.exponentialRampToValueAtTime(freq, now + 0.4);

          // Low pass filter for soft steam resonance
          const filter = this.ctx.createBiquadFilter();
          filter.type = 'lowpass';
          filter.frequency.setValueAtTime(1400, now);

          gain.gain.setValueAtTime(0.001, now);
          gain.gain.linearRampToValueAtTime(0.12, now + 0.08);
          gain.gain.exponentialRampToValueAtTime(0.001, now + 0.45);

          osc.connect(filter);
          filter.connect(gain);
          gain.connect(this.ctx.destination);

          osc.start(now);
          osc.stop(now + 0.46);
        });
      }

      playChuff() {
        if (this.muted || !this.ctx) return;
        this.init();
        const now = this.ctx.currentTime;

        // White noise burst shaped like steam exhaust: "tshhh"
        const bufferSize = this.ctx.sampleRate * 0.08;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
          data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.3));
        }

        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;

        const filter = this.ctx.createBiquadFilter();
        filter.type = 'bandpass';
        filter.frequency.setValueAtTime(800, now);
        filter.Q.setValueAtTime(2.0, now);

        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.15, now);
        gain.gain.exponentialRampToValueAtTime(0.01, now + 0.08);

        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.ctx.destination);

        noise.start(now);
      }

      playBoardingChime() {
        if (this.muted || !this.ctx) return;
        this.init();
        const now = this.ctx.currentTime;

        // Two-tone bell: High ding-dong
        [880, 1174].forEach((freq, idx) => {
          const osc = this.ctx.createOscillator();
          const gain = this.ctx.createGain();

          osc.type = 'sine';
          osc.frequency.setValueAtTime(freq, now + idx * 0.1);

          gain.gain.setValueAtTime(0.001, now + idx * 0.1);
          gain.gain.linearRampToValueAtTime(0.2, now + idx * 0.1 + 0.02);
          gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.1 + 0.35);

          osc.connect(gain);
          gain.connect(this.ctx.destination);

          osc.start(now + idx * 0.1);
          osc.stop(now + idx * 0.1 + 0.36);
        });
      }

      playCrash() {
        if (this.muted || !this.ctx) return;
        this.init();
        const now = this.ctx.currentTime;

        // Heavy derail clatter
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(140, now);
        osc.frequency.exponentialRampToValueAtTime(30, now + 0.5);

        gain.gain.setValueAtTime(0.3, now);
        gain.gain.exponentialRampToValueAtTime(0.01, now + 0.5);

        osc.connect(gain);
        gain.connect(this.ctx.destination);
        osc.start(now);
        osc.stop(now + 0.51);
      }
    }

    /* =========================================================================
       CONSTANTS & THEMES
       ========================================================================= */
    const COLS = 22;
    const ROWS = 14;
    const TILE = 42; // Canvas is 924x588

    const STATIONS = [
      "Sunny Meadow", "Pine Hill Junction", "Brambleside Yard", 
      "Willow Creek", "Copper Kettle Depot", "Rosewood Halt", "Silver Falls"
    ];

    // Palette for distinct train wagons
    const WAGON_COLORS = [
      { body: '#b83227', trim: '#e8a838', roof: '#3d1c19', name: 'Ruby Coach' },      // Red
      { body: '#1f6f8b', trim: '#99f3bd', roof: '#113340', name: 'Azure Sleeper' },   // Blue
      { body: '#2e7d32', trim: '#fdd835', roof: '#133e16', name: 'Emerald Express' }, // Green
      { body: '#d48818', trim: '#fff176', roof: '#543105', name: 'Amber Dining Car' },// Yellow/Gold
      { body: '#6a1b9a', trim: '#ea80fc', roof: '#2d0a42', name: 'Royal Pullman' },   // Purple
      { body: '#c25929', trim: '#ffe082', roof: '#481d09', name: 'Copper Mail Van' }   // Orange
    ];

    /* =========================================================================
       GAME STATE
       ========================================================================= */
    const canvas = document.getElementById('railway-canvas');
    const ctx = canvas.getContext('2d');
    const audio = new TrainAudio();

    let score = 0;
    let highScore = 0;
    let isDemo = true;
    let isGameOver = false;

    // Movement & timing
    let baseTickRate = 180; // ms per grid tile step
    let tickInterval = baseTickRate;
    let lastTickTime = performance.now();
    let moveProgress = 0; // 0.0 to 1.0 between ticks for buttery smooth rendering

    // Direction vector
    let dir = { x: 1, y: 0 };
    let nextDir = { x: 1, y: 0 };

    // Train segments: Array of { x, y, prevX, prevY, angle }
    let train = [];
    let pendingWagons = 0;

    // Passenger waiting at platform
    let passenger = { x: 16, y: 7, variant: 0, bobTimer: 0 };
    let floatingTexts = [];
    let steamParticles = [];

    // Decorative static station accessories
    let decorations = [];

    /* =========================================================================
       INITIALIZATION & LEVEL SETUP
       ========================================================================= */
    function initDecorations() {
      decorations = [];
      // Generate some pretty static model trees and platform sheds
      const occupied = new Set();
      // Add corners
      const corners = [
        { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 },
        { x: COLS - 2, y: 1 }, { x: COLS - 3, y: 1 },
        { x: 1, y: ROWS - 2 }, { x: COLS - 2, y: ROWS - 2 }
      ];
      corners.forEach(c => {
        decorations.push({
          x: c.x,
          y: c.y,
          type: 'tree',
          seed: Math.random()
        });
      });
    }

    function resetTrain(startX, startY, length = 4) {
      train = [];
      dir = { x: 1, y: 0 };
      nextDir = { x: 1, y: 0 };

      for (let i = 0; i < length; i++) {
        train.push({
          x: startX - i,
          y: startY,
          prevX: startX - i - 1,
          prevY: startY,
          angle: 0
        });
      }
    }

    function spawnPassenger() {
      let valid = false;
      let rx = 0, ry = 0;
      let attempts = 0;

      while (!valid && attempts < 100) {
        attempts++;
        rx = Math.floor(Math.random() * (COLS - 4)) + 2;
        ry = Math.floor(Math.random() * (ROWS - 4)) + 2;

        valid = true;
        // Don't spawn on train
        for (let seg of train) {
          if (seg.x === rx && seg.y === ry) { valid = false; break; }
        }
        // Don't spawn on decor
        for (let d of decorations) {
          if (d.x === rx && d.y === ry) { valid = false; break; }
        }
      }

      passenger.x = rx;
      passenger.y = ry;
      passenger.variant = Math.floor(Math.random() * 4);
      passenger.bobTimer = 0;

      // Update station badge name
      const stationEl = document.getElementById('station-name');
      const stName = STATIONS[Math.floor(Math.random() * STATIONS.length)];
      stationEl.textContent = "Platform: " + stName;
    }

    function startGame(asPlayer = true) {
      isDemo = !asPlayer;
      isGameOver = false;
      score = 0;
      pendingWagons = 0;
      tickInterval = baseTickRate;
      updateScoreUI();

      document.getElementById('start-overlay').classList.add('hidden');
      document.getElementById('gameover-overlay').classList.add('hidden');
      document.getElementById('demo-banner').style.display = isDemo ? 'block' : 'none';

      initDecorations();
      resetTrain(6, 7, isDemo ? 5 : 4);
      spawnPassenger();
      floatingTexts = [];
      steamParticles = [];

      lastTickTime = performance.now();
      if (!isDemo) {
        audio.playWhistle();
      }
    }

    function updateScoreUI() {
      document.getElementById('score-val').textContent = score;
      if (score > highScore) {
        highScore = score;
        document.getElementById('best-val').textContent = highScore;
      }
    }

    /* =========================================================================
       AUTOPILOT (DEMO MODE AI - BFS SEARCH)
       ========================================================================= */
    function getDemoNextDirection() {
      const head = train[0];
      const target = { x: passenger.x, y: passenger.y };

      // BFS to passenger
      const queue = [{ x: head.x, y: head.y, path: [] }];
      const visited = new Set();
      visited.add(`${head.x},${head.y}`);

      // Obstacles: train body (except very tip of tail which might move)
      const obstacles = new Set();
      for (let i = 0; i < train.length - 1; i++) {
        obstacles.add(`${train[i].x},${train[i].y}`);
      }

      const neighbors = [
        { x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }
      ];

      let foundPath = null;

      while (queue.length > 0) {
        const curr = queue.shift();

        if (curr.x === target.x && curr.y === target.y) {
          foundPath = curr.path;
          break;
        }

        for (let n of neighbors) {
          const nx = curr.x + n.x;
          const ny = curr.y + n.y;
          const key = `${nx},${ny}`;

          if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
            if (!obstacles.has(key) && !visited.has(key)) {
              visited.add(key);
              queue.push({
                x: nx,
                y: ny,
                path: curr.path.length === 0 ? [n] : curr.path
              });
            }
          }
        }
      }

      if (foundPath && foundPath.length > 0) {
        return foundPath[0];
      }

      // If trapped or no direct path, pick open neighbor with most space
      let bestDir = null;
      let maxOpen = -1;

      for (let n of neighbors) {
        const nx = head.x + n.x;
        const ny = head.y + n.y;
        const key = `${nx},${ny}`;

        if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS && !obstacles.has(key)) {
          // Count open cells (1-step lookahead)
          let openCount = 0;
          for (let sub of neighbors) {
            const sx = nx + sub.x;
            const sy = ny + sub.y;
            if (sx >= 0 && sx < COLS && sy >= 0 && sy < ROWS && !obstacles.has(`${sx},${sy}`)) {
              openCount++;
            }
          }
          if (openCount > maxOpen) {
            maxOpen = openCount;
            bestDir = n;
          }
        }
      }

      return bestDir || dir;
    }

    /* =========================================================================
       TICK UPDATE (GRID LOGIC)
       ========================================================================= */
    function gameTick() {
      if (isGameOver) return;

      if (isDemo) {
        nextDir = getDemoNextDirection();
      }

      // Prevent 180° instant reverse
      if (nextDir.x !== -dir.x || nextDir.y !== -dir.y) {
        dir = { ...nextDir };
      }

      const head = train[0];
      const newX = head.x + dir.x;
      const newY = head.y + dir.y;

      // Check wall collision
      if (newX < 0 || newX >= COLS || newY < 0 || newY >= ROWS) {
        triggerDerail();
        return;
      }

      // Check self collision (wagons)
      for (let i = 0; i < train.length; i++) {
        if (train[i].x === newX && train[i].y === newY) {
          triggerDerail();
          return;
        }
      }

      // Move segments
      // Each segment stores its previous spot for smooth interpolation
      let prevX = head.x;
      let prevY = head.y;

      // Calculate head angle
      const headAngle = Math.atan2(dir.y, dir.x);

      train[0] = {
        x: newX,
        y: newY,
        prevX: head.x,
        prevY: head.y,
        angle: headAngle
      };

      for (let i = 1; i < train.length; i++) {
        const seg = train[i];
        const segAngle = Math.atan2(prevY - seg.y, prevX - seg.x);
        const tempX = seg.x;
        const tempY = seg.y;

        train[i] = {
          x: prevX,
          y: prevY,
          prevX: tempX,
          prevY: tempY,
          angle: segAngle
        };

        prevX = tempX;
        prevY = tempY;
      }

      // Attach new wagon if passenger was picked up
      if (pendingWagons > 0) {
        train.push({
          x: prevX,
          y: prevY,
          prevX: prevX,
          prevY: prevY,
          angle: train[train.length - 1].angle
        });
        pendingWagons--;
      }

      // Sound: chuff chuff rhythm
      audio.playChuff();

      // Emit smoke puffs from engine smokestack
      emitSteam(train[0].prevX, train[0].prevY, dir);

      // Check Passenger collection
      if (newX === passenger.x && newY === passenger.y) {
        onPassengerBoarded();
      }
    }

    function onPassengerBoarded() {
      score++;
      pendingWagons++;
      updateScoreUI();
      audio.playBoardingChime();

      // Faster speed slightly as train grows
      tickInterval = Math.max(90, baseTickRate - score * 3);

      // Floating celebratory text
      const messages = ["All Aboard!", "Tickets Checked!", "+1 Passenger", "Full Steam!", "Next Station!"];
      const msg = messages[Math.floor(Math.random() * messages.length)];
      floatingTexts.push({
        text: msg,
        x: (passenger.x + 0.5) * TILE,
        y: (passenger.y + 0.2) * TILE,
        alpha: 1.0,
        vy: -1.2
      });

      // Special whistle on milestones
      if (score % 5 === 0) {
        audio.playWhistle();
      }

      spawnPassenger();
    }

    function triggerDerail() {
      if (isDemo) {
        // In demo, smoothly restart after a tiny pause
        setTimeout(() => {
          if (isDemo) startGame(false);
        }, 1200);
      } else {
        isGameOver = true;
        audio.playCrash();
        document.getElementById('final-score').textContent = score;
        document.getElementById('gameover-overlay').classList.remove('hidden');
      }
    }

    /* =========================================================================
       PARTICLES & VISUAL FX
       ========================================================================= */
    function emitSteam(gridX, gridY, direction) {
      const px = (gridX + 0.5) * TILE + direction.x * 12;
      const py = (gridY + 0.5) * TILE + direction.y * 12;

      for (let i = 0; i < 2; i++) {
        steamParticles.push({
          x: px + (Math.random() - 0.5) * 6,
          y: py + (Math.random() - 0.5) * 6,
          vx: -direction.x * 0.8 + (Math.random() - 0.5) * 0.8,
          vy: -direction.y * 0.8 - Math.random() * 0.8,
          radius: 5 + Math.random() * 4,
          maxRadius: 18 + Math.random() * 10,
          alpha: 0.8,
          decay: 0.02 + Math.random() * 0.015
        });
      }
    }

    /* =========================================================================
       RENDERING: DIORAMA, TRACKS, TRAIN, PASSENGER
       ========================================================================= */

    function drawModelTracks() {
      // 1. Grassy Field / Diorama Table
      ctx.fillStyle = '#2d4a23';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      // Subtle grass texture / checkered lawn squares
      ctx.fillStyle = 'rgba(255, 255, 255, 0.025)';
      for (let x = 0; x < COLS; x++) {
        for (let y = 0; y < ROWS; y++) {
          if ((x + y) % 2 === 0) {
            ctx.fillRect(x * TILE, y * TILE, TILE, TILE);
          }
        }
      }

      // 2. Full Railway Grid Layout (Ties & Rails on all tiles so train is always on track)
      for (let y = 0; y < ROWS; y++) {
        for (let x = 0; x < COLS; x++) {
          const cx = x * TILE;
          const cy = y * TILE;

          // Gravel Ballast path
          ctx.fillStyle = 'rgba(40, 32, 22, 0.45)';
          ctx.fillRect(cx + 3, cy + 3, TILE - 6, TILE - 6);

          // Wooden sleepers / cross-ties (two per cell in each direction)
          ctx.fillStyle = '#442d18';
          ctx.fillRect(cx + 8, cy + 18, TILE - 16, 5);
          ctx.fillRect(cx + 18, cy + 8, 5, TILE - 16);

          // Subtle iron rail guides
          ctx.strokeStyle = '#6d5e52';
          ctx.lineWidth = 1;
          ctx.strokeRect(cx + 10, cy + 10, TILE - 20, TILE - 20);
        }
      }

      // Diorama perimeter fencing/boardwalk
      ctx.strokeStyle = '#181008';
      ctx.lineWidth = 6;
      ctx.strokeRect(3, 3, canvas.width - 6, canvas.height - 6);

      // Brass corner nails
      ctx.fillStyle = '#e5b158';
      const nails = [[10, 10], [canvas.width - 10, 10], [10, canvas.height - 10], [canvas.width - 10, canvas.height - 10]];
      nails.forEach(([nx, ny]) => {
        ctx.beginPath();
        ctx.arc(nx, ny, 3.5, 0, Math.PI * 2);
        ctx.fill();
      });
    }

    function drawDecorations() {
      decorations.forEach(d => {
        const cx = (d.x + 0.5) * TILE;
        const cy = (d.y + 0.5) * TILE;

        // Model pine tree
        ctx.save();
        ctx.translate(cx, cy);

        // Shadow
        ctx.fillStyle = 'rgba(0,0,0,0.25)';
        ctx.beginPath();
        ctx.ellipse(3, 6, 12, 8, 0, 0, Math.PI * 2);
        ctx.fill();

        // Tree layers
        const greens = ['#1d3b1e', '#2c532d', '#3d6c3e'];
        for (let i = 0; i < 3; i++) {
          ctx.fillStyle = greens[i];
          ctx.beginPath();
          ctx.arc(0, -i * 5, 14 - i * 3, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.restore();
      });
    }

    function drawPlatformAndPassenger() {
      const px = passenger.x * TILE;
      const py = passenger.y * TILE;
      const time = performance.now() * 0.005;

      // Platform Wooden Deck
      ctx.save();
      ctx.fillStyle = '#1e140d';
      ctx.fillRect(px + 2, py + 2, TILE - 4, TILE - 4);

      // Deck planks
      ctx.fillStyle = '#7a5431';
      ctx.fillRect(px + 4, py + 4, TILE - 8, TILE - 8);
      ctx.strokeStyle = '#523820';
      ctx.lineWidth = 1;
      for (let i = 8; i < TILE - 8; i += 6) {
        ctx.beginPath();
        ctx.moveTo(px + 4, py + i);
        ctx.lineTo(px + TILE - 4, py + i);
        ctx.stroke();
      }

      // Yellow safety edge
      ctx.fillStyle = '#e5b158';
      ctx.fillRect(px + 4, py + TILE - 8, TILE - 8, 3);

      // Lantern post at corner of platform
      ctx.fillStyle = '#222';
      ctx.fillRect(px + 6, py + 6, 3, 14);
      ctx.fillStyle = '#ffdf78';
      ctx.beginPath();
      ctx.arc(px + 7.5, py + 6, 3.5, 0, Math.PI * 2);
      ctx.fill();

      // Passenger Figure (bobbing with excitement)
      const bob = Math.sin(time * 3) * 1.5;
      const figX = px + TILE / 2 + 3;
      const figY = py + TILE / 2 + bob;

      // Shadow
      ctx.fillStyle = 'rgba(0,0,0,0.35)';
      ctx.beginPath();
      ctx.ellipse(figX, py + TILE / 2 + 8, 6, 3.5, 0, 0, Math.PI * 2);
      ctx.fill();

      // Suitcase
      ctx.fillStyle = '#8d4b1a';
      ctx.fillRect(figX - 11, figY + 1, 6, 8);
      ctx.fillStyle = '#e2a35d';
      ctx.fillRect(figX - 10, figY - 1, 4, 2); // Handle

      // Coat/Dress (colored by variant)
      const coatColors = ['#c0392b', '#2980b9', '#8e44ad', '#d35400'];
      ctx.fillStyle = coatColors[passenger.variant % coatColors.length];
      ctx.beginPath();
      ctx.roundRect(figX - 4.5, figY - 2, 9, 12, 3);
      ctx.fill();

      // Head
      ctx.fillStyle = '#f5cba7';
      ctx.beginPath();
      ctx.arc(figX, figY - 6, 4, 0, Math.PI * 2);
      ctx.fill();

      // Hat / Cap
      ctx.fillStyle = '#2c3e50';
      ctx.beginPath();
      ctx.ellipse(figX, figY - 8.5, 5.5, 2.5, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillRect(figX - 3, figY - 12, 6, 4);

      // Waving hand
      const wave = Math.sin(time * 8) * 3;
      ctx.fillStyle = '#f5cba7';
      ctx.beginPath();
      ctx.arc(figX + 6, figY - 4 + wave, 2, 0, Math.PI * 2);
      ctx.fill();

      ctx.restore();
    }

    function getInterpolatedSegmentPos(seg, progress) {
      // Smooth linear interpolation between prev grid cell and current
      const ix = seg.prevX + (seg.x - seg.prevX) * progress;
      const iy = seg.prevY + (seg.y - seg.prevY) * progress;
      return {
        x: (ix + 0.5) * TILE,
        y: (iy + 0.5) * TILE,
        angle: seg.angle
      };
    }

    function drawLocomotive(pos) {
      ctx.save();
      ctx.translate(pos.x, pos.y);
      ctx.rotate(pos.angle);

      // Headlight Beam Cone
      const grad = ctx.createRadialGradient(28, 0, 4, 75, 0, 40);
      grad.addColorStop(0, 'rgba(255, 240, 160, 0.45)');
      grad.addColorStop(1, 'rgba(255, 240, 160, 0)');
      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.moveTo(18, -6);
      ctx.lineTo(80, -32);
      ctx.lineTo(80, 32);
      ctx.lineTo(18, 6);
      ctx.closePath();
      ctx.fill();

      // Shadow
      ctx.fillStyle = 'rgba(0,0,0,0.35)';
      ctx.beginPath();
      ctx.roundRect(-16, -11, 35, 22, 5);
      ctx.fill();

      // Cowcatcher (Wedge at front)
      ctx.fillStyle = '#1c1b1a';
      ctx.beginPath();
      ctx.moveTo(16, -9);
      ctx.lineTo(21, 0);
      ctx.lineTo(16, 9);
      ctx.closePath();
      ctx.fill();
      ctx.strokeStyle = '#c48c2c';
      ctx.lineWidth = 1.5;
      ctx.stroke();

      // Boiler (Black/Dark Green cylinder)
      ctx.fillStyle = '#1e2d24';
      ctx.beginPath();
      ctx.roundRect(-8, -8.5, 24, 17, 4);
      ctx.fill();

      // Brass Boiler Bands
      ctx.strokeStyle = '#e5b158';
      ctx.lineWidth = 1.8;
      ctx.beginPath();
      ctx.moveTo(4, -8); ctx.lineTo(4, 8);
      ctx.moveTo(11, -8); ctx.lineTo(11, 8);
      ctx.stroke();

      // Smokestack
      ctx.fillStyle = '#0f0f0f';
      ctx.beginPath();
      ctx.arc(10, 0, 4.5, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = '#c48c2c';
      ctx.beginPath();
      ctx.arc(10, 0, 2.5, 0, Math.PI * 2);
      ctx.fill();

      // Cabin (Driver's cab at rear)
      ctx.fillStyle = '#2b1a13';
      ctx.beginPath();
      ctx.roundRect(-16, -10, 12, 20, 3);
      ctx.fill();

      // Cab Roof
      ctx.fillStyle = '#110b08';
      ctx.beginPath();
      ctx.roundRect(-17, -11, 14, 22, 3);
      ctx.fill();

      // Cab Warm Lit Windows
      ctx.fillStyle = '#ffea88';
      ctx.fillRect(-14, -8, 3.5, 4.5);
      ctx.fillRect(-14, 3.5, 3.5, 4.5);

      // Brass Headlight Lantern
      ctx.fillStyle = '#e5b158';
      ctx.fillRect(16, -3, 3, 6);
      ctx.fillStyle = '#ffffff';
      ctx.beginPath();
      ctx.arc(18, 0, 2.2, 0, Math.PI * 2);
      ctx.fill();

      ctx.restore();
    }

    function drawWagon(pos, index, totalWagons) {
      ctx.save();
      ctx.translate(pos.x, pos.y);
      ctx.rotate(pos.angle);

      // Shadow
      ctx.fillStyle = 'rgba(0,0,0,0.3)';
      ctx.beginPath();
      ctx.roundRect(-14, -10, 28, 20, 4);
      ctx.fill();

      // Coupler linking bar
      ctx.fillStyle = '#222';
      ctx.fillRect(-18, -2, 6, 4);

      if (index === 0) {
        // TENDER (Coal Car directly behind engine)
        ctx.fillStyle = '#251b14';
        ctx.beginPath();
        ctx.roundRect(-13, -8, 25, 16, 2);
        ctx.fill();

        // Coal mound
        ctx.fillStyle = '#111';
        ctx.beginPath();
        ctx.ellipse(-1, 0, 8, 5, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#2c2c2c';
        ctx.beginPath();
        ctx.arc(-3, -2, 2, 0, Math.PI * 2);
        ctx.arc(2, 2, 2.5, 0, Math.PI * 2);
        ctx.fill();

      } else if (index === totalWagons - 1 && totalWagons > 2) {
        // CABOOSE (Last carriage: red with lantern)
        ctx.fillStyle = '#a82a20';
        ctx.beginPath();
        ctx.roundRect(-13, -8.5, 25, 17, 3);
        ctx.fill();

        // Cupola observation roof
        ctx.fillStyle = '#68150f';
        ctx.fillRect(-4, -5.5, 9, 11);

        // Windows
        ctx.fillStyle = '#ffecb3';
        ctx.fillRect(-2, -4, 5, 8);

        // Rear glowing red lantern
        ctx.fillStyle = '#ff3333';
        ctx.beginPath();
        ctx.arc(-14, 0, 2.5, 0, Math.PI * 2);
        ctx.fill();

      } else {
        // PASSENGER CARRIAGE (Colorful toy style)
        const style = WAGON_COLORS[(index - 1) % WAGON_COLORS.length];

        // Main body
        ctx.fillStyle = style.body;
        ctx.beginPath();
        ctx.roundRect(-13, -8.5, 25, 17, 3);
        ctx.fill();

        // Roof Trim
        ctx.fillStyle = style.roof;
        ctx.beginPath();
        ctx.roundRect(-11, -6, 22, 12, 2);
        ctx.fill();

        // Side windows with silhouettes
        ctx.fillStyle = '#ffea9f';
        ctx.fillRect(-9, -7.5, 4, 3);
        ctx.fillRect(-2, -7.5, 4, 3);
        ctx.fillRect(5, -7.5, 4, 3);

        ctx.fillRect(-9, 4.5, 4, 3);
        ctx.fillRect(-2, 4.5, 4, 3);
        ctx.fillRect(5, 4.5, 4, 3);

        // Brass handrail / trim stripe
        ctx.fillStyle = style.trim;
        ctx.fillRect(-12, -0.75, 23, 1.5);
      }

      ctx.restore();
    }

    function drawSteamAndEffects() {
      // Steam particles
      for (let i = steamParticles.length - 1; i >= 0; i--) {
        const p = steamParticles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.radius += (p.maxRadius - p.radius) * 0.08;
        p.alpha -= p.decay;

        if (p.alpha <= 0) {
          steamParticles.splice(i, 1);
          continue;
        }

        ctx.fillStyle = `rgba(240, 240, 245, ${p.alpha})`;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
        ctx.fill();
      }

      // Floating passenger cheers
      for (let i = floatingTexts.length - 1; i >= 0; i--) {
        const ft = floatingTexts[i];
        ft.y += ft.vy;
        ft.alpha -= 0.015;

        if (ft.alpha <= 0) {
          floatingTexts.splice(i, 1);
          continue;
        }

        ctx.save();
        ctx.font = 'bold 15px "Trebuchet MS", sans-serif';
        ctx.fillStyle = `rgba(255, 240, 160, ${ft.alpha})`;
        ctx.strokeStyle = `rgba(40, 20, 10, ${ft.alpha})`;
        ctx.lineWidth = 3;
        ctx.textAlign = 'center';
        ctx.strokeText(ft.text, ft.x, ft.y);
        ctx.fillText(ft.text, ft.x, ft.y);
        ctx.restore();
      }
    }

    /* =========================================================================
       MAIN LOOP (RAF + INTERPOLATION)
       ========================================================================= */
    function gameLoop(now) {
      requestAnimationFrame(gameLoop);

      // Compute grid step progress for smooth 60fps interpolation
      const elapsed = now - lastTickTime;
      moveProgress = Math.min(1.0, elapsed / tickInterval);

      if (elapsed >= tickInterval) {
        lastTickTime = now;
        moveProgress = 0;
        gameTick();
      }

      // Draw everything
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      drawModelTracks();
      drawDecorations();
      drawPlatformAndPassenger();

      // Draw Train from tail to head so locomotive sits on top
      if (train.length > 0) {
        for (let i = train.length - 1; i >= 1; i--) {
          const pos = getInterpolatedSegmentPos(train[i], moveProgress);
          drawWagon(pos, i - 1, train.length - 1);
        }
        const headPos = getInterpolatedSegmentPos(train[0], moveProgress);
        drawLocomotive(headPos);
      }

      drawSteamAndEffects();
    }

    /* =========================================================================
       USER INPUT (KEYBOARD, TOUCH, SOUND)
       ========================================================================= */
    function handleDirectionInput(dx, dy) {
      if (isDemo) {
        // Any real key click/press transitions out of demo
        startGame(true);
        return;
      }
      if (isGameOver) return;

      // Don't allow reversing into previous segment
      if (dx !== -dir.x || dy !== -dir.y) {
        nextDir = { x: dx, y: dy };
      }
    }

    window.addEventListener('keydown', (e) => {
      audio.init();

      if (isDemo && ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','KeyW','KeyA','KeyS','KeyD','Space'].includes(e.code)) {
        startGame(true);
        return;
      }

      switch (e.code) {
        case 'ArrowUp':
        case 'KeyW':
          handleDirectionInput(0, -1);
          e.preventDefault();
          break;
        case 'ArrowDown':
        case 'KeyS':
          handleDirectionInput(0, 1);
          e.preventDefault();
          break;
        case 'ArrowLeft':
        case 'KeyA':
          handleDirectionInput(-1, 0);
          e.preventDefault();
          break;
        case 'ArrowRight':
        case 'KeyD':
          handleDirectionInput(1, 0);
          e.preventDefault();
          break;
        case 'KeyM':
          toggleMute();
          break;
        case 'KeyH':
          audio.playWhistle();
          break;
        case 'Space':
          if (isGameOver) {
            startGame(true);
          }
          break;
      }
    });

    function toggleMute() {
      audio.init();
      audio.muted = !audio.muted;
      document.getElementById('btn-sound').textContent = audio.muted ? '🔇 Muted' : '🔊 Whistle';
    }

    document.getElementById('btn-sound').addEventListener('click', (e) => {
      e.stopPropagation();
      audio.init();
      if (!audio.muted) {
        audio.playWhistle();
      }
      toggleMute();
    });

    // Start / Restart buttons
    document.getElementById('start-btn').addEventListener('click', () => {
      audio.init();
      startGame(true);
    });

    document.getElementById('restart-btn').addEventListener('click', () => {
      audio.init();
      startGame(true);
    });

    // Canvas click directly starts player game if in demo mode
    canvas.addEventListener('pointerdown', () => {
      audio.init();
      if (isDemo) {
        startGame(true);
      } else {
        audio.playWhistle();
      }
    });

    // Touch D-Pad bindings
    document.getElementById('btn-up').addEventListener('pointerdown', (e) => { e.preventDefault(); handleDirectionInput(0, -1); });
    document.getElementById('btn-down').addEventListener('pointerdown', (e) => { e.preventDefault(); handleDirectionInput(0, 1); });
    document.getElementById('btn-left').addEventListener('pointerdown', (e) => { e.preventDefault(); handleDirectionInput(-1, 0); });
    document.getElementById('btn-right').addEventListener('pointerdown', (e) => { e.preventDefault(); handleDirectionInput(1, 0); });

    // Swipe support on canvas
    let touchStartX = 0;
    let touchStartY = 0;
    canvas.addEventListener('touchstart', (e) => {
      const t = e.touches[0];
      touchStartX = t.clientX;
      touchStartY = t.clientY;
    }, { passive: true });

    canvas.addEventListener('touchend', (e) => {
      const t = e.changedTouches[0];
      const dx = t.clientX - touchStartX;
      const dy = t.clientY - touchStartY;
      const absDx = Math.abs(dx);
      const absDy = Math.abs(dy);

      if (Math.max(absDx, absDy) > 20) {
        if (absDx > absDy) {
          handleDirectionInput(dx > 0 ? 1 : -1, 0);
        } else {
          handleDirectionInput(0, dy > 0 ? 1 : -1);
        }
      }
    }, { passive: true });

    /* =========================================================================
       BOOTSTRAP: FIRST PAINT IMMEDIATELY ACTIVE IN DEMO MODE
       ========================================================================= */
    startGame(false); // Starts in self-playing demo mode
    requestAnimationFrame(gameLoop);

  </script>
</body>
</html>
Reasoning trace (3,319 tokens)
**Developing Thought Patterns**

I'm currently mapping out the core mechanics for this locomotive-themed arcade game. My focus is on conceptualizing how the "passengers" will function as the "food" the train collects, and how the train's growth will represent picking them up.

**Refining Visual Elements**

I'm now diving into the visual aspects, aiming for a charming, miniature toy train aesthetic. My priority is ensuring responsiveness across various viewports and crafting immediate, engaging first paint. I envision a diorama-style presentation with soft shadows and vibrant, vintage passenger cars.

**Visualizing Track Mechanics**

I'm solidifying how the railway tracks will manifest. I'm exploring rendering them dynamically under the train's movement or pre-laying them in a diorama-like fashion, aiming for a visually stunning model train layout aesthetic.

**Synthesizing Soundscapes**

I'm currently exploring the Web Audio API to create a satisfyingly rhythmic "chug-chug" sound effect that dynamically syncs with the train's speed. This will involve synthesizing noise bursts for an engaging, rhythmic audio experience, aiming for a cute and immersive feel.

**Adding Sonic Textures**

I'm refining the audio, focusing on a delightful train whistle and distinct passenger sounds. I'm also ensuring the mute button is readily available and audio only starts after user interaction.

**Enhancing Locomotive Visuals**

I'm detailing the locomotive's appearance, giving it a warm, golden headlight glow and puffing smoke particles. I'm also visualizing the track background as a charming diorama, complete with elegant ballast and wooden ties.

**Painting Wagon Details**

I'm focusing on the vibrant toy colors for each wagon – crimson, emerald, royal blue, mustard, violet, and orange. I'm also detailing roof vents, tiny passenger windows, visible wheels, and couplers. The caboose is getting its classic red look with a lantern.

**Charting Navigation Logic**

I'm now exploring navigation, specifically how the train will find passengers using BFS. My focus is on refining the pathfinding algorithm to account for avoiding the train's own wagons.

**Implementing Control Schemes**

I'm developing the control inputs. On mobile, I'm implementing a virtual D-pad. For desktop, I'm planning for keyboard and mouse interaction, alongside an introductory prompt to start the game.

**Defining Grid Dimensions**

I'm establishing the game's grid and canvas dimensions, aiming for a 22x14 grid with a cell size of 42 pixels, resulting in a 924x588 pixel canvas. I'll also add borders to create a scenic station effect.

**Smooth Movement Engineering**

I'm detailing the interpolated movement logic. This involves calculating progress based on elapsed time and tick interval, allowing for smooth turns by interpolating between current and previous segment positions.

**Improving Navigation Pathfinding**

I'm now refining the AI's navigation to ensure it prioritizes finding passengers while intelligently avoiding self-collision. My focus is on implementing a robust pathfinding algorithm that accounts for the train's growing length and available space, ensuring dynamic and engaging gameplay. I'm also beginning to integrate the varied wagon designs and passenger boarding animations.

**Refining AI Pathfinding**

I'm now enhancing the AI's pathfinding to dynamically avoid self-collision, prioritizing passenger acquisition. I'm also starting to integrate distinct wagon designs and the passenger boarding animations into the logic.

**Refining Visual Assets**

I'm continuing to flesh out the visual details, focusing on how to render the game elements with pure HTML5 Canvas. This involves defining the aesthetic for the diorama elements, including the felt-like grass, ballast, and detailed track components. I'm also conceptualizing the specific design of the miniature passengers.

**Defining Locomotive Features**

I'm currently detailing the locomotive's features, specifying its distinct components like the cowcatcher, boiler, and cab. My focus is on defining the golden headlight glow and smoke particle effects, alongside the visual style of the wagons and their couplers.

**Improving Navigation Logic**

I'm currently refining the AI's pathfinding to intelligently avoid self-collision while prioritizing passenger acquisition. I'm also beginning to integrate distinct wagon designs and the passenger boarding animations into the core logic.

**Polishing Interface Elements**

I'm now meticulously crafting the UI, aiming for a rich, vintage railway board aesthetic with wood grain, brass accents, and a mechanical flip-clock score display. I'm also finalizing the steam-punk themed touch controls and ensuring the demo/game over screens are clear and engaging.