← DOOM results

DOOM

Ornith 1.0 35B GGUF UD IQ4 NL · typescript

Initial view of DOOM

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 48,062 bytes · SHA-256 930c9675f5da
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DOOM-LITE — Retro FPS</title>
<style>
/* ── css: game ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
#game-container {
    width: 100vw; height: 100vh; position: relative;
    background: #000;
    display: flex; justify-content: center; align-items: center;
}
#game-canvas {
    width: 100%; height: 100%;
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
    image-rendering: crisp-edges;
    cursor: crosshair;
    display: block;
}
#instructions {
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    text-align: center; color: #fff;
    font-family: monospace; z-index: 10; cursor: pointer;
    user-select: none;
}
#instructions h1 {
    font-size: 48px; color: #f44;
    text-shadow: 0 0 20px #f00; margin: 0 0 20px;
}
#instructions p {
    font-size: 18px; color: #aaa; margin: 8px;
}
#overlay {
    position: absolute; top: 0; left: 0;
    width: 100%; height: 100%;
    display: flex; justify-content: center; align-items: center;
    background: rgba(0,0,0,0.85);
    z-index: 20;
}
</style>
</head>
<body>

<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
// =============================================================================
// Constants & Types
// =============================================================================
const SCREEN_W = 640, SCREEN_H = 480;
const MAP_SIZE = 24;
const FOV = Math.PI / 3, HALF_FOV = FOV / 2, NUM_RAYS = 320;
const TOTAL_KEYS = 3;
const PLAYER_SPEED = 3.2, PLAYER_RADIUS = 0.22;
const WEAPONS = [
    { name: 'PISTOL', damage: 25, fireRate: 250, spread: 0.015, pellets: 1, type: 'pistol' },
    { name: 'SHOTGUN', damage: 14, fireRate: 750, spread: 0.08, pellets: 7, type: 'shotgun' },
    { name: 'ROCKET', damage: 60, fireRate: 1200, spread: 0.005, pellets: 1, type: 'rocket' },
];
const ENEMY_DEFS = {
    imp: { health: 30, speed: 1.8, damage: 12, atkRange: 0.9, atkCD: 800, color: '#dd3333', eyesColor: '#ffff00', size: 0.35 },
    demon: { health: 70, speed: 2.5, damage: 25, atkRange: 1.0, atkCD: 600, color: '#cc00cc', eyesColor: '#00ff00', size: 0.45 },
    knight: { health: 150, speed: 0.8, damage: 40, atkRange: 1.2, atkCD: 1200, color: '#3366cc', eyesColor: '#ff0000', size: 0.5 },
};
const ENEMY_SPAWNS = [
    { x: 8.5, y: 8.5, type: 'imp' },
    { x: 15.5, y: 8.5, type: 'imp' },
    { x: 20.5, y: 10.5, type: 'imp' },
    { x: 10.5, y: 14.5, type: 'demon' },
    { x: 5.5, y: 18.5, type: 'imp' },
    { x: 18.5, y: 14.5, type: 'demon' },
    { x: 8.5, y: 4.5, type: 'imp' },
    { x: 15.5, y: 18.5, type: 'knight' },
    { x: 20.5, y: 6.5, type: 'imp' },
    { x: 12.5, y: 6.5, type: 'demon' },
    { x: 3.5, y: 18.5, type: 'imp' },
    { x: 18.5, y: 4.5, type: 'demon' },
];
const PICKUP_SPAWNS = [
    { x: 4.5, y: 4.5, type: 'health' },
    { x: 8.5, y: 4.5, type: 'ammo' },
    { x: 12.5, y: 2.5, type: 'key' },
    { x: 16.5, y: 4.5, type: 'health' },
    { x: 20.5, y: 6.5, type: 'ammo' },
    { x: 6.5, y: 8.5, type: 'key' },
    { x: 18.5, y: 8.5, type: 'health' },
    { x: 10.5, y: 10.5, type: 'ammo' },
    { x: 4.5, y: 14.5, type: 'health' },
    { x: 14.5, y: 14.5, type: 'key' },
    { x: 20.5, y: 14.5, type: 'ammo' },
    { x: 8.5, y: 18.5, type: 'health' },
    { x: 16.5, y: 18.5, type: 'ammo' },
    { x: 3.5, y: 20.5, type: 'health' },
    { x: 20.5, y: 20.5, type: 'key' },
];
const MAP = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];
const EXIT_POS = { x: 22.5, y: 22.5 };
function isWall(mx, my) {
    if (mx < 0 || mx >= 24 || my < 0 || my >= 24)
        return true;
    return MAP[my][mx] > 0;
}
function canMove(nx, ny) {
    const r = PLAYER_RADIUS;
    for (let dy = -r; dy <= r + 0.01; dy += r) {
        for (let dx = -r; dx <= r + 0.01; dx += r) {
            if (isWall(Math.floor(nx + dx), Math.floor(ny + dy)))
                return false;
        }
    }
    return true;
}
const player = {
    x: 1.5, y: 1.5, angle: 0,
    health: 100, maxHealth: 100,
    ammo: 50, weaponIdx: 0,
    alive: true, respawnT: 0,
    shootAnim: 0, shootT: 0,
    bobPhase: 0, damageFlash: 0,
};
const gameState = {
    started: false, gameWon: false, gameLost: false,
    keysCollected: 0, kills: 0, score: 0, time: 0,
};
const input = {
    forward: false, backward: false, strafeLeft: false, strafeRight: false,
    shoot: false, switchWeapon: false, lastWeaponSwitch: 0,
    mouseDX: 0,
};
let enemies = [];
let pickups = [];
let projectiles = [];
let shakeAmount = 0;
// =============================================================================
// Initialization
// =============================================================================
function initGame() {
    player.x = 1.5;
    player.y = 1.5;
    player.angle = 0;
    player.health = 100;
    player.ammo = 50;
    player.weaponIdx = 0;
    player.alive = true;
    player.shootAnim = 0;
    player.shootT = 0;
    player.bobPhase = 0;
    player.damageFlash = 0;
    player.respawnT = 0;
    gameState.started = true;
    gameState.gameWon = false;
    gameState.gameLost = false;
    gameState.keysCollected = 0;
    gameState.kills = 0;
    gameState.score = 0;
    gameState.time = 0;
    enemies = [];
    pickups = [];
    projectiles = [];
    for (const s of ENEMY_SPAWNS) {
        const d = ENEMY_DEFS[s.type];
        if (!d)
            continue;
        enemies.push({
            x: s.x, y: s.y, defId: s.type,
            health: d.health, maxHealth: d.health,
            state: 'idle', animT: Math.random() * 6.28,
            attackT: 0, deathT: 0, dmgFlash: 0,
            spawnX: s.x, spawnY: s.y,
            angle: Math.random() * 6.28,
            kbX: 0, kbY: 0,
        });
    }
    for (const s of PICKUP_SPAWNS) {
        pickups.push({ x: s.x, y: s.y, type: s.type, active: true, animT: Math.random() * 6.28 });
    }
}
// =============================================================================
// Input
// =============================================================================
window.addEventListener('keydown', (e) => {
    switch (e.code) {
        case 'KeyW':
        case 'ArrowUp':
            input.forward = true;
            e.preventDefault();
            break;
        case 'KeyS':
        case 'ArrowDown':
            input.backward = true;
            e.preventDefault();
            break;
        case 'KeyA':
        case 'ArrowLeft':
            input.strafeLeft = true;
            e.preventDefault();
            break;
        case 'KeyD':
        case 'ArrowRight':
            input.strafeRight = true;
            e.preventDefault();
            break;
        case 'KeyQ':
            input.switchWeapon = true;
            break;
        case 'Space':
            input.shoot = true;
            e.preventDefault();
            break;
        case 'Digit1':
        case 'Numpad1':
            player.weaponIdx = 0;
            break;
        case 'Digit2':
        case 'Numpad2':
            player.weaponIdx = 1;
            break;
        case 'Digit3':
        case 'Numpad3':
            player.weaponIdx = 2;
            break;
    }
});
window.addEventListener('keyup', (e) => {
    switch (e.code) {
        case 'KeyW':
        case 'ArrowUp':
            input.forward = false;
            break;
        case 'KeyS':
        case 'ArrowDown':
            input.backward = false;
            break;
        case 'KeyA':
        case 'ArrowLeft':
            input.strafeLeft = false;
            break;
        case 'KeyD':
        case 'ArrowRight':
            input.strafeRight = false;
            break;
        case 'KeyQ':
            input.switchWeapon = false;
            break;
        case 'Space':
            input.shoot = false;
            break;
    }
});
document.addEventListener('mousemove', (e) => {
    input.mouseDX += e.movementX;
});
// =============================================================================
// Rendering
// =============================================================================
let canvas;
let ctx;
let imgData;
let pixels;
let zBuffer;
function initRender(c) {
    canvas = c;
    ctx = c.getContext('2d', { willReadFrequently: true });
    canvas.width = SCREEN_W;
    canvas.height = SCREEN_H;
    imgData = ctx.createImageData(SCREEN_W, SCREEN_H);
    pixels = imgData.data;
    zBuffer = new Float32Array(SCREEN_W);
}
function render() {
    const w = SCREEN_W, h = SCREEN_H;
    pixels.fill(0);
    zBuffer.fill(Infinity);
    const px = player.x, py = player.y, pa = player.angle;
    const bob = Math.sin(player.bobPhase) * (player.alive ? 4 : 0);
    const cos = Math.cos(pa), sin = Math.sin(pa);
    // Screen shake
    let ssx = 0, ssy = 0;
    if (shakeAmount > 0) {
        ssx = (Math.random() - 0.5) * shakeAmount * 2.5;
        ssy = (Math.random() - 0.5) * shakeAmount * 2.5;
        shakeAmount *= 0.85;
        if (shakeAmount < 0.3)
            shakeAmount = 0;
    }
    // Ceiling
    for (let y = 0; y < Math.floor(h / 2); y++) {
        let idx = y * w * 4;
        for (let x = 0; x < w; x++) {
            pixels[idx] = 8;
            pixels[idx + 1] = 8;
            pixels[idx + 2] = 24;
            pixels[idx + 3] = 255;
            idx += 4;
        }
    }
    // Floor
    for (let y = Math.floor(h / 2); y < h; y++) {
        const dist = (y - h / 2) / (h * 0.5);
        const s = 1 - dist * 0.5;
        let idx = y * w * 4;
        for (let x = 0; x < w; x++) {
            pixels[idx] = 26 * s | 0;
            pixels[idx + 1] = 26 * s | 0;
            pixels[idx + 2] = 46 * s | 0;
            pixels[idx + 3] = 255;
            idx += 4;
        }
    }
    // Raycast walls
    const sw = Math.max(1, w / NUM_RAYS | 0);
    for (let col = 0; col < w; col += sw) {
        const ri = Math.floor(col / w * NUM_RAYS);
        const ra = pa - HALF_FOV + ri / NUM_RAYS * FOV;
        const rdx = Math.cos(ra), rdy = Math.sin(ra);
        let mx = Math.floor(px), my = Math.floor(py);
        const ddx = Math.abs(1 / rdx), ddy = Math.abs(1 / rdy);
        let sx, sy, sdx, sdy;
        if (rdx < 0) {
            sx = -1;
            sdx = (px - mx) * ddx;
        }
        else {
            sx = 1;
            sdx = (mx + 1 - px) * ddx;
        }
        if (rdy < 0) {
            sy = -1;
            sdy = (py - my) * ddy;
        }
        else {
            sy = 1;
            sdy = (my + 1 - py) * ddy;
        }
        let hit = false, side = 0, wt = 1;
        while (!hit) {
            if (sdx < sdy) {
                sdx += ddx;
                mx += sx;
                side = 0;
            }
            else {
                sdy += ddy;
                my += sy;
                side = 1;
            }
            if (mx < 0 || mx >= 24 || my < 0 || my >= 24) {
                hit = true;
                break;
            }
            if (MAP[my][mx] > 0) {
                hit = true;
                wt = MAP[my][mx];
            }
        }
        let pd;
        if (side === 0)
            pd = Math.abs((mx - px + (1 - sx) / 2) / rdx);
        else
            pd = Math.abs((my - py + (1 - sy) / 2) / rdy);
        pd = Math.max(pd, 0.05);
        for (let s2 = 0; s2 < sw && col + s2 < w; s2++)
            zBuffer[col + s2] = pd;
        const lh = Math.floor(h / pd);
        const ds = (h - lh) / 2 + bob;
        let cr, cg, cb;
        if (wt === 2) {
            cr = 0x66;
            cg = 0x66;
            cb = 0x88;
        }
        else {
            const c = [0x8B, 0x69, 0x14];
            cr = c[0];
            cg = c[1];
            cb = c[2];
        }
        const ds2 = 1 / (1 + pd * 0.12);
        const ss = (side === 1 ? 0.7 : 1) * ds2;
        for (let y = Math.max(0, ds | 0); y < Math.min(h, ds + lh); y++) {
            const idx = (y * w + col) * 4;
            pixels[idx] = cr * ss | 0;
            pixels[idx + 1] = cg * ss | 0;
            pixels[idx + 2] = cb * ss | 0;
            pixels[idx + 3] = 255;
            if (sw > 1) {
                for (let s3 = 1; s3 < sw && col + s3 < w; s3++) {
                    const i2 = (y * w + col + s3) * 4;
                    pixels[i2] = pixels[idx];
                    pixels[i2 + 1] = pixels[idx + 1];
                    pixels[i2 + 2] = pixels[idx + 2];
                    pixels[i2 + 3] = 255;
                }
            }
        }
    }
    // Sprites
    const sprites = [];
    for (let i = 0; i < pickups.length; i++) {
        if (!pickups[i].active)
            continue;
        sprites.push({ x: pickups[i].x, y: pickups[i].y, kind: 'pickup', i });
    }
    for (let i = 0; i < enemies.length; i++) {
        if (enemies[i].state === 'dead' && enemies[i].deathT > 1500)
            continue;
        sprites.push({ x: enemies[i].x, y: enemies[i].y, kind: 'enemy', i });
    }
    for (let i = 0; i < projectiles.length; i++) {
        sprites.push({ x: projectiles[i].x, y: projectiles[i].y, kind: 'proj', i });
    }
    sprites.sort((a, b) => {
        const da = (a.x - px) ** 2 + (a.y - py) ** 2;
        const db = (b.x - px) ** 2 + (b.y - py) ** 2;
        return db - da;
    });
    for (const sp of sprites) {
        const dx = sp.x - px, dy = sp.y - py;
        const plX = -sin * Math.tan(HALF_FOV), plY = cos * Math.tan(HALF_FOV);
        const dX = cos, dY = sin;
        const inv = 1 / (plX * dY - dX * plY);
        const tx = inv * (dY * dx - dX * dy);
        const ty = inv * (-plY * dx + plX * dy);
        if (ty <= 0.2)
            continue;
        const sX = Math.floor(w / 2 * (1 + tx / ty));
        const sH = Math.abs(Math.floor(h / ty));
        const sW = sH;
        const sY = Math.floor((h - sH) / 2 + bob);
        drawSprite(sp, sX, sY, sH, sW, ty, w, h);
    }
    // Weapon
    drawWeapon(w, h, bob);
    // Damage vignette
    if (player.damageFlash > 0) {
        const i2 = Math.min(player.damageFlash * 0.35, 0.6);
        const gr = ctx.createRadialGradient(w / 2, h / 2, h * 0.3, w / 2, h / 2, h * 0.8);
        gr.addColorStop(0, 'rgba(255,0,0,0)');
        gr.addColorStop(1, `rgba(255,0,0,${i2})`);
        ctx.fillStyle = gr;
        ctx.fillRect(0, 0, w, h);
    }
    // Muzzle flash
    if (player.shootAnim > 10) {
        const cx = w / 2 + (Math.random() - 0.5) * 12;
        const cy = h - h * 0.22 + (Math.random() - 0.5) * 12;
        const gr = ctx.createRadialGradient(cx, cy, 0, cx, cy, 45);
        gr.addColorStop(0, 'rgba(255,255,200,0.6)');
        gr.addColorStop(0.5, 'rgba(255,150,0,0.25)');
        gr.addColorStop(1, 'rgba(255,50,0,0)');
        ctx.fillStyle = gr;
        ctx.fillRect(0, 0, w, h);
    }
    // Apply shake
    ctx.setTransform(1, 0, 0, 1, ssx, ssy);
    ctx.putImageData(imgData, 0, 0);
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    drawMinimap();
    drawHUD();
    drawCrosshair(w, h);
}
function drawSprite(sp, screenX, screenY, height, width, depth, w, h) {
    if (height < 2)
        return;
    if (sp.kind === 'enemy') {
        const e = enemies[sp.i];
        const d = ENEMY_DEFS[e.defId];
        if (!d)
            return;
        const sh = Math.max(0.2, 1 / (1 + depth * 0.08));
        const r = parseInt(d.color.slice(1, 3), 16) * sh | 0;
        const g = parseInt(d.color.slice(3, 5), 16) * sh | 0;
        const b = parseInt(d.color.slice(5, 7), 16) * sh | 0;
        for (let y = Math.max(0, screenY); y < Math.min(h, screenY + height); y++) {
            const rY = (y - screenY) / height;
            for (let x = Math.max(0, screenX - width / 2); x < Math.min(w, screenX + width / 2); x++) {
                if (depth > zBuffer[x])
                    continue;
                const dx = x / width - 0.5, dy = rY - 0.5;
                if (Math.sqrt(dx * dx + dy * dy) > 0.45)
                    continue;
                const idx = (y * w + x) * 4;
                if (e.dmgFlash > 0) {
                    pixels[idx] = 255;
                    pixels[idx + 1] = 255;
                    pixels[idx + 2] = 255;
                }
                else {
                    const sh2 = Math.max(0.3, 1 - Math.abs(Math.sqrt(dx * dx + dy * dy) - 0.3) * 2);
                    pixels[idx] = r * sh2 | 0;
                    pixels[idx + 1] = g * sh2 | 0;
                    pixels[idx + 2] = b * sh2 | 0;
                }
                pixels[idx + 3] = 255;
            }
        }
        // Eyes
        const eyeY = screenY + height * 0.35 | 0;
        const eyeS = Math.max(1, Math.floor(width * 0.07));
        const eyeR = parseInt(d.eyesColor.slice(1, 3), 16);
        const eyeG = parseInt(d.eyesColor.slice(3, 5), 16);
        const eyeB = parseInt(d.eyesColor.slice(5, 7), 16);
        for (const exOff of [-0.15, 0.15]) {
            const ex = screenX + width * exOff | 0;
            for (let ey = eyeY - eyeS; ey <= eyeY + eyeS; ey++) {
                for (let ex2 = ex - eyeS; ex2 <= ex + eyeS; ex2++) {
                    if (ex2 < 0 || ex2 >= w || ey < 0 || ey >= h)
                        continue;
                    if (depth > zBuffer[ex2])
                        continue;
                    const dist2 = Math.sqrt(((ex2 - ex) / eyeS) ** 2 + ((ey - eyeY) / eyeS) ** 2);
                    if (dist2 <= 1) {
                        const idx = (ey * w + ex2) * 4;
                        pixels[idx] = eyeR;
                        pixels[idx + 1] = eyeG;
                        pixels[idx + 2] = eyeB;
                        pixels[idx + 3] = 255;
                    }
                }
            }
        }
        // Health bar
        if (e.state !== 'dead') {
            const bW = Math.max(4, Math.floor(width * 0.6));
            const bH = Math.max(2, Math.floor(height * 0.05));
            const bX = Math.floor(screenX - bW / 2);
            const bY = Math.max(0, screenY - bH - 4);
            if (bY > 0) {
                for (let y = bY; y < bY + bH && y < h; y++) {
                    for (let x = bX; x < bX + bW && x < w; x++) {
                        if (depth > zBuffer[x])
                            continue;
                        const idx = (y * w + x) * 4;
                        pixels[idx] = 0x33;
                        pixels[idx + 1] = 0x33;
                        pixels[idx + 2] = 0x33;
                        pixels[idx + 3] = 255;
                    }
                }
                const hp = Math.max(0, e.health / e.maxHealth);
                const hpW = Math.floor(bW * hp);
                const hpColor = hp > 0.5 ? 0x00ff00 : hp > 0.25 ? 0xffff00 : 0xff0000;
                const hr = (hpColor >> 16) & 0xff, hg = (hpColor >> 8) & 0xff, hb = hpColor & 0xff;
                for (let y = bY; y < bY + bH && y < h; y++) {
                    for (let x = bX; x < bX + hpW && x < w; x++) {
                        if (depth > zBuffer[x])
                            continue;
                        const idx = (y * w + x) * 4;
                        pixels[idx] = hr;
                        pixels[idx + 1] = hg;
                        pixels[idx + 2] = hb;
                        pixels[idx + 3] = 255;
                    }
                }
            }
        }
        // Death fade
        if (e.state === 'dead') {
            const f = Math.max(0, 1 - e.deathT / 1500);
            for (let y = Math.max(0, screenY); y < Math.min(h, screenY + height); y++) {
                for (let x = Math.max(0, screenX - width / 2); x < Math.min(w, screenX + width / 2); x++) {
                    if (depth > zBuffer[x])
                        continue;
                    const idx = (y * w + x) * 4;
                    pixels[idx] = pixels[idx] * f | 0;
                    pixels[idx + 1] = pixels[idx + 1] * f | 0;
                    pixels[idx + 2] = pixels[idx + 2] * f | 0;
                }
            }
        }
    }
    else if (sp.kind === 'pickup') {
        const p = pickups[sp.i];
        const sz = Math.floor(height * 0.35);
        const bobO = Math.sin(p.animT) * sz * 0.25;
        const py2 = screenY + height * 0.35 + bobO;
        let cr, cg, cb;
        switch (p.type) {
            case 'health':
                cr = 0;
                cg = 255;
                cb = 68;
                break;
            case 'ammo':
                cr = 255;
                cg = 136;
                cb = 0;
                break;
            default:
                cr = 255;
                cg = 221;
                cb = 0;
        }
        for (let y = Math.max(0, Math.floor(py2 - sz)); y < Math.min(h, Math.floor(py2 + sz)); y++) {
            for (let x = Math.max(0, Math.floor(screenX - sz)); x < Math.min(w, Math.floor(screenX + sz)); x++) {
                if (depth > zBuffer[x])
                    continue;
                const dx = (x - screenX) / sz, dy = (y - py2) / sz;
                if (dx * dx + dy * dy > 0.25)
                    continue;
                const idx = (y * w + x) * 4;
                pixels[idx] = cr;
                pixels[idx + 1] = cg;
                pixels[idx + 2] = cb;
                pixels[idx + 3] = 255;
            }
        }
        if (p.type === 'health') {
            const th = Math.max(1, Math.floor(sz * 0.2)), ln = Math.floor(sz * 0.45);
            const pickupY = screenY + height * 0.35 + Math.sin(p.animT) * sz * 0.25;
            for (let y2 = pickupY - ln; y2 <= pickupY + ln; y2++) {
                for (let t = -th; t <= th; t++) {
                    const px2 = screenX + t;
                    if (px2 < 0 || px2 >= w || y2 < 0 || y2 >= h || depth > zBuffer[px2])
                        continue;
                    const idx = (y2 * w + px2) * 4;
                    pixels[idx] = 255;
                    pixels[idx + 1] = 255;
                    pixels[idx + 2] = 255;
                    pixels[idx + 3] = 255;
                }
            }
            for (let x2 = screenX - ln; x2 <= screenX + ln; x2++) {
                for (let t = -th; t <= th; t++) {
                    const py2 = pickupY + t;
                    if (py2 < 0 || py2 >= h || x2 < 0 || x2 >= w || depth > zBuffer[x2])
                        continue;
                    const idx = (py2 * w + x2) * 4;
                    pixels[idx] = 255;
                    pixels[idx + 1] = 255;
                    pixels[idx + 2] = 255;
                    pixels[idx + 3] = 255;
                }
            }
        }
    }
    else if (sp.kind === 'proj') {
        const sz = Math.max(2, Math.floor(80 / depth));
        for (let y = Math.max(0, screenY - sz); y < Math.min(h, screenY + sz); y++) {
            for (let x = Math.max(0, screenX - sz); x < Math.min(w, screenX + sz); x++) {
                if (depth > zBuffer[x])
                    continue;
                const dx = (x - screenX) / sz, dy = (y - screenY) / sz;
                if (dx * dx + dy * dy > 1)
                    continue;
                const idx = (y * w + x) * 4;
                pixels[idx] = 255;
                pixels[idx + 1] = 68;
                pixels[idx + 2] = 0;
                pixels[idx + 3] = 255;
            }
        }
    }
}
function drawWeapon(w, h, bob) {
    const recoil = player.shootAnim > 0 ? Math.sin(player.shootAnim * 0.5) * 12 : 0;
    switch (player.weaponIdx) {
        case 0: {
            const x = w / 2 - 40, y = h - 160 + recoil;
            ctx.fillStyle = '#555';
            ctx.fillRect(x + 25, y + 10, 12, 50);
            ctx.fillStyle = '#444';
            ctx.fillRect(x + 18, y + 50, 28, 25);
            ctx.fillStyle = '#333';
            ctx.fillRect(x + 22, y + 70, 20, 35);
            if (player.shootAnim > 10) {
                ctx.fillStyle = `rgba(255,${150 + Math.random() * 100 | 0},0,${0.5 + Math.random() * 0.5})`;
                ctx.beginPath();
                ctx.arc(x + 31, y + 5, 10 + Math.random() * 8, 0, Math.PI * 2);
                ctx.fill();
            }
            break;
        }
        case 1: {
            const x = w / 2 - 50, y = h - 170 + recoil;
            ctx.fillStyle = '#444';
            ctx.fillRect(x + 20, y + 10, 14, 60);
            ctx.fillRect(x + 45, y + 10, 14, 60);
            ctx.fillStyle = '#555';
            ctx.fillRect(x + 10, y + 60, 65, 25);
            ctx.fillStyle = '#654321';
            ctx.fillRect(x, y + 80, 85, 35);
            ctx.fillStyle = '#333';
            ctx.fillRect(x + 22, y + 50, 40, 10);
            if (player.shootAnim > 10) {
                ctx.fillStyle = `rgba(255,${100 + Math.random() * 100 | 0},0,${0.7 + Math.random() * 0.3})`;
                ctx.beginPath();
                ctx.arc(x + 27, y + 8, 12 + Math.random() * 6, 0, Math.PI * 2);
                ctx.fill();
                ctx.beginPath();
                ctx.arc(x + 52, y + 8, 12 + Math.random() * 6, 0, Math.PI * 2);
                ctx.fill();
            }
            break;
        }
        case 2: {
            const x = w / 2 - 55, y = h - 165 + recoil;
            ctx.fillStyle = '#3a3a3a';
            ctx.fillRect(x + 25, y + 15, 70, 30);
            ctx.fillStyle = '#cc3300';
            ctx.beginPath();
            ctx.moveTo(x + 25, y + 15);
            ctx.lineTo(x + 10, y + 30);
            ctx.lineTo(x + 25, y + 45);
            ctx.fill();
            ctx.fillStyle = '#555';
            ctx.fillRect(x + 90, y + 12, 15, 6);
            ctx.fillRect(x + 90, y + 42, 15, 6);
            ctx.fillStyle = '#444';
            ctx.fillRect(x + 30, y + 40, 25, 40);
            if (player.shootAnim > 10) {
                ctx.fillStyle = `rgba(255,50,0,${0.8 + Math.random() * 0.2})`;
                ctx.beginPath();
                ctx.arc(x + 8, y + 30, 15 + Math.random() * 10, 0, Math.PI * 2);
                ctx.fill();
            }
            break;
        }
    }
}
function drawMinimap() {
    const sc = 4;
    const mx = SCREEN_W - 24 * sc - 10, my = 10;
    ctx.save();
    ctx.globalAlpha = 0.65;
    ctx.fillStyle = '#000';
    ctx.fillRect(mx - 1, my - 1, 24 * sc + 2, 24 * sc + 2);
    for (let y = 0; y < 24; y++) {
        for (let x = 0; x < 24; x++) {
            if (MAP[y][x] > 0) {
                ctx.fillStyle = '#555';
                ctx.fillRect(mx + x * sc, my + y * sc, sc, sc);
            }
        }
    }
    ctx.fillStyle = '#0f0';
    ctx.fillRect(mx + player.x * sc - 2, my + player.y * sc - 2, 4, 4);
    ctx.strokeStyle = '#0f0';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(mx + player.x * sc, my + player.y * sc);
    ctx.lineTo(mx + (player.x + Math.cos(player.angle) * 2) * sc, my + (player.y + Math.sin(player.angle) * 2) * sc);
    ctx.stroke();
    for (const e of enemies) {
        if (e.state === 'dead')
            continue;
        ctx.fillStyle = '#f00';
        ctx.fillRect(mx + e.x * sc - 1, my + e.y * sc - 1, 3, 3);
    }
    for (const p of pickups) {
        if (!p.active)
            continue;
        ctx.fillStyle = p.type === 'health' ? '#0f0' : p.type === 'ammo' ? '#f80' : '#ff0';
        ctx.fillRect(mx + p.x * sc - 1, my + p.y * sc - 1, 2, 2);
    }
    ctx.restore();
}
function drawHUD() {
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(8, SCREEN_H - 30, 140, 22);
    const hp = player.health / player.maxHealth;
    const hc = hp > 0.5 ? '#00ff44' : hp > 0.25 ? '#ffaa00' : '#ff3333';
    ctx.fillStyle = hc;
    ctx.fillRect(10, SCREEN_H - 28, 136 * hp, 18);
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 13px monospace';
    ctx.textAlign = 'center';
    ctx.fillText(`${Math.ceil(player.health)}`, 78, SCREEN_H - 14);
    ctx.textAlign = 'left';
    ctx.fillStyle = '#aaa';
    ctx.font = '9px monospace';
    ctx.fillText('HEALTH', 14, SCREEN_H - 32);
    ctx.textAlign = 'right';
    ctx.fillStyle = '#aaa';
    ctx.font = '9px monospace';
    ctx.fillText('AMMO', SCREEN_W - 12, SCREEN_H - 32);
    ctx.fillStyle = player.ammo > 10 ? '#ffaa00' : '#ff3333';
    ctx.font = 'bold 16px monospace';
    ctx.fillText(`${player.ammo}`, SCREEN_W - 12, SCREEN_H - 14);
    const wn = ['PISTOL', 'SHOTGUN', 'ROCKET'];
    ctx.textAlign = 'center';
    ctx.fillStyle = '#aaa';
    ctx.font = '9px monospace';
    ctx.fillText('WEAPON', SCREEN_W / 2, SCREEN_H - 32);
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 12px monospace';
    ctx.fillText(wn[player.weaponIdx], SCREEN_W / 2, SCREEN_H - 14);
    ctx.textAlign = 'left';
    ctx.fillStyle = '#aaa';
    ctx.font = '9px monospace';
    ctx.fillText('KEYS', 14, 18);
    for (let i = 0; i < 3; i++) {
        const kx = 55 + i * 22;
        ctx.fillStyle = i < gameState.keysCollected ? '#ffdd00' : '#333';
        ctx.beginPath();
        ctx.arc(kx, 13, 5, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = i < gameState.keysCollected ? '#ffdd00' : '#555';
        ctx.fillRect(kx - 1, 16, 2, 5);
    }
    ctx.textAlign = 'right';
    ctx.fillStyle = '#aaa';
    ctx.font = '9px monospace';
    ctx.fillText('KILLS', SCREEN_W - 12, 18);
    ctx.fillStyle = '#ff4444';
    ctx.font = 'bold 11px monospace';
    ctx.fillText(`${gameState.kills}`, SCREEN_W - 12, 31);
    ctx.textAlign = 'center';
    for (let i = 0; i < 3; i++) {
        const sx = SCREEN_W / 2 - 75 + i * 75;
        ctx.fillStyle = i === player.weaponIdx ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.3)';
        ctx.fillRect(sx - 28, SCREEN_H - 6, 56, 14);
        ctx.fillStyle = i === player.weaponIdx ? '#fff' : '#555';
        ctx.font = '9px monospace';
        ctx.fillText(`${i + 1}:${wn[i]}`, sx, SCREEN_H + 2);
    }
}
function drawCrosshair(w, h) {
    const cx = w / 2, cy = h / 2;
    ctx.strokeStyle = 'rgba(255,255,255,0.5)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(cx - 7, cy);
    ctx.lineTo(cx - 3, cy);
    ctx.moveTo(cx + 3, cy);
    ctx.lineTo(cx + 7, cy);
    ctx.moveTo(cx, cy - 7);
    ctx.lineTo(cx, cy - 3);
    ctx.moveTo(cx, cy + 3);
    ctx.lineTo(cx, cy + 7);
    ctx.stroke();
}
// =============================================================================
// Game Logic
// =============================================================================
function update(dt) {
    gameState.time += dt;
    const mdx = input.mouseDX;
    input.mouseDX = 0;
    player.angle += mdx * 0.003;
    const rs = 2.5 * dt / 1000;
    if (input.rotateLeft)
        player.angle -= rs;
    if (input.rotateRight)
        player.angle += rs;
    if (input.switchWeapon && performance.now() - input.lastWeaponSwitch > 200) {
        input.switchWeapon = false;
        input.lastWeaponSwitch = performance.now();
        player.weaponIdx = (player.weaponIdx + 1) % 3;
        player.shootAnim = 0;
    }
    if (player.alive) {
        const ms = PLAYER_SPEED * dt / 1000;
        let dx = 0, dy = 0;
        if (input.forward) {
            dx += Math.cos(player.angle) * ms;
            dy += Math.sin(player.angle) * ms;
        }
        if (input.backward) {
            dx -= Math.cos(player.angle) * ms * 0.6;
            dy -= Math.sin(player.angle) * ms * 0.6;
        }
        if (input.strafeLeft) {
            dx += Math.sin(player.angle) * ms;
            dy -= Math.cos(player.angle) * ms;
        }
        if (input.strafeRight) {
            dx -= Math.sin(player.angle) * ms;
            dy += Math.cos(player.angle) * ms;
        }
        if (dx !== 0 || dy !== 0)
            player.bobPhase += dt * 0.008;
        let nx = player.x + dx, ny = player.y + dy;
        if (canMove(nx, player.y))
            player.x = nx;
        if (canMove(player.x, ny))
            player.y = ny;
    }
    handleShoot(dt);
    if (player.shootAnim > 0)
        player.shootAnim -= dt * 0.015;
    if (player.damageFlash > 0)
        player.damageFlash -= dt * 0.003;
    if (!player.alive) {
        player.respawnT -= dt;
        if (player.respawnT <= 0) {
            player.alive = true;
            player.health = 100;
            player.ammo = Math.max(player.ammo, 12);
            player.x = 1.5;
            player.y = 1.5;
            player.angle = 0;
        }
    }
    // Update enemies
    for (const e of enemies) {
        if (e.state === 'dead') {
            e.deathT += dt;
            continue;
        }
        e.attackT += dt;
        if (e.dmgFlash > 0)
            e.dmgFlash -= dt * 0.004;
        if (Math.abs(e.kbX) > 0.005 || Math.abs(e.kbY) > 0.005) {
            const nx = e.x + e.kbX * dt / 16, ny = e.y + e.kbY * dt / 16;
            if (canMove(nx, ny)) {
                e.x = nx;
                e.y = ny;
            }
            e.kbX *= 0.85;
            e.kbY *= 0.85;
            continue;
        }
        const edx = player.x - e.x, edy = player.y - e.y;
        const dist = Math.sqrt(edx * edx + edy * edy);
        const def = ENEMY_DEFS[e.defId];
        if (!def)
            continue;
        e.angle = Math.atan2(edy, edx);
        if (dist > def.atkRange * 2.5)
            e.state = 'idle';
        else if (dist < def.atkRange) {
            e.state = 'attack';
            if (e.attackT >= def.atkCD) {
                e.attackT = 0;
                if (player.alive && player.damageFlash <= 0) {
                    player.health -= def.damage;
                    player.damageFlash = 2;
                    shakeAmount = Math.max(shakeAmount, 6);
                    if (player.health <= 0) {
                        player.health = 0;
                        player.alive = false;
                        player.respawnT = 2500;
                    }
                }
            }
        }
        else {
            e.state = 'chase';
            const spd = def.speed * dt / 1000;
            const nx = e.x + (edx / dist) * spd, ny = e.y + (edy / dist) * spd;
            if (canMove(nx, ny)) {
                e.x = nx;
                e.y = ny;
            }
            else if (canMove(nx, e.y))
                e.x = nx;
            else if (canMove(e.x, ny))
                e.y = ny;
        }
    }
    // Pickups
    for (const p of pickups) {
        if (!p.active)
            continue;
        p.animT += dt * 0.004;
        const dx = player.x - p.x, dy = player.y - p.y;
        if (dx * dx + dy * dy < 0.36) {
            p.active = false;
            switch (p.type) {
                case 'health':
                    player.health = Math.min(player.health + 25, player.maxHealth);
                    break;
                case 'ammo': {
                    const wpn = WEAPONS[player.weaponIdx];
                    player.ammo = wpn && wpn.type === 'rocket'
                        ? Math.min(player.ammo + 1, 12)
                        : Math.min(player.ammo + 12, 99);
                    break;
                }
                case 'key':
                    gameState.keysCollected++;
                    if (gameState.keysCollected >= TOTAL_KEYS) {
                        gameState.gameWon = true;
                        gameState.score = Math.max(0, 10000 - Math.floor(gameState.time / 1000) * 100) + gameState.kills * 200;
                    }
                    break;
            }
        }
    }
    // Projectiles
    const pms = dt / 16;
    for (let i = projectiles.length - 1; i >= 0; i--) {
        const p = projectiles[i];
        p.life -= dt;
        const nx = p.x + p.dx * pms, ny = p.y + p.dy * pms;
        p.trail.push([p.x, p.y]);
        if (p.trail.length > 10)
            p.trail.shift();
        if (isWall(Math.floor(nx), Math.floor(ny))) {
            boom(p.x, p.y, 3);
            projectiles.splice(i, 1);
            continue;
        }
        p.x = nx;
        p.y = ny;
        for (const e of enemies) {
            if (e.state === 'dead')
                continue;
            const edx2 = e.x - p.x, edy2 = e.y - p.y;
            if (Math.sqrt(edx2 * edx2 + edy2 * edy2) < 0.5) {
                e.health -= p.damage;
                e.dmgFlash = 1;
                const a = Math.atan2(e.y - p.y, e.x - p.x);
                e.kbX = Math.cos(a) * p.damage * 0.02;
                e.kbY = Math.sin(a) * p.damage * 0.02;
                boom(p.x, p.y, 3);
                if (e.health <= 0) {
                    e.state = 'dead';
                    e.deathT = 0;
                    gameState.kills++;
                }
                projectiles.splice(i, 1);
                break;
            }
        }
        if (p.life <= 0 && projectiles[i] === p) {
            boom(p.x, p.y, 2);
            projectiles.splice(i, 1);
        }
    }
    // Win check
    if (player.alive && !gameState.gameWon) {
        const edx = player.x - EXIT_POS.x, edy = player.y - EXIT_POS.y;
        if (Math.sqrt(edx * edx + edy * edy) < 0.8 && gameState.keysCollected >= TOTAL_KEYS) {
            gameState.gameWon = true;
            gameState.score = Math.max(0, 10000 - Math.floor(gameState.time / 1000) * 100) + gameState.kills * 200;
        }
    }
}
function handleShoot(dt) {
    const wp = WEAPONS[player.weaponIdx];
    if (!wp || !input.shoot || !player.alive)
        return;
    player.shootT += dt;
    if (player.shootT < wp.fireRate)
        return;
    if (wp.type === 'rocket') {
        if (player.ammo < 1)
            return;
        player.ammo--;
        player.shootT = 0;
        player.shootAnim = 25;
        const spd = 8, spr = (Math.random() - 0.5) * wp.spread;
        const a = player.angle + spr;
        projectiles.push({ x: player.x, y: player.y, dx: Math.cos(a) * spd, dy: Math.sin(a) * spd, damage: wp.damage, life: 2000, trail: [] });
        shakeAmount = Math.max(shakeAmount, 8);
    }
    else {
        if (player.ammo < 1)
            return;
        player.ammo -= 1;
        player.shootT = 0;
        player.shootAnim = 25;
        for (let p = 0; p < wp.pellets; p++) {
            const a = player.angle + (Math.random() - 0.5) * wp.spread * 2;
            const rdx = Math.cos(a), rdy = Math.sin(a);
            let emx = Math.floor(player.x), emy = Math.floor(player.y);
            const sX = rdx > 0 ? 1 : -1, sY = rdy > 0 ? 1 : -1;
            const ddX = Math.abs(1 / rdx), ddY = Math.abs(1 / rdy);
            let sDx = rdx < 0 ? (player.x - emx) * ddX : (emx + 1 - player.x) * ddX;
            let sDy = rdy < 0 ? (player.y - emy) * ddY : (emy + 1 - player.y) * ddY;
            let clE = null, clD = Infinity, wD = Infinity;
            while (true) {
                if (sDx < sDy) {
                    sDx += ddX;
                    emx += sX;
                }
                else {
                    sDy += ddY;
                    emy += sY;
                }
                const cd = Math.max(sDx, sDy);
                if (cd > wD && clE)
                    break;
                if (emx < 0 || emx >= 24 || emy < 0 || emy >= 24) {
                    wD = cd;
                    break;
                }
                if (MAP[emy] && MAP[emy][emx] > 0) {
                    wD = cd;
                    break;
                }
                for (const e of enemies) {
                    if (e.state === 'dead')
                        continue;
                    const edx2 = e.x - player.x, edy2 = e.y - player.y;
                    const dist = Math.sqrt(edx2 * edx2 + edy2 * edy2);
                    if (Math.abs(edy2 * rdx - edx2 * rdy) < 0.3 && dist < clD && dist < 20) {
                        clD = dist;
                        clE = e;
                    }
                }
            }
            if (clE) {
                clE.health -= wp.damage;
                clE.dmgFlash = 1;
                const pa = Math.atan2(clE.y - player.y, clE.x - player.x);
                clE.kbX = Math.cos(pa) * wp.damage * 0.02;
                clE.kbY = Math.sin(pa) * wp.damage * 0.02;
                if (clE.health <= 0) {
                    clE.state = 'dead';
                    clE.deathT = 0;
                    gameState.kills++;
                }
            }
        }
        shakeAmount = Math.max(shakeAmount, wp.type === 'shotgun' ? 5 : 2);
    }
}
function boom(x, y, radius) {
    for (const e of enemies) {
        if (e.state === 'dead')
            continue;
        const edx = e.x - x, edy = e.y - y;
        const dist = Math.sqrt(edx * edx + edy * edy);
        if (dist < radius) {
            e.health -= 30 * (1 - dist / radius);
            e.dmgFlash = 1;
        }
    }
    shakeAmount = Math.max(shakeAmount, 8);
}
// =============================================================================
// Entry Point
// =============================================================================
function start() {
    // Set title
    document.title = 'DOOM-LITE — Retro FPS';
    // Create DOM structure
    document.body.style.margin = '0';
    document.body.style.overflow = 'hidden';
    document.body.style.background = '#000';
    document.body.style.display = 'flex';
    document.body.style.justifyContent = 'center';
    document.body.style.alignItems = 'center';
    document.body.style.height = '100vh';
    // Game container
    const gameContainer = document.createElement('div');
    gameContainer.id = 'game-container';
    gameContainer.style.position = 'relative';
    gameContainer.style.width = '100vw';
    gameContainer.style.height = '100vh';
    gameContainer.style.display = 'flex';
    gameContainer.style.justifyContent = 'center';
    gameContainer.style.alignItems = 'center';
    gameContainer.style.background = '#111';
    document.body.appendChild(gameContainer);
    // Canvas
    const c = document.createElement('canvas');
    c.id = 'game-canvas';
    c.tabIndex = 1;
    c.style.width = '100%';
    c.style.height = '100%';
    c.style.objectFit = 'contain';
    c.style.imageRendering = 'pixelated';
    gameContainer.appendChild(c);
    // Instructions overlay
    const instructions = document.createElement('div');
    instructions.id = 'instructions';
    instructions.style.position = 'absolute';
    instructions.style.top = '50%';
    instructions.style.left = '50%';
    instructions.style.transform = 'translate(-50%, -50%)';
    instructions.style.textAlign = 'center';
    instructions.style.color = '#fff';
    instructions.style.fontFamily = 'monospace';
    instructions.style.zIndex = '10';
    instructions.style.cursor = 'pointer';
    instructions.innerHTML = `
        <h1 style="font-size: 48px; color: #f44; text-shadow: 0 0 20px #f00; margin: 0 0 20px;">🔥 DOOM-LITE 🔥</h1>
        <p style="font-size: 18px; color: #aaa; margin: 8px;">Click to start</p>
        <p style="font-size: 14px; color: #888; margin-top: 20px;">WASD to move • Mouse to look • Q to switch weapon • SPACE to shoot</p>
        <p style="font-size: 12px; color: #666; margin-top: 10px;">Collect 3 keys and reach the exit • Find the keys!</p>
    `;
    gameContainer.appendChild(instructions);
    c.addEventListener('click', () => {
        c.requestPointerLock();
        instructions.style.display = 'none';
    });
    document.addEventListener('pointerlockchange', () => {
        c.style.cursor = document.pointerLockElement === c ? 'none' : 'default';
    });
    // Init and start
    initRender(c);
    initGame();
    let lastTime = performance.now();
    function loop(now) {
        const dt = Math.min(now - lastTime, 50);
        lastTime = now;
        if (gameState.started && !gameState.gameWon)
            update(dt);
        render();
        requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
    // Check win
    setInterval(() => {
        if (gameState.gameWon) {
            const overlay = document.createElement('div');
            overlay.id = 'overlay';
            overlay.style.position = 'absolute';
            overlay.style.top = '0';
            overlay.style.left = '0';
            overlay.style.width = '100%';
            overlay.style.height = '100%';
            overlay.style.display = 'flex';
            overlay.style.justifyContent = 'center';
            overlay.style.alignItems = 'center';
            overlay.style.background = 'rgba(0,0,0,0.8)';
            overlay.style.zIndex = '20';
            overlay.innerHTML = `
                <div style="text-align: center; font-family: monospace;">
                    <h1 style="font-size: 48px; color: #0f0; text-shadow: 0 0 20px #0f0; margin: 0 0 20px;">🎉 LEVEL COMPLETE!</h1>
                    <div style="font-size: 20px; color: #aaa; line-height: 1.8;">
                        <p>Time: ${Math.floor(gameState.time / 1000)}s</p>
                        <p>Kills: ${gameState.kills}</p>
                        <p>Score: ${gameState.score}</p>
                    </div>
                    <button id="restart-btn" style="margin-top: 20px; padding: 12px 24px; font-size: 18px; font-family: monospace; background: #f44; color: #fff; border: 2px solid #f66; cursor: pointer;">PLAY AGAIN</button>
                </div>
            `;
            gameContainer.appendChild(overlay);
            document.getElementById('restart-btn')?.addEventListener('click', () => {
                overlay.remove();
                initGame();
                if (document.pointerLockElement)
                    document.exitPointerLock();
                c.requestPointerLock();
            });
        }
    }, 200);
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"unsloth/Ornith-1.0-35B-GGUF-UD-IQ4_NL","provider":"brigid","persona":"typescript","sessionId":"f4ee86e9-03be-48f3-a5a2-286c7e69baf7","tokensIn":3198049,"tokensOut":128951,"tokensTotal":3327000,"cost":0,"turns":30,"toolCalls":105,"failedToolCalls":7,"timestamp":"2026-08-04T17:05:41.624Z"} -->