← DOOM results

DOOM

thinkingcap qwen3.6 27b · 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 64,091 bytes · SHA-256 655e483d83ef
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DOOM-LIKE Retro FPS</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Courier New', monospace; user-select: none; }
#game-container { position: relative; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; }
#game-canvas { image-rendering: pixelated; background: #000; cursor: crosshair; max-width: 100%; max-height: calc(100% - 80px); }
/* ── css: hud ── */
#hud { position: absolute; bottom: 0; left: 0; right: 0; height: 80px; background: linear-gradient(to top, #1a1a2e, transparent); display: flex; justify-content: space-between; align-items: center; padding: 10px 30px; pointer-events: none; z-index: 10; }
#health-bar { color: #ff4444; font-size: 28px; font-weight: bold; text-shadow: 0 0 10px #ff0000; display: flex; align-items: center; gap: 8px; }
#ammo-display { color: #ffaa00; font-size: 24px; font-weight: bold; text-shadow: 0 0 10px #ff6600; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
#minimap-container { position: absolute; top: 10px; right: 10px; border: 2px solid #444; z-index: 10; pointer-events: none; }
#minimap { background: rgba(0,0,0,0.7); }
#overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.85); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 20; }
#overlay-title { color: #ff4444; font-size: 64px; font-weight: bold; text-shadow: 0 0 30px #ff0000; margin-bottom: 20px; }
#overlay-subtitle { color: #aaa; font-size: 24px; text-align: center; line-height: 1.6; }
#start-screen { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(ellipse at center, #1a0a0a 0%, #000 70%); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 30; cursor: pointer; }
</style>
</head>
<body>
<div id="game-container">
  <canvas id="game-canvas"></canvas>
  <div id="hud">
    <div id="health-bar"><span id="health-icon">❤️</span><span id="health-val">100</span></div>
    <div id="ammo-display"><span id="weapon-name">PISTOL</span><span id="ammo-val">50</span></div>
    <div id="minimap-container"><canvas id="minimap"></canvas></div>
  </div>
  <div id="overlay" style="display:none;">
    <div id="overlay-title"></div>
    <div id="overlay-subtitle"></div>
  </div><div id="start-screen">
  <div style="font-size:48px;color:#ff2200;text-shadow:0 0 30px #ff0000,0 0 60px #aa0000;margin-bottom:10px;font-weight:bold;">SHADOW DUNGEON</div>
  <div style="font-size:18px;color:#cc4400;text-shadow:0 0 15px #ff4400;margin-bottom:30px;">A Retro FPS Adventure</div>
  <div style="color:#aaa;font-size:16px;line-height:2;text-align:center;">
    WASD — Move &nbsp;|&nbsp; Mouse — Look<br>
    Click / Space — Shoot &nbsp;|&nbsp; 1/2/3 — Weapons<br>
    R — Respawn (when dead) &nbsp;|&nbsp; Enter — Next Level<br><br>
    <span style="color:#ff6600;font-size:20px;">Click to Start</span>
  </div>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./renderer":"src/renderer.ts","./game":"src/game.ts","./hud":"src/hud.ts","./minimap":"src/minimap.ts","./input":"src/input.ts"},"src/maps.ts":{"./types":"src/types.ts"},"src/state.ts":{"./types":"src/types.ts","./maps":"src/maps.ts"},"src/raycaster.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts","./raycaster":"src/raycaster.ts"},"src/game.ts":{"./types":"src/types.ts","./raycaster":"src/raycaster.ts","./input":"src/input.ts","./sound":"src/sound.ts"},"src/minimap.ts":{"./types":"src/types.ts"},"src/hud.ts":{"./types":"src/types.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";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const state_1 = require("./state");
const renderer_1 = require("./renderer");
const game_1 = require("./game");
const hud_1 = require("./hud");
const minimap_1 = require("./minimap");
const input_1 = require("./input");
let state = (0, state_1.initGameState)(0);
let lastTime = performance.now();
let gameStarted = false;
// Start screen handling
const startScreen = document.getElementById('start-screen');
function startGame() {
    if (!gameStarted) {
        gameStarted = true;
        startScreen.style.display = 'none';
        (0, input_1.requestPointerLock)();
    }
}
startScreen.addEventListener('click', startGame);
window.addEventListener('keydown', e => {
    if (e.code === 'Enter' || e.code === 'Space')
        startGame();
});
// Handle game-over actions
function handleOverlayInput() {
    if (!state.player.alive && !state.won) {
        if (input_1.keys['KeyR']) {
            (0, game_1.respawnPlayer)(state);
        }
    }
    else if (state.won) {
        if (input_1.keys['Enter'] || input_1.keys['NumpadEnter']) {
            Promise.resolve().then(() => __importStar(require('./state'))).then(mod => {
                const newState = mod.initGameState(state.level + 1);
                Object.assign(state, newState);
            });
        }
    }
}
function gameLoop(now) {
    const dt = Math.min(0.05, (now - lastTime) / 1000);
    lastTime = now;
    if (gameStarted) {
        handleOverlayInput();
        (0, game_1.update)(state, dt);
        (0, renderer_1.render)(state);
        (0, hud_1.updateHUD)(state);
        (0, minimap_1.renderMinimap)(state);
    }
    else {
        // Render a dark scene behind the start screen
        (0, renderer_1.render)(state);
    }
    requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// Core types for the FPS game
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENEMY_DEFS = exports.EnemyType = exports.WEAPONS = exports.WeaponType = exports.WALL_COLORS = exports.MAP_H = exports.MAP_W = exports.ROT_SPEED = exports.MOVE_SPEED = exports.FOV = exports.TILE = exports.CANVAS_H = exports.CANVAS_W = void 0;
exports.CANVAS_W = 640;
exports.CANVAS_H = 360;
exports.TILE = 1; // tile size in world units
exports.FOV = Math.PI / 3;
exports.MOVE_SPEED = 3.5;
exports.ROT_SPEED = 2.8;
// Map constants
exports.MAP_W = 24;
exports.MAP_H = 24;
// Wall colors by type (r, g, b)
exports.WALL_COLORS = [
    [100, 100, 100], // 0: unused
    [139, 69, 19], // 1: brown brick
    [80, 80, 80], // 2: gray stone
    [101, 67, 33], // 3: dark wood
    [50, 50, 80], // 4: blue stone
    [80, 50, 50], // 5: red brick
];
var WeaponType;
(function (WeaponType) {
    WeaponType[WeaponType["PISTOL"] = 0] = "PISTOL";
    WeaponType[WeaponType["SHOTGUN"] = 1] = "SHOTGUN";
    WeaponType[WeaponType["PLASMA"] = 2] = "PLASMA";
})(WeaponType || (exports.WeaponType = WeaponType = {}));
exports.WEAPONS = [
    { name: "PISTOL", damage: 15, ammoCost: 1, fireRate: 400, spread: 0, pellets: 1, range: 20 },
    { name: "SHOTGUN", damage: 10, ammoCost: 2, fireRate: 800, spread: 0.15, pellets: 6, range: 12 },
    { name: "PLASMA", damage: 25, ammoCost: 3, fireRate: 150, spread: 0.04, pellets: 1, range: 18 },
];
var EnemyType;
(function (EnemyType) {
    EnemyType[EnemyType["GRUNT"] = 0] = "GRUNT";
    EnemyType[EnemyType["SOLDIER"] = 1] = "SOLDIER";
    EnemyType[EnemyType["DEMON"] = 2] = "DEMON";
})(EnemyType || (exports.EnemyType = EnemyType = {}));
exports.ENEMY_DEFS = {
    [EnemyType.GRUNT]: { health: 40, speed: 1.5, damage: 8, attackRange: 2.5, attackRate: 1500, color: [100, 160, 100] },
    [EnemyType.SOLDIER]: { health: 70, speed: 1.2, damage: 15, attackRange: 8, attackRate: 2000, color: [160, 100, 100] },
    [EnemyType.DEMON]: { health: 120, speed: 2.5, damage: 25, attackRange: 2, attackRate: 800, color: [180, 60, 60] },
};
};

// ── module: src/maps.ts ──
__mods["src/maps.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NUM_LEVELS = void 0;
exports.getLevel = getLevel;
// Map tiles: 0=empty, 1-5=wall types
const LEVELS = [
    // Level 1 - The Dungeon
    [
        [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, 4, 4, 4, 0, 0, 0, 0, 1],
        [1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1],
        [1, 0, 0, 2, 0, 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, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 5, 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, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 1],
        [1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1],
        [1, 0, 0, 3, 0, 0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 5, 5, 5, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 2, 2, 2, 0, 0, 4, 4, 0, 4, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 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, 3, 3, 3, 3, 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, 1, 1],
    ],
    // Level 2 - The Fortress
    [
        [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
        [5, 0, 0, 0, 0, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 5],
        [5, 0, 0, 0, 0, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 5],
        [5, 0, 0, 0, 0, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 1, 1, 1, 0, 0, 5],
        [5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 5],
        [5, 4, 4, 4, 4, 4, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 5],
        [5, 4, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 0, 1, 0, 1, 0, 0, 5],
        [5, 4, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 0, 0, 1, 1, 1, 0, 0, 5],
        [5, 4, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 5],
        [5, 4, 4, 4, 4, 4, 0, 0, 3, 3, 0, 3, 3, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 5],
        [5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 0, 5],
        [5, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 4, 0, 5],
        [5, 0, 0, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, 1, 0, 0, 3, 0, 4, 0, 0, 4, 0, 5],
        [5, 0, 0, 2, 0, 0, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3, 0, 4, 0, 0, 4, 0, 5],
        [5, 0, 0, 2, 0, 0, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3, 0, 4, 4, 4, 4, 0, 5],
        [5, 0, 0, 2, 2, 2, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 5],
        [5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 3, 0, 0, 2, 2, 2, 0, 5],
        [5, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 2, 0, 5],
        [5, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 2, 0, 5],
        [5, 4, 0, 0, 0, 4, 0, 0, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 5],
        [5, 4, 4, 4, 4, 4, 0, 0, 5, 0, 0, 0, 5, 0, 0, 3, 3, 0, 0, 2, 2, 2, 0, 5],
        [5, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
        [5, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
        [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
    ],
];
function getLevel(index) {
    return LEVELS[index % LEVELS.length];
}
exports.NUM_LEVELS = LEVELS.length;
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initGameState = initGameState;
const types_1 = require("./types");
const maps_1 = require("./maps");
let nextId = 0;
function createPlayer() {
    return { pos: { x: 1.5, y: 1.5 }, angle: 0, health: 100, maxHealth: 100, alive: true };
}
const ENEMY_SPAWNS = [
    // Level 1
    [
        { type: types_1.EnemyType.GRUNT, x: 5.5, y: 4.5 },
        { type: types_1.EnemyType.GRUNT, x: 10.5, y: 8.5 },
        { type: types_1.EnemyType.SOLDIER, x: 15.5, y: 3.5 },
        { type: types_1.EnemyType.GRUNT, x: 20.5, y: 6.5 },
        { type: types_1.EnemyType.DEMON, x: 8.5, y: 14.5 },
        { type: types_1.EnemyType.SOLDIER, x: 18.5, y: 12.5 },
        { type: types_1.EnemyType.GRUNT, x: 3.5, y: 18.5 },
        { type: types_1.EnemyType.DEMON, x: 14.5, y: 20.5 },
        { type: types_1.EnemyType.SOLDIER, x: 20.5, y: 16.5 },
        { type: types_1.EnemyType.GRUNT, x: 7.5, y: 21.5 },
    ],
    // Level 2
    [
        { type: types_1.EnemyType.SOLDIER, x: 4.5, y: 3.5 },
        { type: types_1.EnemyType.DEMON, x: 9.5, y: 6.5 },
        { type: types_1.EnemyType.GRUNT, x: 15.5, y: 2.5 },
        { type: types_1.EnemyType.SOLDIER, x: 18.5, y: 7.5 },
        { type: types_1.EnemyType.DEMON, x: 6.5, y: 13.5 },
        { type: types_1.EnemyType.GRUNT, x: 20.5, y: 11.5 },
        { type: types_1.EnemyType.SOLDIER, x: 14.5, y: 17.5 },
        { type: types_1.EnemyType.DEMON, x: 3.5, y: 20.5 },
        { type: types_1.EnemyType.GRUNT, x: 18.5, y: 21.5 },
        { type: types_1.EnemyType.SOLDIER, x: 10.5, y: 22.5 },
        { type: types_1.EnemyType.DEMON, x: 21.5, y: 4.5 },
        { type: types_1.EnemyType.GRUNT, x: 7.5, y: 19.5 },
    ],
];
const PICKUP_SPAWNS = [
    // Level 1
    [
        { x: 3.5, y: 7.5, kind: 'health', amount: 25 },
        { x: 8.5, y: 2.5, kind: 'ammo_pistol', amount: 15 },
        { x: 14.5, y: 10.5, kind: 'ammo_shotgun', amount: 8 },
        { x: 20.5, y: 13.5, kind: 'health', amount: 30 },
        { x: 6.5, y: 16.5, kind: 'ammo_plasma', amount: 10 },
        { x: 17.5, y: 20.5, kind: 'ammo_pistol', amount: 20 },
        { x: 11.5, y: 21.5, kind: 'health', amount: 25 },
    ],
    // Level 2
    [
        { x: 3.5, y: 6.5, kind: 'ammo_shotgun', amount: 8 },
        { x: 10.5, y: 4.5, kind: 'health', amount: 30 },
        { x: 17.5, y: 9.5, kind: 'ammo_plasma', amount: 12 },
        { x: 6.5, y: 15.5, kind: 'ammo_pistol', amount: 20 },
        { x: 21.5, y: 14.5, kind: 'health', amount: 25 },
        { x: 13.5, y: 20.5, kind: 'ammo_shotgun', amount: 6 },
        { x: 4.5, y: 21.5, kind: 'ammo_plasma', amount: 8 },
        { x: 19.5, y: 3.5, kind: 'health', amount: 30 },
    ],
];
function initGameState(level) {
    nextId = 0;
    const map = (0, maps_1.getLevel)(level);
    const spawns = ENEMY_SPAWNS[level % ENEMY_SPAWNS.length];
    const enemies = spawns.map(s => ({
        id: nextId++,
        type: s.type,
        pos: { x: s.x, y: s.y },
        health: types_1.ENEMY_DEFS[s.type].health,
        maxHealth: types_1.ENEMY_DEFS[s.type].health,
        alive: true,
        lastAttack: 0,
        alertDist: 8,
    }));
    const pickups = PICKUP_SPAWNS[level % PICKUP_SPAWNS.length].map(s => ({
        id: nextId++,
        pos: { x: s.x, y: s.y },
        def: { type: s.kind, amount: s.amount },
        collected: false,
    }));
    return {
        map,
        player: createPlayer(),
        enemies,
        pickups,
        currentWeapon: types_1.WeaponType.PISTOL,
        ammo: [50, 12, 30],
        lastShot: 0,
        shooting: false,
        shootFrame: 0,
        damageFlash: 0,
        kills: 0,
        totalEnemies: enemies.length,
        level,
        won: false,
    };
}
};

// ── module: src/raycaster.ts ──
__mods["src/raycaster.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.isWall = isWall;
exports.hasLineOfSight = hasLineOfSight;
exports.dist = dist;
// Cast a single ray using DDA algorithm
function castRay(state, px, py, angle) {
    const map = state.map;
    const dirX = Math.cos(angle);
    const dirY = Math.sin(angle);
    let mapX = Math.floor(px);
    let mapY = Math.floor(py);
    const deltaDistX = Math.abs(1 / (dirX || 0.0001));
    const deltaDistY = Math.abs(1 / (dirY || 0.0001));
    let stepX, stepY;
    let sideDistX, sideDistY;
    if (dirX < 0) {
        stepX = -1;
        sideDistX = (px - mapX) * deltaDistX;
    }
    else {
        stepX = 1;
        sideDistX = (mapX + 1.0 - px) * deltaDistX;
    }
    if (dirY < 0) {
        stepY = -1;
        sideDistY = (py - mapY) * deltaDistY;
    }
    else {
        stepY = 1;
        sideDistY = (mapY + 1.0 - py) * deltaDistY;
    }
    let side = 0;
    let hit = false;
    while (!hit) {
        if (sideDistX < sideDistY) {
            sideDistX += deltaDistX;
            mapX += stepX;
            side = 0;
        }
        else {
            sideDistY += deltaDistY;
            mapY += stepY;
            side = 1;
        }
        if (mapX < 0 || mapX >= state.map[0].length || mapY < 0 || mapY >= state.map.length) {
            break;
        }
        if (map[mapY][mapX] > 0) {
            hit = true;
        }
    }
    let perpDist;
    let wallX;
    if (side === 0) {
        perpDist = sideDistX - deltaDistX;
        wallX = py + perpDist * dirY;
    }
    else {
        perpDist = sideDistY - deltaDistY;
        wallX = px + perpDist * dirX;
    }
    wallX -= Math.floor(wallX);
    const wallType = (mapX >= 0 && mapX < state.map[0].length && mapY >= 0 && mapY < state.map.length)
        ? map[mapY][mapX] : 1;
    return { dist: perpDist, wallType, side, hitX: wallX };
}
// Check if a position is inside a wall
function isWall(map, x, y) {
    const mx = Math.floor(x);
    const my = Math.floor(y);
    if (mx < 0 || mx >= map[0].length || my < 0 || my >= map.length)
        return true;
    return map[my][mx] > 0;
}
// Check line of sight between two points (simple grid check)
function hasLineOfSight(map, 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);
    for (let i = 0; i < steps; i++) {
        const t = i / steps;
        const cx = x1 + dx * t;
        const cy = y1 + dy * t;
        if (isWall(map, cx, cy))
            return false;
    }
    return true;
}
// Distance between two points
function dist(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;
    return Math.sqrt(dx * dx + dy * dy);
}
};

// ── 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 types_2 = require("./types");
const raycaster_1 = require("./raycaster");
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d', { alpha: false });
canvas.width = types_1.CANVAS_W;
canvas.height = types_1.CANVAS_H;
// Pre-render wall textures for each type
const TEXTURE_SIZE = 64;
const textures = [];
function generateTextures() {
    for (let t = 1; t < types_1.WALL_COLORS.length; t++) {
        const imgData = ctx.createImageData(TEXTURE_SIZE, TEXTURE_SIZE);
        const baseColor = types_1.WALL_COLORS[t];
        for (let y = 0; y < TEXTURE_SIZE; y++) {
            for (let x = 0; x < TEXTURE_SIZE; x++) {
                const idx = (y * TEXTURE_SIZE + x) * 4;
                // Base color with noise
                let r = baseColor[0] + (Math.random() - 0.5) * 30;
                let g = baseColor[1] + (Math.random() - 0.5) * 30;
                let b = baseColor[2] + (Math.random() - 0.5) * 30;
                // Brick pattern for types 1, 5
                if (t === 1 || t === 5) {
                    const brickH = 8;
                    const brickW = 16;
                    const rowOffset = Math.floor(y / brickH) % 2 === 0 ? 0 : brickW / 2;
                    const inMortarX = (x + rowOffset) % brickW < 1 || x % brickW === 0;
                    const inMortarY = y % brickH < 1;
                    if (inMortarX || inMortarY) {
                        r *= 0.5;
                        g *= 0.5;
                        b *= 0.5;
                    }
                    else {
                        // Slight variation per brick
                        const brickNoise = Math.sin(x * 0.3 + y * 0.1) * 10;
                        r += brickNoise;
                        g += brickNoise;
                        b += brickNoise;
                    }
                }
                // Stone pattern for types 2, 4
                if (t === 2 || t === 4) {
                    const stoneNoise = Math.sin(x * 0.5 + y * 0.3) * 15 +
                        Math.cos(x * 0.2 - y * 0.7) * 10;
                    r += stoneNoise;
                    g += stoneNoise;
                    b += stoneNoise;
                }
                // Wood pattern for type 3
                if (t === 3) {
                    const grain = Math.sin(y * 0.8 + Math.sin(x * 0.15) * 3) * 12;
                    r += grain;
                    g += grain * 0.7;
                    b += grain * 0.4;
                }
                imgData.data[idx] = Math.max(0, Math.min(255, r));
                imgData.data[idx + 1] = Math.max(0, Math.min(255, g));
                imgData.data[idx + 2] = Math.max(0, Math.min(255, b));
                imgData.data[idx + 3] = 255;
            }
        }
        textures[t] = imgData;
    }
}
generateTextures();
// Z-buffer for sprite clipping
const zBuffer = new Float64Array(types_1.CANVAS_W);
function render(state) {
    const player = state.player;
    // Ceiling gradient
    const ceilGrad = ctx.createLinearGradient(0, 0, 0, types_1.CANVAS_H / 2);
    ceilGrad.addColorStop(0, '#0a0a15');
    ceilGrad.addColorStop(1, '#1a1a2e');
    ctx.fillStyle = ceilGrad;
    ctx.fillRect(0, 0, types_1.CANVAS_W, types_1.CANVAS_H / 2);
    // Floor gradient
    const floorGrad = ctx.createLinearGradient(0, types_1.CANVAS_H / 2, 0, types_1.CANVAS_H);
    floorGrad.addColorStop(0, '#1a1a2e');
    floorGrad.addColorStop(1, '#3d3530');
    ctx.fillStyle = floorGrad;
    ctx.fillRect(0, types_1.CANVAS_H / 2, types_1.CANVAS_W, types_1.CANVAS_H / 2);
    // Cast rays and draw walls with textures
    for (let x = 0; x < types_1.CANVAS_W; x++) {
        const rayAngle = player.angle - types_1.FOV / 2 + (x / types_1.CANVAS_W) * types_1.FOV;
        const hit = (0, raycaster_1.castRay)(state, player.pos.x, player.pos.y, rayAngle);
        // Fix fisheye
        const correctedDist = hit.dist * Math.cos(rayAngle - player.angle);
        zBuffer[x] = correctedDist;
        if (correctedDist < 0.1)
            continue;
        const wallHeight = Math.min(types_1.CANVAS_H * 2, types_1.CANVAS_H / correctedDist);
        const wallTop = (types_1.CANVAS_H - wallHeight) / 2;
        // Distance-based shading
        let shade = Math.max(0.15, 1 - correctedDist / 16);
        if (hit.side === 1)
            shade *= 0.7;
        // Draw textured wall strip
        const tex = textures[hit.wallType];
        if (tex && wallHeight > 2) {
            for (let y = Math.max(0, Math.floor(wallTop)); y < Math.min(types_1.CANVAS_H, Math.ceil(wallTop + wallHeight)); y++) {
                // Texture Y coordinate
                const texY = Math.floor(((y - wallTop) / wallHeight) * TEXTURE_SIZE);
                const idx = (texY * TEXTURE_SIZE + Math.floor(hit.hitX * TEXTURE_SIZE)) * 4;
                if (idx >= 0 && idx < tex.data.length - 3) {
                    const r = Math.floor(tex.data[idx] * shade);
                    const g = Math.floor(tex.data[idx + 1] * shade);
                    const b = Math.floor(tex.data[idx + 2] * shade);
                    ctx.fillStyle = `rgb(${r},${g},${b})`;
                    ctx.fillRect(x, y, 1, 1);
                }
            }
        }
        else {
            // Fallback: solid color with shading
            const wc = types_1.WALL_COLORS[hit.wallType] || [100, 100, 100];
            ctx.fillStyle = `rgb(${Math.floor(wc[0] * shade)},${Math.floor(wc[1] * shade)},${Math.floor(wc[2] * shade)})`;
            ctx.fillRect(x, wallTop, 1, wallHeight);
        }
        // Wall edge highlights
        if (wallHeight > 4) {
            const edgeAlpha = shade * 0.15;
            ctx.fillStyle = `rgba(255,255,255,${edgeAlpha})`;
            ctx.fillRect(x, Math.floor(wallTop), 1, 2);
            ctx.fillStyle = `rgba(0,0,0,${shade * 0.4})`;
            ctx.fillRect(x, Math.ceil(wallTop + wallHeight) - 2, 1, 2);
        }
    }
    // Render sprites (enemies and pickups)
    renderSprites(state);
    // Damage flash overlay
    if (state.damageFlash > 0) {
        ctx.fillStyle = `rgba(255, 0, 0, ${Math.min(0.4, state.damageFlash)})`;
        ctx.fillRect(0, 0, types_1.CANVAS_W, types_1.CANVAS_H);
    }
    // Vignette effect
    const vigGrad = ctx.createRadialGradient(types_1.CANVAS_W / 2, types_1.CANVAS_H / 2, types_1.CANVAS_H * 0.3, types_1.CANVAS_W / 2, types_1.CANVAS_H / 2, types_1.CANVAS_H * 0.8);
    vigGrad.addColorStop(0, 'rgba(0,0,0,0)');
    vigGrad.addColorStop(1, 'rgba(0,0,0,0.5)');
    ctx.fillStyle = vigGrad;
    ctx.fillRect(0, 0, types_1.CANVAS_W, types_1.CANVAS_H);
    // Crosshair
    drawCrosshair(ctx, types_1.CANVAS_W / 2, types_1.CANVAS_H / 2);
    // Weapon rendering
    renderWeapon(state);
}
function drawCrosshair(ctx, cx, cy) {
    const size = 8;
    const gap = 4;
    ctx.strokeStyle = 'rgba(0, 255, 0, 0.7)';
    ctx.lineWidth = 1.5;
    // Top
    ctx.beginPath();
    ctx.moveTo(cx, cy - gap);
    ctx.lineTo(cx, cy - size);
    ctx.stroke();
    // Bottom
    ctx.beginPath();
    ctx.moveTo(cx, cy + gap);
    ctx.lineTo(cx, cy + size);
    ctx.stroke();
    // Left
    ctx.beginPath();
    ctx.moveTo(cx - gap, cy);
    ctx.lineTo(cx - size, cy);
    ctx.stroke();
    // Right
    ctx.beginPath();
    ctx.moveTo(cx + gap, cy);
    ctx.lineTo(cx + size, cy);
    ctx.stroke();
    // Center dot
    ctx.fillStyle = 'rgba(0, 255, 0, 0.9)';
    ctx.fillRect(cx - 1, cy - 1, 2, 2);
}
function renderSprites(state) {
    const player = state.player;
    const sprites = [];
    for (const enemy of state.enemies) {
        if (!enemy.alive)
            continue;
        const d = (0, raycaster_1.dist)(player.pos, enemy.pos);
        sprites.push({ pos: enemy.pos, dist: d, data: { type: 'enemy', enemy } });
    }
    for (const pickup of state.pickups) {
        if (pickup.collected)
            continue;
        const d = (0, raycaster_1.dist)(player.pos, pickup.pos);
        sprites.push({ pos: pickup.pos, dist: d, data: { type: 'pickup', pickup } });
    }
    // Sort back to front
    sprites.sort((a, b) => b.dist - a.dist);
    for (const sprite of sprites) {
        const dx = sprite.pos.x - player.pos.x;
        const dy = sprite.pos.y - player.pos.y;
        const cosA = Math.cos(-player.angle);
        const sinA = Math.sin(-player.angle);
        const tx = dx * cosA - dy * sinA;
        const ty = dx * sinA + dy * cosA;
        if (tx <= 0.1)
            continue;
        const screenX = types_1.CANVAS_W / 2 * (1 + ty / tx / Math.tan(types_1.FOV / 2));
        if (sprite.data.type === 'enemy') {
            renderEnemy(state, sprite.data.enemy, sprite.dist, screenX);
        }
        else {
            renderPickup(sprite.data.pickup, sprite.dist, screenX);
        }
    }
}
function renderEnemy(state, enemy, dist, screenX) {
    const def = types_2.ENEMY_DEFS[enemy.type];
    const size = Math.min(types_1.CANVAS_H * 1.5, types_1.CANVAS_H / dist);
    const spriteLeft = Math.floor(screenX - size / 2);
    const spriteRight = Math.floor(screenX + size / 2);
    let visible = false;
    for (let x = Math.max(0, spriteLeft); x < Math.min(types_1.CANVAS_W, spriteRight); x += 4) {
        if (dist < zBuffer[x]) {
            visible = true;
            break;
        }
    }
    if (!visible)
        return;
    const topY = types_1.CANVAS_H / 2 - size * 0.35;
    let shade = Math.max(0.2, 1 - dist / 14);
    const r = Math.floor(def.color[0] * shade);
    const g = Math.floor(def.color[1] * shade);
    const b = Math.floor(def.color[2] * shade);
    // Draw enemy body pixel by pixel for proper z-buffering
    for (let x = Math.max(0, spriteLeft); x < Math.min(types_1.CANVAS_W, spriteRight); x++) {
        if (dist >= zBuffer[x])
            continue;
        const nx = (x - screenX) / (size / 2); // normalized: -1 to 1
        // Body shape with rounded edges
        const bodyWidth = 0.5 * Math.cos(nx * Math.PI / 2);
        if (Math.abs(nx) > bodyWidth + 0.1)
            continue;
        // Shadow underneath
        ctx.fillStyle = `rgba(0,0,0,${shade * 0.3})`;
        ctx.fillRect(x - 1, topY + size * 0.92, 3, size * 0.08);
        // Legs
        if (Math.abs(nx) < 0.35) {
            const legColor = `rgb(${Math.floor(r * 0.6)},${Math.floor(g * 0.6)},${Math.floor(b * 0.6)})`;
            ctx.fillStyle = legColor;
            ctx.fillRect(x, topY + size * 0.72, 1, size * 0.2);
        }
        // Body
        ctx.fillStyle = `rgb(${r},${g},${b})`;
        ctx.fillRect(x, topY + size * 0.15, 1, size * 0.6);
        // Body highlight
        if (Math.abs(nx) < 0.2) {
            ctx.fillStyle = `rgba(255,255,255,${shade * 0.1})`;
            ctx.fillRect(x, topY + size * 0.2, 1, size * 0.4);
        }
        // Head
        if (Math.abs(nx) < 0.38) {
            const headR = Math.min(255, r + 30);
            const headG = Math.min(255, g + 30);
            const headB = Math.min(255, b + 30);
            ctx.fillStyle = `rgb(${headR},${headG},${headB})`;
            ctx.fillRect(x, topY - size * 0.02, 1, size * 0.22);
        }
        // Eyes (menacing red glow)
        if ((Math.abs(nx - 0.12) < 0.06 || Math.abs(nx + 0.12) < 0.06)) {
            ctx.fillStyle = `rgb(255,${Math.floor(30 * shade)},0)`;
            ctx.fillRect(x, topY + size * 0.04, 1, size * 0.07);
        }
        // Mouth (for demons)
        if (enemy.type === 2 && Math.abs(nx) < 0.15) {
            ctx.fillStyle = `rgb(${Math.floor(r * 0.3)},${Math.floor(g * 0.3)},${Math.floor(b * 0.3)})`;
            ctx.fillRect(x, topY + size * 0.14, 1, size * 0.03);
        }
    }
    // Health bar above enemy
    if (enemy.health < enemy.maxHealth && dist < 8) {
        const barW = Math.floor(size * 0.6);
        const barH = 4;
        const barX = Math.floor(screenX - barW / 2);
        const barY = Math.floor(topY - 12);
        ctx.fillStyle = '#333';
        ctx.fillRect(barX, barY, barW, barH);
        const hpPct = enemy.health / enemy.maxHealth;
        ctx.fillStyle = hpPct > 0.5 ? '#4f4' :
            hpPct > 0.25 ? '#ff4' : '#f44';
        ctx.fillRect(barX, barY, Math.floor(barW * hpPct), barH);
        // Health bar border
        ctx.strokeStyle = '#666';
        ctx.lineWidth = 1;
        ctx.strokeRect(barX, barY, barW, barH);
    }
}
function renderPickup(pickup, dist, screenX) {
    const size = Math.min(types_1.CANVAS_H * 0.5, types_1.CANVAS_H / dist);
    const spriteLeft = Math.floor(screenX - size / 2);
    const spriteRight = Math.floor(screenX + size / 2);
    let visible = false;
    for (let x = Math.max(0, spriteLeft); x < Math.min(types_1.CANVAS_W, spriteRight); x += 4) {
        if (dist < zBuffer[x]) {
            visible = true;
            break;
        }
    }
    if (!visible)
        return;
    const shade = Math.max(0.3, 1 - dist / 12);
    let color;
    switch (pickup.def.type) {
        case 'health':
            color = `rgb(${Math.floor(80 * shade)},${Math.floor(255 * shade)},${Math.floor(80 * shade)})`;
            break;
        case 'ammo_pistol':
            color = `rgb(${Math.floor(255 * shade)},${Math.floor(200 * shade)},${Math.floor(50 * shade)})`;
            break;
        case 'ammo_shotgun':
            color = `rgb(${Math.floor(255 * shade)},${Math.floor(120 * shade)},${Math.floor(30 * shade)})`;
            break;
        case 'ammo_plasma':
            color = `rgb(${Math.floor(80 * shade)},${Math.floor(150 * shade)},${Math.floor(255 * shade)})`;
            break;
        default: color = '#fff';
    }
    const bobY = Math.sin(Date.now() / 300) * size * 0.08;
    const topY = types_1.CANVAS_H / 2 + size * 0.15 + bobY;
    for (let x = Math.max(0, spriteLeft); x < Math.min(types_1.CANVAS_W, spriteRight); x++) {
        if (dist >= zBuffer[x])
            continue;
        const nx = (x - screenX) / (size / 2);
        if (Math.abs(nx) > 0.45)
            continue;
        // Glow underneath
        ctx.fillStyle = `rgba(255,255,255,${shade * 0.1})`;
        ctx.fillRect(x - 1, topY + size * 0.3, 3, size * 0.08);
        // Main pickup body (diamond shape)
        const diamondW = 0.4 * (1 - Math.abs((nx)));
        if (Math.abs(nx) < 0.45) {
            ctx.fillStyle = color;
            ctx.fillRect(x, topY - size * 0.05, 1, size * 0.3);
            // Inner highlight
            if (Math.abs(nx) < 0.25) {
                ctx.fillStyle = `rgba(255,255,255,${shade * 0.4})`;
                ctx.fillRect(x, topY + size * 0.05, 1, size * 0.1);
            }
        }
        // Cross for health pickups
        if (pickup.def.type === 'health' && Math.abs(nx) < 0.12) {
            ctx.fillStyle = `rgba(255,255,255,${shade * 0.8})`;
            ctx.fillRect(x, topY + size * 0.05, 1, size * 0.15);
        }
    }
}
function renderWeapon(state) {
    const weaponIdx = state.currentWeapon;
    const bobX = Math.sin(Date.now() / 200) * 3;
    const bobY = Math.abs(Math.cos(Date.now() / 200)) * 4;
    let shootOffset = 0;
    if (state.shootFrame > 0) {
        shootOffset = state.shootFrame * 15;
    }
    const baseX = types_1.CANVAS_W / 2 + bobX;
    const baseY = types_1.CANVAS_H - 5 + bobY;
    ctx.save();
    switch (weaponIdx) {
        case 0:
            drawPistol(ctx, baseX, baseY - shootOffset);
            break;
        case 1:
            drawShotgun(ctx, baseX, baseY - shootOffset);
            break;
        case 2:
            drawPlasmaGun(ctx, baseX, baseY - shootOffset);
            break;
    }
    ctx.restore();
    // Muzzle flash
    if (state.shootFrame > 0) {
        const flashSize = state.shootFrame * 40;
        const gradient = ctx.createRadialGradient(types_1.CANVAS_W / 2, types_1.CANVAS_H * 0.52, 0, types_1.CANVAS_W / 2, types_1.CANVAS_H * 0.52, flashSize);
        gradient.addColorStop(0, `rgba(255,255,220,${state.shootFrame * 0.9})`);
        gradient.addColorStop(0.3, `rgba(255,180,50,${state.shootFrame * 0.6})`);
        gradient.addColorStop(1, 'rgba(255,80,0,0)');
        ctx.fillStyle = gradient;
        ctx.fillRect(types_1.CANVAS_W / 2 - flashSize, types_1.CANVAS_H * 0.52 - flashSize, flashSize * 2, flashSize * 2);
    }
}
function drawPistol(ctx, x, y) {
    // Shadow
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.fillRect(x - 8, y - 75, 16, 4);
    // Barrel
    ctx.fillStyle = '#555';
    ctx.fillRect(x - 7, y - 82, 14, 52);
    ctx.fillStyle = '#666';
    ctx.fillRect(x - 5, y - 80, 10, 48);
    ctx.fillStyle = '#777';
    ctx.fillRect(x - 3, y - 78, 6, 44);
    // Barrel tip
    ctx.fillStyle = '#222';
    ctx.fillRect(x - 5, y - 84, 10, 4);
    // Handle
    ctx.fillStyle = '#6b4226';
    ctx.fillRect(x - 9, y - 32, 18, 38);
    ctx.fillStyle = '#7d5230';
    ctx.fillRect(x - 7, y - 30, 14, 34);
    // Trigger guard
    ctx.strokeStyle = '#555';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(x, y - 26, 9, 0.1 * Math.PI, 0.9 * Math.PI);
    ctx.stroke();
}
function drawShotgun(ctx, x, y) {
    // Shadow
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.fillRect(x - 16, y - 85, 32, 4);
    // Double barrel
    ctx.fillStyle = '#4a4a4a';
    ctx.fillRect(x - 15, y - 92, 11, 62);
    ctx.fillRect(x + 4, y - 92, 11, 62);
    // Barrel highlights
    ctx.fillStyle = '#666';
    ctx.fillRect(x - 13, y - 90, 7, 58);
    ctx.fillRect(x + 6, y - 90, 7, 58);
    // Barrel tips
    ctx.fillStyle = '#222';
    ctx.fillRect(x - 14, y - 94, 9, 3);
    ctx.fillRect(x + 5, y - 94, 9, 3);
    // Body
    ctx.fillStyle = '#5a3820';
    ctx.fillRect(x - 17, y - 36, 34, 42);
    ctx.fillStyle = '#6b4830';
    ctx.fillRect(x - 15, y - 34, 30, 38);
    // Pump grip
    ctx.fillStyle = '#4a2810';
    ctx.fillRect(x - 19, y - 52, 38, 10);
    ctx.fillStyle = '#5a3820';
    ctx.fillRect(x - 17, y - 50, 34, 6);
    // Stock
    ctx.fillStyle = '#6b4830';
    ctx.fillRect(x - 11, y - 4, 22, 16);
}
function drawPlasmaGun(ctx, x, y) {
    // Shadow
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.fillRect(x - 14, y - 70, 28, 4);
    // Main body
    ctx.fillStyle = '#3a5a8a';
    ctx.fillRect(x - 13, y - 77, 26, 52);
    ctx.fillStyle = '#4a6a9a';
    ctx.fillRect(x - 11, y - 75, 22, 48);
    // Energy core (pulsing glow)
    const pulse = Math.sin(Date.now() / 120) * 0.3 + 0.7;
    ctx.fillStyle = `rgba(0,${Math.floor(180 * pulse)},255,${pulse})`;
    ctx.fillRect(x - 7, y - 72, 14, 22);
    // Core bright center
    ctx.fillStyle = `rgba(150,220,255,${pulse * 0.8})`;
    ctx.fillRect(x - 3, y - 68, 6, 14);
    // Barrel tip
    ctx.fillStyle = '#5a7aaa';
    ctx.fillRect(x - 9, y - 87, 18, 12);
    ctx.fillStyle = '#2a3a5a';
    ctx.fillRect(x - 6, y - 89, 12, 4);
    // Side grips
    ctx.fillStyle = '#4a6a9a';
    ctx.fillRect(x - 19, y - 52, 7, 28);
    ctx.fillRect(x + 12, y - 52, 7, 28);
    // Handle
    ctx.fillStyle = '#3a4a6a';
    ctx.fillRect(x - 9, y - 28, 18, 30);
    ctx.fillStyle = '#4a5a7a';
    ctx.fillRect(x - 7, y - 26, 14, 26);
    // Energy lines on body
    ctx.strokeStyle = `rgba(0,${Math.floor(200 * pulse)},255,${pulse * 0.5})`;
    ctx.lineWidth = 1;
    for (let i = 0; i < 3; i++) {
        const ly = y - 68 + i * 8;
        ctx.beginPath();
        ctx.moveTo(x - 9, ly);
        ctx.lineTo(x + 9, ly);
        ctx.stroke();
    }
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.keys = void 0;
exports.requestPointerLock = requestPointerLock;
exports.getInput = getInput;
exports.keys = {};
let mouseDX = 0;
let mouseDown = false;
let mouseClicked = false;
window.addEventListener('keydown', e => {
    exports.keys[e.code] = true;
    // Prevent scrolling with space/arrows/WASD
    if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.code)) {
        e.preventDefault();
    }
});
window.addEventListener('keyup', e => {
    exports.keys[e.code] = false;
});
canvasEl().addEventListener('mousedown', () => { mouseDown = true; mouseClicked = true; });
window.addEventListener('mouseup', () => { mouseDown = false; });
function canvasEl() { return document.getElementById('game-canvas'); }
// Pointer lock for mouse look
function requestPointerLock() {
    const c = canvasEl();
    if (c && !document.pointerLockElement) {
        c.requestPointerLock?.();
    }
}
window.addEventListener('mousemove', e => {
    if (document.pointerLockElement) {
        mouseDX += e.movementX;
    }
});
function getInput() {
    const result = { dx: mouseDX, down: mouseDown, clicked: mouseClicked };
    mouseDX = 0;
    mouseClicked = false;
    return result;
}
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.update = update;
exports.respawnPlayer = respawnPlayer;
exports.nextLevel = nextLevel;
const types_1 = require("./types");
const types_2 = require("./types");
const raycaster_1 = require("./raycaster");
const input_1 = require("./input");
const sound_1 = require("./sound");
const PLAYER_RADIUS = 0.25;
function update(state, dt) {
    if (state.player.alive && !state.won) {
        handlePlayerInput(state, dt);
        updateEnemies(state, dt);
        checkPickups(state);
    }
    // Decay effects
    if (state.shootFrame > 0)
        state.shootFrame -= dt * 5;
    if (state.damageFlash > 0)
        state.damageFlash -= dt * 3;
}
function handlePlayerInput(state, dt) {
    const player = state.player;
    // Rotation from mouse
    const input = (0, input_1.getInput)();
    player.angle += input.dx * 0.002;
    if (input.clicked || input_1.keys['Space']) {
        shootWeapon(state);
    }
    // Weapon switching
    if (input_1.keys['Digit1'] || input_1.keys['Numpad1'])
        state.currentWeapon = types_1.WeaponType.PISTOL;
    if (input_1.keys['Digit2'] || input_1.keys['Numpad2'])
        state.currentWeapon = types_1.WeaponType.SHOTGUN;
    if (input_1.keys['Digit3'] || input_1.keys['Numpad3'])
        state.currentWeapon = types_1.WeaponType.PLASMA;
    // Movement
    let moveX = 0, moveY = 0;
    const speed = types_1.MOVE_SPEED * dt;
    if (input_1.keys['KeyW'] || input_1.keys['ArrowUp']) {
        moveX += Math.cos(player.angle) * speed;
        moveY += Math.sin(player.angle) * speed;
    }
    if (input_1.keys['KeyS'] || input_1.keys['ArrowDown']) {
        moveX -= Math.cos(player.angle) * speed;
        moveY -= Math.sin(player.angle) * speed;
    }
    if (input_1.keys['KeyA'] || input_1.keys['ArrowLeft']) {
        // Strafe left
        moveX += Math.cos(player.angle - Math.PI / 2) * speed;
        moveY += Math.sin(player.angle - Math.PI / 2) * speed;
    }
    if (input_1.keys['KeyD'] || input_1.keys['ArrowRight']) {
        // Strafe right
        moveX += Math.cos(player.angle + Math.PI / 2) * speed;
        moveY += Math.sin(player.angle + Math.PI / 2) * speed;
    }
    // Keyboard rotation fallback
    if (input_1.keys['KeyQ'] || input_1.keys['Numpad4'])
        player.angle -= types_1.ROT_SPEED * dt;
    if (input_1.keys['KeyE'] || input_1.keys['Numpad6'])
        player.angle += types_1.ROT_SPEED * dt;
    // Collision detection with sliding
    const newX = player.pos.x + moveX;
    const newY = player.pos.y + moveY;
    if (!(0, raycaster_1.isWall)(state.map, newX, player.pos.y)) {
        player.pos.x = newX;
    }
    if (!(0, raycaster_1.isWall)(state.map, player.pos.x, newY)) {
        player.pos.y = newY;
    }
    // Extra margin check
    const marginX = Math.cos(player.angle) * PLAYER_RADIUS;
    const marginY = Math.sin(player.angle) * PLAYER_RADIUS;
    if ((0, raycaster_1.isWall)(state.map, player.pos.x + marginX, player.pos.y + marginY)) {
        player.pos.x -= moveX;
        player.pos.y -= moveY;
    }
}
function shootWeapon(state) {
    const now = Date.now();
    const weaponIdx = state.currentWeapon;
    // Check ammo
    if (state.ammo[weaponIdx] <= 0)
        return;
    // Check fire rate
    const fireRate = [400, 800, 150][weaponIdx];
    if (now - state.lastShot < fireRate)
        return;
    state.lastShot = now;
    state.ammo[weaponIdx]--;
    state.shooting = true;
    state.shootFrame = 1.0;
    (0, sound_1.playShoot)(weaponIdx);
    const pellets = [1, 6, 1][weaponIdx];
    const spread = [0, 0.15, 0.04][weaponIdx];
    const damage = [15, 10, 25][weaponIdx];
    const range = [20, 12, 18][weaponIdx];
    for (let p = 0; p < pellets; p++) {
        const shotAngle = state.player.angle + (Math.random() - 0.5) * spread;
        // Check which enemies are hit
        let closestHit = null;
        for (const enemy of state.enemies) {
            if (!enemy.alive)
                continue;
            const d = (0, raycaster_1.dist)(state.player.pos, enemy.pos);
            if (d > range)
                continue;
            // Check angle to enemy
            const dx = enemy.pos.x - state.player.pos.x;
            const dy = enemy.pos.y - state.player.pos.y;
            let angleToEnemy = Math.atan2(dy, dx) - shotAngle;
            // Normalize angle
            while (angleToEnemy > Math.PI)
                angleToEnemy -= 2 * Math.PI;
            while (angleToEnemy < -Math.PI)
                angleToEnemy += 2 * Math.PI;
            const hitWidth = Math.atan2(0.4, d); // enemy hit radius
            if (Math.abs(angleToEnemy) < hitWidth && (0, raycaster_1.hasLineOfSight)(state.map, state.player.pos.x, state.player.pos.y, enemy.pos.x, enemy.pos.y)) {
                if (!closestHit || d < closestHit.dist) {
                    closestHit = { enemy, dist: d };
                }
            }
        }
        if (closestHit) {
            const dmg = damage * (0.8 + Math.random() * 0.4); // slight variance
            closestHit.enemy.health -= dmg;
            if (closestHit.enemy.health <= 0) {
                closestHit.enemy.alive = false;
                (0, sound_1.playEnemyDeath)();
                state.kills++;
                // Check win condition
                const aliveCount = state.enemies.filter(e => e.alive).length;
                if (aliveCount === 0) {
                    state.won = true;
                }
            }
        }
    }
}
function updateEnemies(state, dt) {
    const now = Date.now();
    for (const enemy of state.enemies) {
        if (!enemy.alive)
            continue;
        const def = types_2.ENEMY_DEFS[enemy.type];
        const playerPos = state.player.pos;
        const d = (0, raycaster_1.dist)(playerPos, enemy.pos);
        // Alert if close enough or can see player
        const alertRange = enemy.type === types_1.EnemyType.DEMON ? 14 : enemy.alertDist;
        if (d < alertRange || (0, raycaster_1.hasLineOfSight)(state.map, playerPos.x, playerPos.y, enemy.pos.x, enemy.pos.y)) {
            // Move toward player with slight wobble for natural movement
            const dx = playerPos.x - enemy.pos.x;
            const dy = playerPos.y - enemy.pos.y;
            let angle = Math.atan2(dy, dx);
            // Add slight random wobble to movement direction
            if (d > 1.5) {
                angle += (Math.sin(Date.now() / 300 + enemy.id * 7) * 0.3);
            }
            let moveX = Math.cos(angle) * def.speed * dt;
            let moveY = Math.sin(angle) * def.speed * dt;
            // Collision with walls
            const newX = enemy.pos.x + moveX;
            const newY = enemy.pos.y + moveY;
            if (!(0, raycaster_1.isWall)(state.map, newX, enemy.pos.y)) {
                enemy.pos.x = newX;
            }
            else {
                // Try sliding
                if (!(0, raycaster_1.isWall)(state.map, newX, enemy.pos.y - 0.3)) {
                    enemy.pos.x = newX;
                }
                else if (!(0, raycaster_1.isWall)(state.map, newX, enemy.pos.y + 0.3)) {
                    enemy.pos.x = newX;
                }
            }
            if (!(0, raycaster_1.isWall)(state.map, enemy.pos.x, newY)) {
                enemy.pos.y = newY;
            }
            else {
                // Try sliding
                if (!(0, raycaster_1.isWall)(state.map, enemy.pos.x - 0.3, newY)) {
                    enemy.pos.y = newY;
                }
                else if (!(0, raycaster_1.isWall)(state.map, enemy.pos.x + 0.3, newY)) {
                    enemy.pos.y = newY;
                }
            }
            // Attack player
            if (d < def.attackRange && now - enemy.lastAttack > def.attackRate) {
                enemy.lastAttack = now;
                if (state.player.alive) {
                    (0, sound_1.playHit)();
                    state.player.health -= def.damage;
                    state.damageFlash = 1.0;
                    if (state.player.health <= 0) {
                        state.player.health = 0;
                        state.player.alive = false;
                        (0, sound_1.playDeath)();
                    }
                }
            }
        }
    }
}
function checkPickups(state) {
    const playerPos = state.player.pos;
    for (const pickup of state.pickups) {
        if (pickup.collected)
            continue;
        const d = (0, raycaster_1.dist)(playerPos, pickup.pos);
        if (d > 0.6)
            continue;
        pickup.collected = true;
        (0, sound_1.playPickup)();
        switch (pickup.def.type) {
            case 'health':
                state.player.health = Math.min(state.player.maxHealth, state.player.health + pickup.def.amount);
                break;
            case 'ammo_pistol':
                state.ammo[types_1.WeaponType.PISTOL] += pickup.def.amount;
                break;
            case 'ammo_shotgun':
                state.ammo[types_1.WeaponType.SHOTGUN] += pickup.def.amount;
                break;
            case 'ammo_plasma':
                state.ammo[types_1.WeaponType.PLASMA] += pickup.def.amount;
                break;
        }
    }
}
function respawnPlayer(state) {
    state.player.health = state.player.maxHealth;
    state.player.alive = true;
    state.player.pos.x = 1.5;
    state.player.pos.y = 1.5;
    state.player.angle = 0;
    state.damageFlash = 0;
}
function nextLevel(state) {
    const newLevel = state.level + 1;
    // Reload level data
    Promise.resolve().then(() => __importStar(require('./state'))).then(mod => {
        const newState = mod.initGameState(newLevel);
        Object.assign(state, newState);
    });
}
};

// ── module: src/minimap.ts ──
__mods["src/minimap.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderMinimap = renderMinimap;
const types_1 = require("./types");
const minimapCanvas = document.getElementById('minimap');
const mctx = minimapCanvas.getContext('2d', { alpha: false });
minimapCanvas.width = 120;
minimapCanvas.height = 120;
function renderMinimap(state) {
    const map = state.map;
    const mw = map[0].length;
    const mh = map.length;
    const scale = Math.min(120 / mw, 120 / mh);
    mctx.fillStyle = '#111';
    mctx.fillRect(0, 0, 120, 120);
    // Draw walls
    for (let y = 0; y < mh; y++) {
        for (let x = 0; x < mw; x++) {
            if (map[y][x] > 0) {
                mctx.fillStyle = '#555';
                mctx.fillRect(x * scale, y * scale, scale + 0.5, scale + 0.5);
            }
        }
    }
    // Draw pickups
    for (const pickup of state.pickups) {
        if (pickup.collected)
            continue;
        let color = '#fff';
        switch (pickup.def.type) {
            case 'health':
                color = '#0f0';
                break;
            case 'ammo_pistol':
                color = '#ff0';
                break;
            case 'ammo_shotgun':
                color = '#f80';
                break;
            case 'ammo_plasma':
                color = '#48f';
                break;
        }
        mctx.fillStyle = color;
        mctx.fillRect(pickup.pos.x * scale - 1, pickup.pos.y * scale - 1, 3, 3);
    }
    // Draw enemies
    for (const enemy of state.enemies) {
        if (!enemy.alive)
            continue;
        const def = types_1.ENEMY_DEFS[enemy.type];
        mctx.fillStyle = `rgb(${def.color[0]},${def.color[1]},${def.color[2]})`;
        mctx.fillRect(enemy.pos.x * scale - 2, enemy.pos.y * scale - 2, 4, 4);
    }
    // Draw player
    const px = state.player.pos.x * scale;
    const py = state.player.pos.y * scale;
    mctx.fillStyle = '#0f0';
    mctx.beginPath();
    mctx.arc(px, py, 3, 0, Math.PI * 2);
    mctx.fill();
    // Player direction indicator
    mctx.strokeStyle = '#0f0';
    mctx.lineWidth = 1;
    mctx.beginPath();
    mctx.moveTo(px, py);
    mctx.lineTo(px + Math.cos(state.player.angle) * 8, py + Math.sin(state.player.angle) * 8);
    mctx.stroke();
}
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateHUD = updateHUD;
const types_1 = require("./types");
const healthVal = document.getElementById('health-val');
const weaponName = document.getElementById('weapon-name');
const ammoVal = document.getElementById('ammo-val');
// Create kills display if not exists
let killsDisplay = null;
if (!document.getElementById('kills-display')) {
    const el = document.createElement('span');
    el.id = 'kills-display';
    el.style.cssText = 'color:#ff6600;font-size:14px;text-shadow:0 0 8px #ff3300;position:absolute;top:10px;left:10px;z-index:10;pointer-events:none;';
    document.getElementById('game-container').appendChild(el);
    killsDisplay = el;
}
else {
    killsDisplay = document.getElementById('kills-display');
}
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlay-title');
const overlaySubtitle = document.getElementById('overlay-subtitle');
function updateHUD(state) {
    healthVal.textContent = Math.max(0, state.player.health).toString();
    const weaponDef = types_1.WEAPONS[state.currentWeapon];
    weaponName.textContent = weaponDef.name;
    ammoVal.textContent = state.ammo[state.currentWeapon].toString();
    // Update kills display
    if (killsDisplay) {
        const remaining = state.enemies.filter(e => e.alive).length;
        killsDisplay.textContent = `ENEMIES: ${remaining}`;
    }
    // Health bar color
    const hpPct = state.player.health / state.player.maxHealth;
    if (hpPct > 0.5) {
        healthVal.style.color = '#ff4444';
    }
    else if (hpPct > 0.25) {
        healthVal.style.color = '#ff8800';
    }
    else {
        healthVal.style.color = '#ff0000';
    }
    // Show/hide overlay based on game state
    if (!state.player.alive && !state.won) {
        overlay.style.display = 'flex';
        overlayTitle.textContent = 'YOU DIED';
        overlaySubtitle.innerHTML = `Kills: ${state.kills} / ${state.totalEnemies}<br><br>Press R to respawn`;
    }
    else if (state.won) {
        overlay.style.display = 'flex';
        overlayTitle.textContent = 'LEVEL COMPLETE!';
        overlayTitle.style.color = '#44ff44';
        overlaySubtitle.innerHTML = `Kills: ${state.kills} / ${state.totalEnemies}<br><br>Press ENTER for next level`;
    }
    else {
        overlay.style.display = 'none';
    }
}
};

// ── module: src/sound.ts ──
__mods["src/sound.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.playShoot = playShoot;
exports.playHit = playHit;
exports.playPickup = playPickup;
exports.playDeath = playDeath;
exports.playEnemyDeath = playEnemyDeath;
// Simple synthesized sound effects using Web Audio API
let audioCtx = null;
function getAudio() {
    if (!audioCtx) {
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    }
    return audioCtx;
}
function playShoot(weaponIdx) {
    try {
        const ctx = getAudio();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        switch (weaponIdx) {
            case 0: // Pistol - sharp crack
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(200, ctx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(50, ctx.currentTime + 0.1);
                gain.gain.setValueAtTime(0.3, ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
                osc.start(ctx.currentTime);
                osc.stop(ctx.currentTime + 0.15);
                break;
            case 1: // Shotgun - deep boom
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(80, ctx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(20, ctx.currentTime + 0.3);
                gain.gain.setValueAtTime(0.4, ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.35);
                osc.start(ctx.currentTime);
                osc.stop(ctx.currentTime + 0.35);
                // Add noise burst
                const bufferSize = ctx.sampleRate * 0.2;
                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) * Math.exp(-i / (bufferSize * 0.1));
                }
                const noise = ctx.createBufferSource();
                noise.buffer = buffer;
                const nGain = ctx.createGain();
                nGain.gain.setValueAtTime(0.3, ctx.currentTime);
                nGain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
                noise.connect(nGain);
                nGain.connect(ctx.destination);
                noise.start(ctx.currentTime);
                break;
            case 2: // Plasma - electric zap
                osc.type = 'square';
                osc.frequency.setValueAtTime(800, ctx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.15);
                gain.gain.setValueAtTime(0.2, ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.18);
                osc.start(ctx.currentTime);
                osc.stop(ctx.currentTime + 0.18);
                break;
        }
    }
    catch (e) { /* Audio not available */ }
}
function playHit() {
    try {
        const ctx = getAudio();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(300, ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.1);
        gain.gain.setValueAtTime(0.2, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.12);
        osc.start(ctx.currentTime);
        osc.stop(ctx.currentTime + 0.12);
    }
    catch (e) { /* Audio not available */ }
}
function playPickup() {
    try {
        const ctx = getAudio();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.type = 'sine';
        osc.frequency.setValueAtTime(400, ctx.currentTime);
        osc.frequency.linearRampToValueAtTime(800, ctx.currentTime + 0.15);
        gain.gain.setValueAtTime(0.2, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
        osc.start(ctx.currentTime);
        osc.stop(ctx.currentTime + 0.2);
    }
    catch (e) { /* Audio not available */ }
}
function playDeath() {
    try {
        const ctx = getAudio();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(400, ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(30, ctx.currentTime + 1.0);
        gain.gain.setValueAtTime(0.3, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.2);
        osc.start(ctx.currentTime);
        osc.stop(ctx.currentTime + 1.2);
    }
    catch (e) { /* Audio not available */ }
}
function playEnemyDeath() {
    try {
        const ctx = getAudio();
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.type = 'square';
        osc.frequency.setValueAtTime(500, ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(80, ctx.currentTime + 0.4);
        gain.gain.setValueAtTime(0.25, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.45);
        osc.start(ctx.currentTime);
        osc.stop(ctx.currentTime + 0.45);
    }
    catch (e) { /* Audio not available */ }
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"d6295fc8-58d0-4f10-afe6-2808d209c8a2","tokensIn":1576901,"tokensOut":32988,"tokensTotal":1609889,"turns":54,"toolCalls":53,"failedToolCalls":3,"timestamp":"2026-07-31T18:39:21.372Z"} -->