← DOOM results

DOOM

Qwen 3.6 27B MTP GGUF · 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 53,839 bytes · SHA-256 604fe74a3707
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Doom</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
#app { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; }
canvas { display: block; image-rendering: pixelated; cursor: none; }
#overlay {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: none; justify-content: center; align-items: center;
  flex-direction: column; z-index: 10; background: rgba(0,0,0,0.8);
  color: #fff; font-family: monospace;
}
#overlay h1 { font-size: 48px; margin-bottom: 16px; }
#overlay p { font-size: 20px; margin-bottom: 8px; }
#overlay button {
  margin-top: 20px; padding: 12px 32px; font-size: 20px;
  font-family: monospace; background: #c00; color: #fff; border: none;
  cursor: pointer; text-transform: uppercase;
}
#overlay button:hover { background: #f00; }
#start-screen {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; justify-content: center; align-items: center;
  flex-direction: column; z-index: 20; background: #000;
  color: #fff; font-family: monospace;
}
#start-screen h1 {
  font-size: 72px; color: #c00; text-shadow: 0 0 20px #f00;
  margin-bottom: 24px; letter-spacing: 8px;
}
#start-screen p { font-size: 18px; margin-bottom: 4px; color: #aaa; }
#start-screen button {
  margin-top: 32px; padding: 16px 48px; font-size: 24px;
  font-family: monospace; background: #c00; color: #fff; border: none;
  cursor: pointer; text-transform: uppercase; animation: pulse 1s infinite;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 10px #c00; }
  50% { box-shadow: 0 0 30px #f00; }
}
</style>
</head>
<body>
<div id="app">
  <canvas id="game"></canvas>
  <div id="overlay">
    <h1 id="overlay-title"></h1>
    <p id="overlay-msg"></p>
    <p id="overlay-score"></p>
    <button id="overlay-btn">Continue</button>
  </div>
  <div id="start-screen">
    <h1>DOOM</h1>
    <p>WASD - Move &nbsp;|&nbsp; Mouse - Aim &nbsp;|&nbsp; Click - Shoot</p>
    <p>1/2/3 - Switch Weapons &nbsp;|&nbsp; R - Reload</p>
    <button id="start-btn">ENTER HELL</button>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./game":"src/game.ts","./input":"src/input.ts","./player":"src/player.ts","./enemies":"src/enemies.ts","./renderer":"src/renderer.ts","./sound":"src/sound.ts"},"src/map.ts":{"./types":"src/types.ts"},"src/raycast.ts":{"./types":"src/types.ts","./map":"src/map.ts"},"src/game.ts":{"./types":"src/types.ts","./map":"src/map.ts"},"src/player.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts","./game":"src/game.ts","./input":"src/input.ts","./sound":"src/sound.ts"},"src/enemies.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts","./game":"src/game.ts","./sound":"src/sound.ts"},"src/renderer.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts"}};
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 });
const types_1 = require("./types");
const game_1 = require("./game");
const input_1 = require("./input");
const player_1 = require("./player");
const enemies_1 = require("./enemies");
const renderer_1 = require("./renderer");
const sound_1 = require("./sound");
let game;
let canvas;
let ctx;
let lastTime = 0;
let animFrameId = 0;
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlay-title');
const overlayMsg = document.getElementById('overlay-msg');
const overlayScore = document.getElementById('overlay-score');
const overlayBtn = document.getElementById('overlay-btn');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
function init() {
    canvas = document.getElementById('game');
    canvas.width = 640;
    canvas.height = 400;
    // Scale canvas to fit window while keeping aspect ratio
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);
    ctx = canvas.getContext('2d');
    game = (0, game_1.createGame)();
    (0, input_1.setupInput)(canvas);
    startBtn.addEventListener('click', startGame);
    overlayBtn.addEventListener('click', handleOverlayButton);
    requestAnimationFrame(gameLoop);
}
function resizeCanvas() {
    const ar = 640 / 400;
    let w = window.innerWidth;
    let h = window.innerHeight;
    if (w / h > ar) {
        w = h * ar;
    }
    else {
        h = w / ar;
    }
    canvas.style.width = `${w}px`;
    canvas.style.height = `${h}px`;
}
function startGame() {
    startScreen.style.display = 'none';
    game.phase = 'playing';
    canvas.requestPointerLock();
}
function handleOverlayButton() {
    if (game.phase === 'dead') {
        (0, game_1.respawnPlayer)(game);
    }
    else if (game.phase === 'won') {
        game = (0, game_1.createGame)();
        game.phase = 'playing';
    }
    overlay.style.display = 'none';
    canvas.requestPointerLock();
}
function update(dt) {
    game.time += dt;
    if (game.phase === 'dead') {
        game.deadTimer--;
        if (game.deadTimer <= 0) {
            overlayTitle.textContent = 'YOU DIED';
            overlayTitle.style.color = '#f00';
            overlayMsg.textContent = `${game.player.kills} enemies killed`;
            overlayScore.textContent = `Score: ${game.player.score}`;
            overlayBtn.textContent = 'RESPAWN';
            overlay.style.display = 'flex';
        }
        return;
    }
    if (game.phase === 'won') {
        if (game.deadTimer > 0) {
            game.deadTimer--;
        }
        else if (game.deadTimer === -1) {
            overlayTitle.textContent = 'LEVEL COMPLETE!';
            overlayTitle.style.color = '#0f0';
            overlayMsg.textContent = `${game.player.kills} enemies eliminated`;
            overlayScore.textContent = `Final Score: ${game.player.score}`;
            overlayBtn.textContent = 'PLAY AGAIN';
            overlay.style.display = 'flex';
            game.deadTimer = -2;
        }
        return;
    }
    // Update player
    (0, player_1.updatePlayer)(game, dt);
    // Update weapons
    for (let i = 0; i < 3; i++) {
        if (i !== game.player.currentWeapon)
            continue;
        (0, player_1.updateWeapon)(game, i);
    }
    // Update enemies
    (0, enemies_1.updateEnemies)(game);
    // Enemy projectiles / damage
    for (const e of game.enemies) {
        if (!e.alive)
            continue;
        // Enemies that are in "attack" range do damage via the enemy system
    }
    // Pickup collection
    const p = game.player;
    for (const pk of game.pickups) {
        if (!pk.active || pk.collected)
            continue;
        const dx = pk.x - p.x;
        const dy = pk.y - p.y;
        if (dx * dx + dy * dy < 0.5) {
            pk.collected = true;
            pk.active = false;
            (0, sound_1.playPickup)();
            if (pk.type === 'health' && p.health < p.maxHealth) {
                p.health = Math.min(p.maxHealth, p.health + 25);
                for (let i = 0; i < 5; i++)
                    (0, game_1.spawnParticle)(game, pk.x, pk.y, '#0f0');
            }
            else if (pk.type === 'ammo_pistol') {
                const w = p.weapons[types_1.WeaponType.Pistol];
                w.reserve = Math.min(w.reserve + 12, 72);
                for (let i = 0; i < 5; i++)
                    (0, game_1.spawnParticle)(game, pk.x, pk.y, '#ff0');
            }
            else if (pk.type === 'ammo_shotgun') {
                // Equip shotgun if not yet
                if (!p.weapons[types_1.WeaponType.Shotgun].equipped) {
                    p.weapons[types_1.WeaponType.Shotgun].equipped = true;
                }
                const w = p.weapons[types_1.WeaponType.Shotgun];
                w.reserve = Math.min(w.reserve + 8, 32);
                for (let i = 0; i < 5; i++)
                    (0, game_1.spawnParticle)(game, pk.x, pk.y, '#f80');
            }
        }
    }
    // Update particles
    for (let i = game.particles.length - 1; i >= 0; i--) {
        const pt = game.particles[i];
        pt.x += pt.vx;
        pt.y += pt.vy;
        pt.life--;
        if (pt.life <= 0)
            game.particles.splice(i, 1);
    }
    // Win condition: all enemies dead
    const aliveCount = game.enemies.filter(e => e.alive).length;
    if (aliveCount === 0 && game.phase === 'playing') {
        game.phase = 'won';
        game.deadTimer = 60; // Delay before showing overlay
    }
}
function gameLoop(timestamp) {
    const dt = Math.min(32, timestamp - lastTime);
    lastTime = timestamp;
    update(dt);
    (0, renderer_1.render)(game, ctx);
    animFrameId = requestAnimationFrame(gameLoop);
}
init();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TILE = exports.HALF_FOV = exports.FOV = exports.EnemyType = exports.WeaponType = void 0;
var WeaponType;
(function (WeaponType) {
    WeaponType[WeaponType["Fist"] = 0] = "Fist";
    WeaponType[WeaponType["Pistol"] = 1] = "Pistol";
    WeaponType[WeaponType["Shotgun"] = 2] = "Shotgun";
})(WeaponType || (exports.WeaponType = WeaponType = {}));
var EnemyType;
(function (EnemyType) {
    EnemyType[EnemyType["Imp"] = 0] = "Imp";
    EnemyType[EnemyType["Demon"] = 1] = "Demon";
    EnemyType[EnemyType["Commander"] = 2] = "Commander";
})(EnemyType || (exports.EnemyType = EnemyType = {}));
exports.FOV = Math.PI / 3;
exports.HALF_FOV = exports.FOV / 2;
exports.TILE = 1;
};

// ── module: src/map.ts ──
__mods["src/map.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAP_HEIGHT = exports.MAP_WIDTH = exports.MAP_LAYOUT = void 0;
exports.createEnemies = createEnemies;
exports.createPickups = createPickups;
const types_1 = require("./types");
// 0=floor, 1=stone, 2=red brick, 3=blue brick, 4=metal, 5=gate(exit)
// Player starts at P, enemies at I, D, C, pickups at H, A
exports.MAP_LAYOUT = [
    [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, 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, 2, 2, 2, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 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, 4, 4, 4, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 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, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 1],
    [1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 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, 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, 5, 1],
];
exports.MAP_WIDTH = exports.MAP_LAYOUT[0].length;
exports.MAP_HEIGHT = exports.MAP_LAYOUT.length;
function createEnemies() {
    const enemies = [];
    const spawns = [
        { x: 5.5, y: 4.5, type: types_1.EnemyType.Imp },
        { x: 10.5, y: 3.5, type: types_1.EnemyType.Imp },
        { x: 18.5, y: 5.5, type: types_1.EnemyType.Demon },
        { x: 15.5, y: 2.5, type: types_1.EnemyType.Imp },
        { x: 7.5, y: 8.5, type: types_1.EnemyType.Commander },
        { x: 12.5, y: 10.5, type: types_1.EnemyType.Imp },
        { x: 3.5, y: 12.5, type: types_1.EnemyType.Demon },
        { x: 20.5, y: 10.5, type: types_1.EnemyType.Imp },
        { x: 10.5, y: 14.5, type: types_1.EnemyType.Commander },
        { x: 4.5, y: 16.5, type: types_1.EnemyType.Imp },
        { x: 16.5, y: 14.5, type: types_1.EnemyType.Demon },
        { x: 21.5, y: 17.5, type: types_1.EnemyType.Imp },
        { x: 8.5, y: 18.5, type: types_1.EnemyType.Commander },
        { x: 14.5, y: 18.5, type: types_1.EnemyType.Demon },
    ];
    for (const s of spawns) {
        const stats = s.type === types_1.EnemyType.Imp
            ? { health: 30, speed: 0.02, damage: 5, fireRate: 60, alertDist: 8 }
            : s.type === types_1.EnemyType.Demon
                ? { health: 60, speed: 0.035, damage: 10, fireRate: 40, alertDist: 10 }
                : { health: 100, speed: 0.015, damage: 15, fireRate: 50, alertDist: 12 };
        enemies.push({
            x: s.x, y: s.y,
            type: s.type,
            health: stats.health,
            maxHealth: stats.health,
            alive: true,
            speed: stats.speed,
            damage: stats.damage,
            fireRate: stats.fireRate,
            fireTimer: Math.floor(Math.random() * stats.fireRate),
            state: 'idle',
            alertDist: stats.alertDist,
            seePlayer: false,
            animFrame: 0,
            animTimer: 0,
            hitFlash: 0,
            deathTimer: 0,
        });
    }
    return enemies;
}
function createPickups() {
    return [
        { x: 3.5, y: 3.5, type: 'health', active: true, collected: false, animPhase: 0 },
        { x: 8.5, y: 3.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
        { x: 14.5, y: 3.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
        { x: 6.5, y: 7.5, type: 'health', active: true, collected: false, animPhase: 0 },
        { x: 12.5, y: 7.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
        { x: 3.5, y: 10.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
        { x: 18.5, y: 14.5, type: 'health', active: true, collected: false, animPhase: 0 },
        { x: 20.5, y: 3.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
        { x: 10.5, y: 18.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
        { x: 15.5, y: 10.5, type: 'health', active: true, collected: false, animPhase: 0 },
        { x: 5.5, y: 14.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
        { x: 22.5, y: 18.5, type: 'health', active: true, collected: false, animPhase: 0 },
    ];
}
};

// ── module: src/raycast.ts ──
__mods["src/raycast.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.canSee = canSee;
exports.isWall = isWall;
const map_1 = require("./map");
function castRay(ox, oy, angle) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    const dx = cos;
    const dy = sin;
    // DDA algorithm
    let mapX = Math.floor(ox);
    let mapY = Math.floor(oy);
    const deltaDistX = Math.abs(1 / (dx || 0.0001));
    const deltaDistY = Math.abs(1 / (dy || 0.0001));
    let stepX, stepY;
    let sideDistX, sideDistY;
    if (dx < 0) {
        stepX = -1;
        sideDistX = (ox - mapX) * deltaDistX;
    }
    else {
        stepX = 1;
        sideDistX = (mapX + 1 - ox) * deltaDistX;
    }
    if (dy < 0) {
        stepY = -1;
        sideDistY = (oy - mapY) * deltaDistY;
    }
    else {
        stepY = 1;
        sideDistY = (mapY + 1 - oy) * deltaDistY;
    }
    let side = 0;
    let maxSteps = 64;
    while (maxSteps-- > 0) {
        if (sideDistX < sideDistY) {
            sideDistX += deltaDistX;
            mapX += stepX;
            side = 0;
        }
        else {
            sideDistY += deltaDistY;
            mapY += stepY;
            side = 1;
        }
        if (mapX < 0 || mapX >= map_1.MAP_WIDTH || mapY < 0 || mapY >= map_1.MAP_HEIGHT) {
            return { dist: 64, wallX: 0, side, wallType: 0 };
        }
        const wallType = map_1.MAP_LAYOUT[mapY][mapX];
        if (wallType > 0) {
            let perpDist;
            if (side === 0) {
                perpDist = (mapX - ox + (1 - stepX) / 2) / (dx || 0.0001);
            }
            else {
                perpDist = (mapY - oy + (1 - stepY) / 2) / (dy || 0.0001);
            }
            const absDist = Math.abs(perpDist);
            // Texture coordinate
            let wallX;
            if (side === 0) {
                wallX = oy + perpDist * (sin / (cos || 0.0001));
            }
            else {
                wallX = ox + perpDist * (cos / (sin || 0.0001));
            }
            wallX -= Math.floor(wallX);
            return { dist: absDist, wallX, side, wallType };
        }
    }
    return { dist: 64, wallX: 0, side, wallType: 0 };
}
function canSee(x1, y1, x2, y2) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    const dist = Math.sqrt(dx * dx + dy * dy);
    const steps = Math.ceil(dist * 4);
    const sx = dx / steps;
    const sy = dy / steps;
    for (let i = 0; i < steps; i++) {
        const cx = x1 + sx * i;
        const cy = y1 + sy * i;
        const mx = Math.floor(cx);
        const my = Math.floor(cy);
        if (mx >= 0 && mx < map_1.MAP_WIDTH && my >= 0 && my < map_1.MAP_HEIGHT) {
            if (map_1.MAP_LAYOUT[my][mx] > 0)
                return false;
        }
    }
    return true;
}
function isWall(x, y) {
    const mx = Math.floor(x);
    const my = Math.floor(y);
    if (mx < 0 || mx >= map_1.MAP_WIDTH || my < 0 || my >= map_1.MAP_HEIGHT)
        return true;
    return map_1.MAP_LAYOUT[my][mx] > 0;
}
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.createGame = createGame;
exports.spawnParticle = spawnParticle;
exports.respawnPlayer = respawnPlayer;
exports.tryEquipShotgun = tryEquipShotgun;
const types_1 = require("./types");
const map_1 = require("./map");
function createPlayer() {
    return {
        x: 1.5,
        y: 1.5,
        angle: 0,
        health: 100,
        maxHealth: 100,
        alive: true,
        score: 0,
        kills: 0,
        weapons: [
            { equipped: true, ammo: Infinity, maxAmmo: Infinity, reserve: 0, fireTimer: 0, reloadTimer: 0, reloading: false },
            { equipped: true, ammo: 12, maxAmmo: 12, reserve: 36, fireTimer: 0, reloadTimer: 0, reloading: false },
            { equipped: false, ammo: 8, maxAmmo: 8, reserve: 16, fireTimer: 0, reloadTimer: 0, reloading: false },
        ],
        currentWeapon: 0,
        damageFlash: 0,
        bobPhase: 0,
    };
}
function createGame() {
    return {
        map: map_1.MAP_LAYOUT,
        mapWidth: map_1.MAP_WIDTH,
        mapHeight: map_1.MAP_HEIGHT,
        player: createPlayer(),
        enemies: (0, map_1.createEnemies)(),
        pickups: (0, map_1.createPickups)(),
        particles: [],
        time: 0,
        phase: 'start',
        deadTimer: 0,
        totalEnemies: 0,
        totalKills: 0,
        levelNum: 1,
    };
}
function spawnParticle(game, x, y, color) {
    const angle = Math.random() * Math.PI * 2;
    const speed = 0.02 + Math.random() * 0.04;
    game.particles.push({
        x, y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed,
        life: 30 + Math.random() * 20,
        maxLife: 50,
        color,
        size: 2 + Math.random() * 3,
    });
}
function respawnPlayer(game) {
    const p = game.player;
    p.x = 1.5;
    p.y = 1.5;
    p.angle = 0;
    p.health = p.maxHealth;
    p.alive = true;
    p.damageFlash = 0;
    // Reset all enemies
    for (const e of game.enemies) {
        if (!e.alive) {
            const spawns = [
                { x: 5.5, y: 4.5, type: types_1.EnemyType.Imp },
                { x: 10.5, y: 3.5, type: types_1.EnemyType.Imp },
                { x: 18.5, y: 5.5, type: types_1.EnemyType.Demon },
                { x: 15.5, y: 2.5, type: types_1.EnemyType.Imp },
                { x: 7.5, y: 8.5, type: types_1.EnemyType.Commander },
                { x: 12.5, y: 10.5, type: types_1.EnemyType.Imp },
                { x: 3.5, y: 12.5, type: types_1.EnemyType.Demon },
                { x: 20.5, y: 10.5, type: types_1.EnemyType.Imp },
                { x: 10.5, y: 14.5, type: types_1.EnemyType.Commander },
                { x: 4.5, y: 16.5, type: types_1.EnemyType.Imp },
                { x: 16.5, y: 14.5, type: types_1.EnemyType.Demon },
                { x: 21.5, y: 17.5, type: types_1.EnemyType.Imp },
                { x: 8.5, y: 18.5, type: types_1.EnemyType.Commander },
                { x: 14.5, y: 18.5, type: types_1.EnemyType.Demon },
            ];
            for (let i = 0; i < spawns.length; i++) {
                if (e === game.enemies[i]) {
                    const s = spawns[i];
                    e.x = s.x;
                    e.y = s.y;
                    e.type = s.type;
                    const stats = s.type === types_1.EnemyType.Imp
                        ? { health: 30, speed: 0.02, damage: 5 }
                        : s.type === types_1.EnemyType.Demon
                            ? { health: 60, speed: 0.035, damage: 10 }
                            : { health: 100, speed: 0.015, damage: 15 };
                    e.health = stats.health;
                    e.maxHealth = stats.health;
                    e.speed = stats.speed;
                    e.damage = stats.damage;
                    e.alive = true;
                    e.deathTimer = 0;
                    break;
                }
            }
        }
    }
    // Respawn collected pickups
    for (const p of game.pickups) {
        if (p.collected) {
            p.collected = false;
            p.active = true;
        }
    }
    game.phase = 'playing';
}
function tryEquipShotgun(game) {
    const w = game.player.weapons[types_1.WeaponType.Shotgun];
    if (w.reserve > 0 || w.ammo < w.maxAmmo) {
        if (w.reserve > 0) {
            const needed = w.maxAmmo - w.ammo;
            const take = Math.min(needed, w.reserve);
            w.ammo += take;
            w.reserve -= take;
        }
    }
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupInput = setupInput;
exports.consumeMouseDX = consumeMouseDX;
exports.consumeMouseClick = consumeMouseClick;
exports.isKeyDown = isKeyDown;
const keys = {};
let mouseDX = 0;
let mouseDown = false;
let mouseClicked = false;
function setupInput(canvas) {
    document.addEventListener('keydown', (e) => {
        keys[e.key.toLowerCase()] = true;
        if (['w', 'a', 's', 'd', ' '].includes(e.key.toLowerCase()))
            e.preventDefault();
    });
    document.addEventListener('keyup', (e) => {
        keys[e.key.toLowerCase()] = false;
    });
    canvas.addEventListener('click', () => {
        if (!document.pointerLockElement) {
            canvas.requestPointerLock();
        }
    });
    document.addEventListener('pointerlockchange', () => {
        // track pointer lock state
    });
    document.addEventListener('mousemove', (e) => {
        if (document.pointerLockElement === canvas) {
            mouseDX += e.movementX;
        }
    });
    document.addEventListener('mousedown', (e) => {
        if (e.button === 0)
            mouseDown = true;
    });
    document.addEventListener('mouseup', (e) => {
        if (e.button === 0)
            mouseDown = false;
    });
}
function consumeMouseDX() {
    const v = mouseDX;
    mouseDX = 0;
    return v;
}
function consumeMouseClick() {
    const v = mouseDown;
    return v;
}
function isKeyDown(key) {
    return !!keys[key.toLowerCase()];
}
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updatePlayer = updatePlayer;
exports.updateWeapon = updateWeapon;
exports.damageEnemy = damageEnemy;
const types_1 = require("./types");
const raycast_1 = require("./raycast");
const game_1 = require("./game");
const input_1 = require("./input");
const sound_1 = require("./sound");
const MOVE_SPEED = 0.06;
const TURN_SPEED = 0.003;
function updatePlayer(game, dt) {
    const p = game.player;
    if (!p.alive)
        return;
    // Mouse look
    const mouseDelta = (0, input_1.consumeMouseDX)();
    p.angle += mouseDelta * TURN_SPEED;
    let moveX = 0;
    let moveY = 0;
    const cos = Math.cos(p.angle);
    const sin = Math.sin(p.angle);
    let moving = false;
    if ((0, input_1.isKeyDown)('w') || (0, input_1.isKeyDown)('arrowup')) {
        moveX += cos * MOVE_SPEED;
        moveY += sin * MOVE_SPEED;
        moving = true;
    }
    if ((0, input_1.isKeyDown)('s') || (0, input_1.isKeyDown)('arrowdown')) {
        moveX -= cos * MOVE_SPEED;
        moveY -= sin * MOVE_SPEED;
        moving = true;
    }
    if ((0, input_1.isKeyDown)('a') || (0, input_1.isKeyDown)('arrowleft')) {
        moveX += sin * MOVE_SPEED;
        moveY -= cos * MOVE_SPEED;
        moving = true;
    }
    if ((0, input_1.isKeyDown)('d') || (0, input_1.isKeyDown)('arrowright')) {
        moveX -= sin * MOVE_SPEED;
        moveY += cos * MOVE_SPEED;
        moving = true;
    }
    // Collision with radius
    const r = 0.2;
    const nx = p.x + moveX;
    const ny = p.y + moveY;
    if (!(0, raycast_1.isWall)(nx - (moveX > 0 ? r : -r), p.y))
        p.x = nx;
    if (!(0, raycast_1.isWall)(p.x, ny - (moveY > 0 ? r : -r)))
        p.y = ny;
    // Head bob
    if (moving) {
        p.bobPhase += 0.15;
    }
    // Damage flash
    if (p.damageFlash > 0)
        p.damageFlash -= 1;
    // Weapon switch
    if ((0, input_1.isKeyDown)('1'))
        p.currentWeapon = types_1.WeaponType.Fist;
    if ((0, input_1.isKeyDown)('2'))
        p.currentWeapon = types_1.WeaponType.Pistol;
    if ((0, input_1.isKeyDown)('3') && p.weapons[types_1.WeaponType.Shotgun].equipped)
        p.currentWeapon = types_1.WeaponType.Shotgun;
    // Reload
    if ((0, input_1.isKeyDown)('r')) {
        const w = p.weapons[p.currentWeapon];
        if (p.currentWeapon === types_1.WeaponType.Pistol && w.reserve > 0 && !w.reloading) {
            w.reloading = true;
            w.reloadTimer = 30;
        }
        if (p.currentWeapon === types_1.WeaponType.Shotgun && (w.reserve > 0 || w.ammo < w.maxAmmo) && !w.reloading) {
            w.reloading = true;
            w.reloadTimer = 45;
        }
    }
    // Shooting
    updateWeapon(game, p.currentWeapon);
}
function updateWeapon(game, weapon) {
    const p = game.player;
    const w = p.weapons[weapon];
    if (!p.alive)
        return;
    // Reload
    if (w.reloading) {
        w.reloadTimer--;
        if (w.reloadTimer <= 0) {
            w.reloading = false;
            if (weapon === types_1.WeaponType.Pistol) {
                const needed = w.maxAmmo - w.ammo;
                const take = Math.min(needed, w.reserve);
                w.ammo += take;
                w.reserve -= take;
            }
            if (weapon === types_1.WeaponType.Shotgun) {
                (0, game_1.tryEquipShotgun)(game);
            }
        }
        return;
    }
    if (w.fireTimer > 0)
        w.fireTimer--;
    if (mouseDown()) {
        if (weapon === types_1.WeaponType.Fist) {
            if (w.fireTimer <= 0) {
                fireFist(game);
                w.fireTimer = 15;
            }
        }
        else if (weapon === types_1.WeaponType.Pistol) {
            if (w.fireTimer <= 0 && w.ammo > 0) {
                firePistol(game);
                w.fireTimer = 12;
                w.ammo--;
            }
        }
        else if (weapon === types_1.WeaponType.Shotgun) {
            if (w.fireTimer <= 0 && w.ammo > 0) {
                fireShotgun(game);
                w.fireTimer = 30;
                w.ammo--;
            }
        }
    }
}
const input_2 = require("./input");
function mouseDown() {
    return (0, input_2.consumeMouseClick)();
}
function fireFist(game) {
    const p = game.player;
    // Melee attack - check enemies in front
    for (const e of game.enemies) {
        if (!e.alive)
            continue;
        const dx = e.x - p.x;
        const dy = e.y - p.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 1.5) {
            const angleToEnemy = Math.atan2(dy, dx);
            let angleDiff = angleToEnemy - p.angle;
            while (angleDiff > Math.PI)
                angleDiff -= Math.PI * 2;
            while (angleDiff < -Math.PI)
                angleDiff += Math.PI * 2;
            if (Math.abs(angleDiff) < 0.4) {
                damageEnemy(game, e, 15);
                (0, sound_1.playFist)();
                // Knockback
                const kb = 0.5;
                e.x += Math.cos(angleToEnemy) * kb;
                e.y += Math.sin(angleToEnemy) * kb;
            }
        }
    }
}
function firePistol(game) {
    (0, sound_1.playShoot)();
    const p = game.player;
    const hits = findEnemiesInCone(game, p.x, p.y, p.angle, 0.03, 20);
    for (const hit of hits) {
        damageEnemy(game, hit.enemy, 20 + Math.floor(Math.random() * 10));
        for (let i = 0; i < 3; i++) {
            (0, game_1.spawnParticle)(game, p.x, p.y, '#ff0');
        }
    }
    // Muzzle flash even if no hit
    for (let i = 0; i < 3; i++) {
        (0, game_1.spawnParticle)(game, p.x, p.y, '#ff0');
    }
}
function fireShotgun(game) {
    (0, sound_1.playShotgun)();
    const p = game.player;
    // Spread shot - multiple pellets
    for (let i = 0; i < 5; i++) {
        const spread = (Math.random() - 0.5) * 0.08;
        const hits = findEnemiesInCone(game, p.x, p.y, p.angle + spread, 0.02, 15);
        for (const hit of hits) {
            damageEnemy(game, hit.enemy, 12 + Math.floor(Math.random() * 8));
        }
    }
    // Muzzle flash
    for (let i = 0; i < 8; i++) {
        (0, game_1.spawnParticle)(game, p.x, p.y, '#f80');
    }
}
function findEnemiesInCone(game, px, py, angle, cone, maxDist) {
    const hits = [];
    const sorted = [...game.enemies]
        .filter(e => e.alive)
        .map(e => ({ enemy: e, dist: Math.sqrt((e.x - px) ** 2 + (e.y - py) ** 2) }))
        .sort((a, b) => a.dist - b.dist);
    for (const hit of sorted) {
        if (hit.dist > maxDist)
            continue;
        const dx = hit.enemy.x - px;
        const dy = hit.enemy.y - py;
        let angleToEnemy = Math.atan2(dy, dx) - angle;
        while (angleToEnemy > Math.PI)
            angleToEnemy -= Math.PI * 2;
        while (angleToEnemy < -Math.PI)
            angleToEnemy += Math.PI * 2;
        if (Math.abs(angleToEnemy) < cone) {
            if ((0, raycast_1.canSee)(px, py, hit.enemy.x, hit.enemy.y)) {
                hits.push(hit);
            }
        }
    }
    return hits;
}
function damageEnemy(game, enemy, damage) {
    enemy.health -= damage;
    enemy.hitFlash = 8;
    if (enemy.health <= 0) {
        enemy.alive = false;
        enemy.deathTimer = 30;
        game.player.score += enemy.type === types_1.EnemyType.Commander ? 300 :
            enemy.type === types_1.EnemyType.Demon ? 200 : 100;
        game.player.kills++;
        game.totalKills++;
        (0, sound_1.playEnemyDie)();
        // Blood particles
        for (let i = 0; i < 10; i++) {
            (0, game_1.spawnParticle)(game, enemy.x, enemy.y, '#c00');
        }
    }
}
};

// ── module: src/enemies.ts ──
__mods["src/enemies.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateEnemies = updateEnemies;
exports.damagePlayer = damagePlayer;
const raycast_1 = require("./raycast");
const game_1 = require("./game");
const sound_1 = require("./sound");
const ATTACK_RANGE = 1.5;
function updateEnemies(game) {
    const p = game.player;
    if (!p.alive)
        return;
    for (const e of game.enemies) {
        if (!e.alive) {
            if (e.deathTimer > 0)
                e.deathTimer--;
            continue;
        }
        // Hit flash decay
        if (e.hitFlash > 0)
            e.hitFlash--;
        // Animation
        e.animTimer++;
        if (e.animTimer > 10) {
            e.animTimer = 0;
            e.animFrame = (e.animFrame + 1) % 2;
        }
        const dx = p.x - e.x;
        const dy = p.y - e.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        // Line of sight check
        e.seePlayer = dist < e.alertDist && (0, raycast_1.canSee)(e.x, e.y, p.x, p.y);
        // State machine
        if (e.seePlayer) {
            e.state = dist < ATTACK_RANGE ? 'attack' : 'chase';
        }
        else if (!e.seePlayer && dist < e.alertDist * 0.3) {
            // Still chase if very close
            e.state = 'chase';
        }
        switch (e.state) {
            case 'chase':
                chasePlayer(game, e, dist, dx, dy);
                break;
            case 'attack':
                meleeAttack(game, e, dist);
                break;
        }
    }
}
function chasePlayer(game, e, dist, dx, dy) {
    if (dist < 0.8)
        return;
    const angle = Math.atan2(dy, dx);
    const moveX = Math.cos(angle) * e.speed;
    const moveY = Math.sin(angle) * e.speed;
    const r = 0.25;
    const nx = e.x + moveX;
    const ny = e.y + moveY;
    if (!(0, raycast_1.isWall)(nx - (moveX > 0 ? r : -r), e.y))
        e.x = nx;
    if (!(0, raycast_1.isWall)(e.x, ny - (moveY > 0 ? r : -r)))
        e.y = ny;
    // Try to shoot
    e.fireTimer--;
    if (e.fireTimer <= 0 && dist < e.alertDist * 0.8) {
        e.fireTimer = e.fireRate + Math.floor(Math.random() * 20);
        const p = game.player;
        const hitChance = Math.max(0.12, 0.6 - dist * 0.04);
        (0, sound_1.playEnemyShoot)();
        if (Math.random() < hitChance) {
            damagePlayer(game, e.damage);
        }
        for (let i = 0; i < 3; i++) {
            (0, game_1.spawnParticle)(game, e.x, e.y, '#f80');
        }
    }
}
function meleeAttack(game, e, dist) {
    e.fireTimer--;
    if (e.fireTimer <= 0 && dist < ATTACK_RANGE + 0.5) {
        e.fireTimer = e.fireRate;
        damagePlayer(game, e.damage);
        for (let i = 0; i < 5; i++) {
            (0, game_1.spawnParticle)(game, e.x, e.y, '#f80');
        }
    }
    else if (dist > ATTACK_RANGE + 1) {
        e.state = 'chase';
    }
}
function damagePlayer(game, damage) {
    const p = game.player;
    if (!p.alive)
        return;
    p.health -= damage;
    p.damageFlash = 10;
    if (p.health <= 0) {
        p.health = 0;
        p.alive = false;
        game.phase = 'dead';
        game.deadTimer = 120;
        (0, sound_1.playDeath)();
    }
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.render = render;
const types_1 = require("./types");
const raycast_1 = require("./raycast");
const W = 640;
const H = 400;
function render(game, ctx) {
    const p = game.player;
    const bob = Math.sin(p.bobPhase) * 4;
    ctx.imageSmoothingEnabled = false;
    // Sky
    const sky = ctx.createLinearGradient(0, 0, 0, H / 2 + bob);
    sky.addColorStop(0, '#1a0500');
    sky.addColorStop(1, '#4a1000');
    ctx.fillStyle = sky;
    ctx.fillRect(0, 0, W, H / 2 + bob);
    // Floor
    const flr = ctx.createLinearGradient(0, H / 2 + bob, 0, H);
    flr.addColorStop(0, '#2a2a2a');
    flr.addColorStop(1, '#111');
    ctx.fillStyle = flr;
    ctx.fillRect(0, H / 2 + bob, W, H / 2 - bob);
    const zbuf = new Float64Array(W);
    // ── Walls ──
    for (let x = 0; x < W; x++) {
        const rayAngle = p.angle - Math.PI / 6 + (x / W) * (Math.PI / 3);
        const hit = (0, raycast_1.castRay)(p.x, p.y, rayAngle);
        const perpDist = hit.dist;
        zbuf[x] = perpDist;
        const lineH = Math.min(H * 3, H / (perpDist || 0.01));
        const ds = Math.max(0, (H - lineH) / 2 + bob);
        const de = Math.min(H, (H + lineH) / 2 + bob);
        const shade = Math.max(0.12, 1 - perpDist / 16);
        const wc = wallColor(hit.wallType, hit.side, shade, hit.wallX);
        ctx.fillStyle = wc;
        ctx.fillRect(x, ds, 1, de - ds);
        // Top edge highlight
        ctx.fillStyle = lighten(wc, 0.15);
        ctx.fillRect(x, ds, 1, Math.max(1, lineH * 0.02));
    }
    // ── Sprites ──
    renderSprites(game, p, zbuf, bob, ctx);
    // ── Weapon ──
    renderWeapon(game, p, bob, ctx);
    // ── HUD ──
    renderHUD(game, p, ctx);
    // ── Damage flash ──
    if (p.damageFlash > 0) {
        ctx.fillStyle = `rgba(255,0,0,${p.damageFlash / 18})`;
        ctx.fillRect(0, 0, W, H);
    }
    // ── Crosshair ──
    ctx.strokeStyle = '#0f0';
    ctx.lineWidth = 1;
    const cx = W / 2, cy = H / 2;
    ctx.beginPath();
    ctx.moveTo(cx - 7, cy);
    ctx.lineTo(cx - 2, cy);
    ctx.moveTo(cx + 2, cy);
    ctx.lineTo(cx + 7, cy);
    ctx.moveTo(cx, cy - 7);
    ctx.lineTo(cx, cy - 2);
    ctx.moveTo(cx, cy + 2);
    ctx.lineTo(cx, cy + 7);
    ctx.stroke();
}
function renderSprites(g, p, zbuf, bob, ctx) {
    const list = [];
    for (const e of g.enemies) {
        if (!e.alive && e.deathTimer <= 0)
            continue;
        list.push({ x: e.x, y: e.y, d: dist(p, e), tag: 'e', data: e });
    }
    for (const pk of g.pickups) {
        if (!pk.active || pk.collected)
            continue;
        list.push({ x: pk.x, y: pk.y, d: dist(p, pk), tag: 'p', data: pk });
    }
    list.sort((a, b) => b.d - a.d);
    for (const s of list) {
        const dx = s.x - p.x, dy = s.y - p.y;
        let ang = Math.atan2(dy, dx) - p.angle;
        while (ang > Math.PI)
            ang -= Math.PI * 2;
        while (ang < -Math.PI)
            ang += Math.PI * 2;
        if (Math.abs(ang) > Math.PI / 3)
            continue;
        const sx = (0.5 + ang / (Math.PI / 3)) * W;
        const pd = s.d * Math.cos(ang);
        if (pd < 0.2)
            continue;
        const sh = Math.min(H, H / pd);
        if (s.tag === 'e')
            drawEnemy(s.data, sx, sh, pd, zbuf, bob, ctx);
        else
            drawPickup(s.data, sx, sh, pd, zbuf, bob, g, ctx);
    }
    // particles
    for (const pt of g.particles) {
        const dx = pt.x - p.x, dy = pt.y - p.y;
        let ang = Math.atan2(dy, dx) - p.angle;
        while (ang > Math.PI)
            ang -= Math.PI * 2;
        while (ang < -Math.PI)
            ang += Math.PI * 2;
        if (Math.abs(ang) > Math.PI / 3)
            continue;
        const sx = (0.5 + ang / (Math.PI / 3)) * W;
        const pd = Math.sqrt(dx * dx + dy * dy) * Math.cos(ang);
        if (pd < 0.1)
            continue;
        const sz = pt.size * H / pd;
        const sy = H / 2 + bob;
        ctx.globalAlpha = pt.life / pt.maxLife;
        ctx.fillStyle = pt.color;
        ctx.fillRect(sx - sz / 2, sy - sz / 2, sz, sz);
        ctx.globalAlpha = 1;
    }
}
function drawEnemy(e, sx, sh, d, zbuf, bob, ctx) {
    const sw = sh * 0.6;
    const x0 = Math.max(0, Math.floor(sx - sw / 2));
    const x1 = Math.min(W, Math.floor(sx + sw / 2));
    const baseY = Math.floor(H / 2 + sh * 0.05 + bob);
    const topY = Math.max(0, baseY - sh * 0.65);
    const botY = Math.min(H, baseY + sh * 0.35);
    const shade = Math.max(0.2, 1 - d / 15);
    const deadScale = e.deathTimer > 0 ? (1 - e.deathTimer / 30) * 0.5 + 0.5 : 1;
    const hTop = Math.floor(topY + (botY - topY) * (1 - deadScale) * 0.5);
    for (let s = x0; s < x1; s++) {
        if (d >= zbuf[s])
            continue;
        const t = (s - x0) / (x1 - x0);
        const c = 0.5, bw = 0.35;
        if (Math.abs(t - c) > bw)
            continue;
        const col = e.hitFlash > 0 ? [255, 255, 255] :
            !e.alive ? [80, 30, 30] :
                e.type === types_1.EnemyType.Imp ? [160, 80, 40] :
                    e.type === types_1.EnemyType.Demon ? [170, 40, 40] :
                        [60, 100, 60];
        const r = Math.floor(col[0] * shade);
        const gn = Math.floor(col[1] * shade);
        const b = Math.floor(col[2] * shade);
        // Head area
        const headEnd = hTop + (botY - hTop) * 0.3;
        const headW = bw * 0.65;
        if (Math.abs(t - c) < headW && hTop < headEnd) {
            ctx.fillStyle = `rgb(${Math.floor(r * 1.1)},${Math.floor(gn * 1.1)},${Math.floor(b * 1.1)})`;
            ctx.fillRect(s, hTop, 1, headEnd - hTop);
            // Eyes
            if (e.alive && (Math.abs(t - c + 0.08) < 0.03 || Math.abs(t - c - 0.08) < 0.03)) {
                const eyeCol = e.type === types_1.EnemyType.Commander ? '255,40,40' : '255,255,0';
                ctx.fillStyle = `rgb(${eyeCol})`;
                ctx.fillRect(s, hTop + (headEnd - hTop) * 0.5, 1, headEnd - hTop);
            }
        }
        // Body
        if (headEnd < botY) {
            ctx.fillStyle = `rgb(${r},${gn},${b})`;
            ctx.fillRect(s, headEnd, 1, botY - headEnd);
        }
        // Health bar (above sprite, only visible close)
        if (e.alive && shade > 0.3) {
            const hbW = sw * 0.5;
            const hbX = sx - hbW / 2;
            const hbY = hTop - 6;
            if (s >= Math.floor(hbX) && s <= Math.ceil(hbX + hbW)) {
                const lt = (s - hbX) / hbW;
                ctx.fillStyle = '#400';
                ctx.fillRect(s, hbY, 1, 3);
                if (lt < e.health / e.maxHealth) {
                    const hp = e.health / e.maxHealth;
                    ctx.fillStyle = hp > 0.5 ? '#0c0' : hp > 0.25 ? '#cc0' : '#f00';
                    ctx.fillRect(s, hbY, 1, 3);
                }
            }
        }
    }
}
function drawPickup(pk, sx, sh, d, zbuf, bob, g, ctx) {
    const sz = Math.max(4, sh * 0.2);
    const px = sz * 0.4;
    const bOff = Math.sin(g.time * 0.08 + pk.x * 3) * sz * 0.15;
    const cy = H / 2 + sh * 0.12 + bob + bOff;
    const shade = Math.max(0.3, 1 - d / 12);
    const col = pk.type === 'health' ? [0, 255, 0] :
        pk.type === 'ammo_pistol' ? [255, 255, 0] :
            [255, 140, 0];
    const x0 = Math.max(0, Math.floor(sx - px));
    const x1 = Math.min(W, Math.floor(sx + px));
    const y0 = Math.max(0, Math.floor(cy - sz / 2));
    const y1 = Math.min(H, Math.floor(cy + sz / 2));
    for (let s = x0; s < x1; s++) {
        if (d >= zbuf[s])
            continue;
        ctx.fillStyle = `rgb(${Math.floor(col[0] * shade)},${Math.floor(col[1] * shade)},${Math.floor(col[2] * shade)})`;
        ctx.fillRect(s, y0, 1, y1 - y0);
    }
    // Glow ring
    ctx.globalAlpha = 0.2;
    ctx.fillStyle = `rgb(${col[0]},${col[1]},${col[2]})`;
    ctx.fillRect(x0 - 3, y0 - 3, (x1 - x0) + 6, (y1 - y0) + 6);
    ctx.globalAlpha = 1;
    // Label
    if (shade > 0.5) {
        ctx.font = `${Math.max(8, Math.floor(sz * 0.8))}px monospace`;
        ctx.textAlign = 'center';
        ctx.fillStyle = '#fff';
        const lbl = pk.type === 'health' ? '+' : pk.type === 'ammo_pistol' ? 'P' : 'S';
        ctx.fillText(lbl, sx, cy + sz * 0.35);
        ctx.textAlign = 'left';
    }
}
// ── Weapon ──
function renderWeapon(g, p, bob, ctx) {
    const wep = p.currentWeapon;
    const w = p.weapons[wep];
    const cx = W / 2;
    const wbx = Math.sin(p.bobPhase) * 8;
    const wby = Math.abs(Math.cos(p.bobPhase)) * 5;
    let fireKick = 0;
    if (w.fireTimer > (wep === types_1.WeaponType.Shotgun ? 20 : 5))
        fireKick = -18;
    let reloadOff = 0;
    if (w.reloading)
        reloadOff = Math.sin(w.reloadTimer * 0.2) * 40;
    const bx = cx + wbx;
    const by = H - 80 + wby + fireKick + reloadOff;
    ctx.save();
    if (wep === types_1.WeaponType.Fist) {
        ctx.fillStyle = '#b87';
        ctx.fillRect(bx - 18, by - 15, 36, 55);
        ctx.fillStyle = '#965';
        ctx.fillRect(bx - 22, by + 40, 44, 35);
    }
    else if (wep === types_1.WeaponType.Pistol) {
        ctx.fillStyle = '#444';
        ctx.fillRect(bx - 9, by - 5, 18, 55);
        ctx.fillStyle = '#333';
        ctx.fillRect(bx - 7, by + 50, 14, 30);
        ctx.fillStyle = '#222';
        ctx.fillRect(bx - 5, by - 35, 10, 30);
        if (w.fireTimer > 10) {
            ctx.fillStyle = '#ff0';
            ctx.beginPath();
            ctx.arc(bx, by - 40, 14, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#f80';
            ctx.beginPath();
            ctx.arc(bx, by - 40, 7, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    else {
        ctx.fillStyle = '#444';
        ctx.fillRect(bx - 14, by - 10, 28, 60);
        ctx.fillStyle = '#543';
        ctx.fillRect(bx - 10, by + 50, 20, 30);
        ctx.fillStyle = '#333';
        ctx.fillRect(bx - 7, by - 55, 14, 45);
        ctx.fillStyle = '#654';
        ctx.fillRect(bx - 12, by + 12, 24, 8);
        if (w.fireTimer > 22) {
            ctx.fillStyle = '#f80';
            ctx.beginPath();
            ctx.arc(bx, by - 60, 22, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#ff0';
            ctx.beginPath();
            ctx.arc(bx, by - 60, 12, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    ctx.restore();
}
// ── HUD ──
function renderHUD(g, p, ctx) {
    const barH = 40;
    ctx.fillStyle = 'rgba(0,0,0,0.75)';
    ctx.fillRect(0, H - barH, W, barH);
    ctx.fillStyle = '#555';
    ctx.fillRect(0, H - barH, W, 2);
    ctx.font = 'bold 16px monospace';
    ctx.textAlign = 'left';
    // Health
    ctx.fillStyle = p.health > 50 ? '#0f0' : p.health > 25 ? '#ff0' : '#f00';
    ctx.fillText(`♥ ${p.health}`, 10, H - 14);
    // Weapon name & ammo
    const w = p.weapons[p.currentWeapon];
    const names = ['FIST', 'PISTOL', 'SHOTGUN'];
    ctx.fillStyle = '#999';
    ctx.font = '12px monospace';
    ctx.fillText(names[p.currentWeapon], 100, H - 26);
    ctx.fillStyle = w.ammo === Infinity ? '#fff' : w.ammo > 0 ? '#ff0' : '#f00';
    ctx.font = 'bold 16px monospace';
    ctx.fillText(w.ammo === Infinity ? '∞' : `${w.ammo} / ${w.reserve}`, 100, H - 8);
    // Score & kills
    ctx.textAlign = 'center';
    ctx.fillStyle = '#fff';
    ctx.fillText(`SCORE: ${p.score}`, W / 2, H - 14);
    ctx.fillStyle = '#a88';
    ctx.font = '11px monospace';
    ctx.fillText(`KILLS: ${p.kills}`, W / 2, H - 28);
    // Enemies alive
    const alive = g.enemies.filter(e => e.alive).length;
    ctx.fillStyle = alive === 0 ? '#0f0' : '#f88';
    ctx.fillText(`ENEMIES: ${alive}`, W / 2, H - 8);
    // Weapon slots
    ctx.textAlign = 'left';
    ctx.font = '10px monospace';
    for (let i = 0; i < 3; i++) {
        const sx = W - 135 + i * 42;
        ctx.fillStyle = i === p.currentWeapon ? '#c00' : '#333';
        ctx.fillRect(sx, H - barH - 22, 34, 18);
        ctx.fillStyle = '#fff';
        ctx.fillText(`${i + 1}: ${['F', 'P', 'S'][i]}`, sx + 4, H - barH - 9);
    }
    // Minimap
    drawMinimap(g, ctx);
}
function drawMinimap(g, ctx) {
    const ms = 3; // tile size in pixels
    const mw = g.mapWidth * ms;
    const mh = g.mapHeight * ms;
    const mx = W - mw - 8;
    const my = 8;
    ctx.globalAlpha = 0.6;
    ctx.fillStyle = '#000';
    ctx.fillRect(mx - 2, my - 2, mw + 4, mh + 4);
    for (let y = 0; y < g.mapHeight; y++) {
        for (let x = 0; x < g.mapWidth; x++) {
            const t = g.map[y][x];
            if (t > 0) {
                ctx.fillStyle = t === 5 ? '#0f0' : t === 4 ? '#888' : '#555';
                ctx.fillRect(mx + x * ms, my + y * ms, ms, ms);
            }
        }
    }
    // Enemies
    for (const e of g.enemies) {
        if (!e.alive)
            continue;
        ctx.fillStyle = '#f00';
        ctx.fillRect(mx + e.x * ms - 1, my + e.y * ms - 1, 2, 2);
    }
    // Player
    ctx.fillStyle = '#0f0';
    ctx.fillRect(mx + g.player.x * ms - 2, my + g.player.y * ms - 2, 4, 4);
    // Direction indicator
    ctx.strokeStyle = '#0f0';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(mx + g.player.x * ms, my + g.player.y * ms);
    ctx.lineTo(mx + (g.player.x + Math.cos(g.player.angle) * 1.5) * ms, my + (g.player.y + Math.sin(g.player.angle) * 1.5) * ms);
    ctx.stroke();
    ctx.globalAlpha = 1;
}
// ── Helpers ──
function dist(a, b) {
    return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
function wallColor(type, side, shade, wx) {
    const [r0, g0, b0] = type === 1 ? [120, 120, 120] :
        type === 2 ? [130, 60, 40] :
            type === 3 ? [40, 50, 120] :
                type === 4 ? [90, 90, 90] :
                    type === 5 ? [20, 120, 20] :
                        [200, 0, 0];
    let s = shade;
    if (side)
        s *= 0.75;
    // Brick pattern
    if (type <= 3) {
        if (Math.abs(wx - 0.5) < 0.04 || Math.abs(wx) < 0.04)
            s *= 0.82;
    }
    if (type === 4) {
        if (Math.abs(wx - 0.5) < 0.1)
            s *= 0.9;
    }
    return `rgb(${Math.floor(r0 * s)},${Math.floor(g0 * s)},${Math.floor(b0 * s)})`;
}
function lighten(hex, amt) {
    // Parse rgb(r,g,b) or rgb()
    const m = hex.match(/(\d+)/g);
    if (!m)
        return hex;
    const r = Math.min(255, +m[0] + 60 * amt);
    const g = Math.min(255, +m[1] + 60 * amt);
    const b = Math.min(255, +m[2] + 60 * amt);
    return `rgb(${r},${g},${b})`;
}
};

// ── module: src/sound.ts ──
__mods["src/sound.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.playShoot = playShoot;
exports.playShotgun = playShotgun;
exports.playFist = playFist;
exports.playHit = playHit;
exports.playPickup = playPickup;
exports.playDeath = playDeath;
exports.playEnemyDie = playEnemyDie;
exports.playEnemyShoot = playEnemyShoot;
// Procedural sound effects using Web Audio API
let audioCtx = null;
function ensureAudioCtx() {
    if (!audioCtx) {
        audioCtx = new AudioContext();
    }
    return audioCtx;
}
function playNoise(ctx, duration, vol, freq, type = 'square') {
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.type = type;
    osc.frequency.setValueAtTime(freq, ctx.currentTime);
    gain.gain.setValueAtTime(vol, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
    osc.connect(gain);
    gain.connect(ctx.destination);
    osc.start();
    osc.stop(ctx.currentTime + duration);
}
function playNoiseBurst(ctx, duration, vol) {
    const bufferSize = ctx.sampleRate * duration;
    const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) {
        data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
    }
    const source = ctx.createBufferSource();
    source.buffer = buffer;
    const gain = ctx.createGain();
    gain.gain.setValueAtTime(vol, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
    source.connect(gain);
    gain.connect(ctx.destination);
    source.start();
}
function playShoot() {
    const ctx = ensureAudioCtx();
    playNoiseBurst(ctx, 0.1, 0.3);
    playNoise(ctx, 0.08, 0.2, 150, 'sawtooth');
}
function playShotgun() {
    const ctx = ensureAudioCtx();
    playNoiseBurst(ctx, 0.2, 0.5);
    playNoise(ctx, 0.15, 0.3, 80, 'sawtooth');
}
function playFist() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.06, 0.2, 200, 'square');
}
function playHit() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.1, 0.15, 300, 'sawtooth');
}
function playPickup() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.1, 0.2, 800, 'sine');
    setTimeout(() => {
        const c = ensureAudioCtx();
        playNoise(c, 0.1, 0.15, 1200, 'sine');
    }, 80);
}
function playDeath() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.5, 0.3, 200, 'sawtooth');
    playNoise(ctx, 0.4, 0.2, 100, 'square');
}
function playEnemyDie() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.3, 0.2, 150, 'sawtooth');
    playNoise(ctx, 0.2, 0.15, 80, 'square');
}
function playEnemyShoot() {
    const ctx = ensureAudioCtx();
    playNoise(ctx, 0.15, 0.15, 400, 'square');
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"63126259-e86b-45e2-bf54-0d10de1a55d0","tokensIn":1350853,"tokensOut":32387,"tokensTotal":1383240,"cost":0,"turns":41,"toolCalls":46,"failedToolCalls":3,"timestamp":"2026-08-02T03:38:09.838Z"} -->