← DOOM results

DOOM

ThinkingCap Qwen3.6 27B 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 49,724 bytes · SHA-256 69f29e64b7c1
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SHADOW DUNGEON - Retro FPS</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; background: #000; overflow: hidden; display: flex; justify-content: center; align-items: center; }
body { display: flex; flex-direction: column; align-items: center; }
canvas { image-rendering: pixelated; image-rendering: crisp-edges; width: auto; height: auto; max-width: 100vw; max-height: 100vh; border: 2px solid #333; }
#hud { width: 640px; height: 48px; background: #111; border: 2px solid #333; border-top: none; display: flex; align-items: center; justify-content: space-around; font-family: 'Courier New', monospace; color: #0f0; font-size: 14px; font-weight: bold; image-rendering: pixelated; }
.hud-item { display: flex; flex-direction: column; align-items: center; }
.hud-label { font-size: 10px; color: #0a0; }
.hud-value { font-size: 18px; }
</style>
</head>
<body>
<canvas id="game" width="640" height="480"></canvas>
<div id="hud"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./level":"src/level.ts","./input":"src/input.ts","./engine":"src/engine.ts","./textures":"src/textures.ts"},"src/level.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts"},"src/engine.ts":{"./types":"src/types.ts","./renderer":"src/renderer.ts","./input":"src/input.ts","./textures":"src/textures.ts"},"src/textures.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";
Object.defineProperty(exports, "__esModule", { value: true });
const level_1 = require("./level");
const input_1 = require("./input");
const engine_1 = require("./engine");
const textures_1 = require("./textures");
(0, textures_1.initTextures)();
(0, input_1.setupInput)();
const state = (0, level_1.createInitialState)();
(0, level_1.spawnEnemies)(state);
(0, level_1.spawnPickups)(state);
(0, engine_1.startGame)(state);
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_AMMO = exports.WEAPONS = exports.MAX_HEALTH = exports.SPRINT_MULT = exports.ROT_SPEED = exports.MOVE_SPEED = exports.FOV = exports.TILE = void 0;
// Game constants
exports.TILE = 1;
exports.FOV = Math.PI / 3;
exports.MOVE_SPEED = 3.0;
exports.ROT_SPEED = 2.5;
exports.SPRINT_MULT = 1.6;
exports.MAX_HEALTH = 100;
// Weapon definitions
exports.WEAPONS = {
    pistol: { name: 'Pistol', damage: 15, fireRate: 300, ammoUse: 1, spread: 0.02, pellets: 1, range: 20, color: '#ff0' },
    shotgun: { name: 'Shotgun', damage: 10, fireRate: 700, ammoUse: 2, spread: 0.12, pellets: 6, range: 12, color: '#f80' },
    plasma: { name: 'Plasma', damage: 30, fireRate: 180, ammoUse: 3, spread: 0.04, pellets: 1, range: 25, color: '#0ff' },
};
exports.MAX_AMMO = {
    pistol: 50,
    shotgun: 30,
    plasma: 20,
};
};

// ── module: src/level.ts ──
__mods["src/level.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInitialState = createInitialState;
exports.spawnEnemies = spawnEnemies;
exports.spawnPickups = spawnPickups;
const types_1 = require("./types");
// Map layout: 1=stone, 2=brick, 3=metal, 4=floor detail, 5=door
// 0 = empty space
const mapData = [
    [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, 0, 0, 3, 3, 3, 0, 0, 0, 0, 4, 4, 4, 0, 0, 1],
    [1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1],
    [1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 4, 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, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 1],
    [1, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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],
];
function createInitialState() {
    return {
        player: {
            x: 1.5, z: 1.5, angle: 0, pitch: 0,
            health: types_1.MAX_HEALTH,
            ammo: { pistol: types_1.MAX_AMMO.pistol, shotgun: types_1.MAX_AMMO.shotgun, plasma: types_1.MAX_AMMO.plasma },
            currentWeapon: 'pistol',
            weapons: ['pistol', 'shotgun', 'plasma'],
            alive: true,
            deadTimer: 0,
            damageFlash: 0,
            killCount: 0,
        },
        enemies: [],
        pickups: [],
        map: mapData,
        mapWidth: mapData[0].length,
        mapHeight: mapData.length,
        level: 1,
        weaponState: { cooldown: 0, animTimer: 0, firing: false, muzzleFlash: 0 },
        screenShake: 0,
        message: '',
        messageTimer: 0,
        gameOver: false,
        won: false,
        totalEnemies: 0,
        started: false,
    };
}
// Place enemies at predefined positions
function spawnEnemies(state) {
    const positions = [
        { x: 5, z: 5, type: 'grunt' },
        { x: 10, z: 3, type: 'soldier' },
        { x: 15, z: 4, type: 'grunt' },
        { x: 8, z: 8, type: 'brute' },
        { x: 18, z: 2, type: 'soldier' },
        { x: 12, z: 10, type: 'grunt' },
        { x: 3, z: 12, type: 'soldier' },
        { x: 16, z: 14, type: 'brute' },
        { x: 6, z: 16, type: 'grunt' },
        { x: 14, z: 18, type: 'soldier' },
        { x: 20, z: 10, type: 'boss' },
        { x: 10, z: 15, type: 'grunt' },
    ];
    for (const pos of positions) {
        state.enemies.push({
            x: pos.x, z: pos.z, alive: true,
            hp: pos.type === 'boss' ? 200 : pos.type === 'brute' ? 80 : pos.type === 'soldier' ? 50 : 30,
            maxHp: pos.type === 'boss' ? 200 : pos.type === 'brute' ? 80 : pos.type === 'soldier' ? 50 : 30,
            speed: pos.type === 'boss' ? 1.2 : pos.type === 'brute' ? 1.0 : 1.5,
            damage: pos.type === 'boss' ? 20 : pos.type === 'brute' ? 15 : pos.type === 'soldier' ? 10 : 5,
            attackRange: pos.type === 'boss' ? 3 : 2,
            attackRate: pos.type === 'boss' ? 800 : 1000,
            attackCooldown: 0,
            state: 'idle',
            deathTimer: 0,
            type: pos.type,
            spawnDelay: 2 + Math.random() * 2,
            textureOffset: Math.random() * 1000,
            hitFlash: 0,
            hitFlashTimer: 0,
        });
    }
    state.totalEnemies = positions.length;
}
// Place pickups
function spawnPickups(state) {
    const positions = [
        { x: 4, z: 4, type: 'health' },
        { x: 8, z: 2, type: 'ammo' },
        { x: 15, z: 2, type: 'ammo' },
        { x: 20, z: 5, type: 'healthBig' },
        { x: 10, z: 8, type: 'ammoBig' },
        { x: 3, z: 14, type: 'health' },
        { x: 16, z: 12, type: 'ammo' },
        { x: 6, z: 18, type: 'healthBig' },
        { x: 12, z: 16, type: 'ammo' },
        { x: 20, z: 15, type: 'health' },
    ];
    for (const pos of positions) {
        state.pickups.push({
            x: pos.x, z: pos.z,
            alive: true,
            type: pos.type,
            respawnTimer: 0,
            bobPhase: Math.random() * Math.PI * 2,
        });
    }
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.projectEnemy = projectEnemy;
exports.projectPickup = projectPickup;
exports.getWallColor = getWallColor;
const WALL_COLORS = {
    1: [100, 100, 100], // stone
    2: [139, 69, 19], // brick
    3: [80, 80, 100], // metal
    4: [60, 80, 60], // forest
    5: [120, 100, 60], // door
};
function castRay(state, px, pz, angle) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    // DDA raycasting
    const map = state.map;
    const mw = state.mapWidth;
    const mh = state.mapHeight;
    let mapX = Math.floor(px);
    let mapZ = Math.floor(pz);
    const stepX = cos >= 0 ? 1 : -1;
    const stepZ = sin >= 0 ? 1 : -1;
    const deltaDistX = Math.abs(1 / (cos || 0.0001));
    const deltaDistZ = Math.abs(1 / (sin || 0.0001));
    let sideDistX = cos >= 0 ? (mapX + 1 - px) * deltaDistX : (px - mapX) * deltaDistX;
    let sideDistZ = sin >= 0 ? (mapZ + 1 - pz) * deltaDistZ : (pz - mapZ) * deltaDistZ;
    let side = 0;
    let maxSteps = 64;
    while (maxSteps-- > 0) {
        if (sideDistX < sideDistZ) {
            sideDistX += deltaDistX;
            mapX += stepX;
            side = 0;
        }
        else {
            sideDistZ += deltaDistZ;
            mapZ += stepZ;
            side = 1;
        }
        if (mapX < 0 || mapX >= mw || mapZ < 0 || mapZ >= mh)
            break;
        const tile = map[mapZ][mapX];
        if (tile > 0) {
            let perpDist = side === 0 ? sideDistX - deltaDistX : sideDistZ - deltaDistZ;
            let texX;
            if (side === 0) {
                texX = pz + perpDist * sin;
                texX -= Math.floor(texX);
            }
            else {
                texX = px + perpDist * cos;
                texX -= Math.floor(texX);
            }
            return { dist: perpDist, wallType: tile, side, texX, isWall: true };
        }
    }
    return { dist: 64, wallType: 0, side: 0, texX: 0, isWall: false };
}
function projectEnemy(state, enemy, camX, camZ, camDirX, camDirZ, camPlaneX, camPlaneZ, width, height) {
    if (!enemy.alive && enemy.deathTimer <= 0)
        return null;
    const dx = enemy.x - camX;
    const dz = enemy.z - camZ;
    const invDet = 1.0 / (camDirX * camPlaneZ - camPlaneX * camDirZ);
    const transformX = invDet * (camPlaneZ * dx - camDirZ * dz);
    const transformZ = invDet * (-camPlaneX * dx + camDirX * dz);
    if (transformZ <= 0.1)
        return null;
    const screenX = Math.floor((width / 2) * (1 + transformX / transformZ));
    const spriteHeight = Math.abs(Math.floor(height / transformZ));
    const spriteWidth = Math.floor(spriteHeight * 0.6);
    return {
        screenX,
        size: spriteWidth,
        clipTop: Math.floor((height - spriteHeight) / 2),
        clipBot: Math.floor((height + spriteHeight) / 2),
        visible: true,
        dist: transformZ,
    };
}
function projectPickup(state, pickup, camX, camZ, camDirX, camDirZ, camPlaneX, camPlaneZ, width, height) {
    if (!pickup.alive)
        return null;
    const dx = pickup.x - camX;
    const dz = pickup.z - camZ;
    const invDet = 1.0 / (camDirX * camPlaneZ - camPlaneX * camDirZ);
    const transformX = invDet * (camPlaneZ * dx - camDirZ * dz);
    const transformZ = invDet * (-camPlaneX * dx + camDirX * dz);
    if (transformZ <= 0.1)
        return null;
    const screenX = Math.floor((width / 2) * (1 + transformX / transformZ));
    const spriteHeight = Math.floor(Math.abs(Math.floor(height / transformZ)));
    const size = Math.floor(spriteHeight * 0.3);
    return {
        screenX,
        size,
        clipTop: Math.floor((height - spriteHeight * 0.5) / 2),
        clipBot: Math.floor((height + spriteHeight * 0.5) / 2),
        visible: true,
        dist: transformZ,
    };
}
function getWallColor(tile, side, dist) {
    const base = WALL_COLORS[tile] || [100, 100, 100];
    const shade = side === 1 ? 0.7 : 1.0;
    const fog = Math.max(0.15, 1.0 - dist / 20);
    const mult = shade * fog;
    const r = Math.floor(base[0] * mult);
    const g = Math.floor(base[1] * mult);
    const b = Math.floor(base[2] * mult);
    return `rgb(${r},${g},${b})`;
}
};

// ── 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.setupInput = setupInput;
exports.isKeyDown = isKeyDown;
exports.keys = {};
function setupInput() {
    window.addEventListener('keydown', (e) => {
        exports.keys[e.code] = true;
        e.preventDefault();
    });
    window.addEventListener('keyup', (e) => {
        exports.keys[e.code] = false;
        e.preventDefault();
    });
}
function isKeyDown(code) {
    return !!exports.keys[code];
}
};

// ── module: src/engine.ts ──
__mods["src/engine.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.startGame = startGame;
const types_1 = require("./types");
const renderer_1 = require("./renderer");
const input_1 = require("./input");
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
let state;
let lastTime = 0;
let mouseX = 0;
let mouseLocked = false;
let shooting = false;
function startGame(initState) {
    state = initState;
    canvas.addEventListener('click', () => {
        if (!state.started) {
            state.started = true;
            showMessage('Eliminate all enemies!', 3);
        }
        canvas.requestPointerLock();
    });
    document.addEventListener('pointerlockchange', () => {
        mouseLocked = document.pointerLockElement === canvas;
    });
    document.addEventListener('mousemove', (e) => {
        if (mouseLocked) {
            mouseX = e.movementX;
        }
    });
    document.addEventListener('mousedown', (e) => {
        if (e.button === 0)
            shooting = true;
    });
    document.addEventListener('mouseup', (e) => {
        if (e.button === 0)
            shooting = false;
    });
    lastTime = performance.now();
    requestAnimationFrame(gameLoop);
}
function gameLoop(now) {
    const dt = Math.min((now - lastTime) / 1000, 0.05);
    lastTime = now;
    if (state.started) {
        update(dt);
    }
    render();
    updateHUD();
    requestAnimationFrame(gameLoop);
}
function update(dt) {
    if (!state.player.alive) {
        state.player.deadTimer -= dt;
        if (state.player.deadTimer <= 0)
            respawnPlayer();
        return;
    }
    // Handle start screen
    if (!state.started) {
        if ((0, input_1.isKeyDown)('Space') || (0, input_1.isKeyDown)('Enter')) {
            state.started = true;
            showMessage('Eliminate all enemies!', 3);
        }
        return;
    }
    updatePlayer(dt);
    updateEnemies(dt);
    updatePickups(dt);
    updateWeapon(dt);
    updateMessages(dt);
    // Screen shake decay
    if (state.screenShake > 0)
        state.screenShake *= 0.9;
    if (state.screenShake < 0.1)
        state.screenShake = 0;
    // Damage flash decay
    if (state.player.damageFlash > 0)
        state.player.damageFlash -= dt * 2;
}
function updatePlayer(dt) {
    const p = state.player;
    const map = state.map;
    // Rotation (mouse + keyboard)
    const mouseSensitivity = 0.002;
    p.angle += mouseX * mouseSensitivity;
    mouseX = 0;
    if ((0, input_1.isKeyDown)('ArrowLeft') || (0, input_1.isKeyDown)('KeyQ'))
        p.angle -= types_1.ROT_SPEED * dt;
    if ((0, input_1.isKeyDown)('ArrowRight') || (0, input_1.isKeyDown)('KeyE'))
        p.angle += types_1.ROT_SPEED * dt;
    // Movement
    const speed = types_1.MOVE_SPEED * ((0, input_1.isKeyDown)('ShiftLeft') ? types_1.SPRINT_MULT : 1);
    let moveX = 0;
    let moveZ = 0;
    if ((0, input_1.isKeyDown)('KeyW') || (0, input_1.isKeyDown)('ArrowUp')) {
        moveX += Math.cos(p.angle) * speed * dt;
        moveZ += Math.sin(p.angle) * speed * dt;
    }
    if ((0, input_1.isKeyDown)('KeyS') || (0, input_1.isKeyDown)('ArrowDown')) {
        moveX -= Math.cos(p.angle) * speed * dt;
        moveZ -= Math.sin(p.angle) * speed * dt;
    }
    if ((0, input_1.isKeyDown)('KeyA')) {
        moveX += Math.cos(p.angle - Math.PI / 2) * speed * dt;
        moveZ += Math.sin(p.angle - Math.PI / 2) * speed * dt;
    }
    if ((0, input_1.isKeyDown)('KeyD')) {
        moveX -= Math.cos(p.angle - Math.PI / 2) * speed * dt;
        moveZ -= Math.sin(p.angle - Math.PI / 2) * speed * dt;
    }
    // Collision
    const radius = 0.2;
    const newX = p.x + moveX;
    const newZ = p.z + moveZ;
    if (map[Math.floor(p.z)] && map[Math.floor(p.z)][Math.floor(newX)] === 0) {
        p.x = newX;
    }
    if (map[Math.floor(newZ)] && map[Math.floor(newZ)][Math.floor(p.x)] === 0) {
        p.z = newZ;
    }
    // Weapon switching
    if ((0, input_1.isKeyDown)('Digit1'))
        p.currentWeapon = 'pistol';
    if ((0, input_1.isKeyDown)('Digit2') && p.weapons.includes('shotgun'))
        p.currentWeapon = 'shotgun';
    if ((0, input_1.isKeyDown)('Digit3') && p.weapons.includes('plasma'))
        p.currentWeapon = 'plasma';
    // Shooting
    if (shooting) {
        fireWeapon();
    }
}
function fireWeapon() {
    const p = state.player;
    const ws = state.weaponState;
    const weapon = types_1.WEAPONS[p.currentWeapon];
    if (ws.cooldown > 0)
        return;
    if (p.ammo[p.currentWeapon] <= 0) {
        // Try to switch to another weapon with ammo
        let switched = false;
        for (const w of p.weapons) {
            if (w !== p.currentWeapon && p.ammo[w] > 0) {
                p.currentWeapon = w;
                switched = true;
                break;
            }
        }
        if (!switched)
            return;
    }
    ws.cooldown = weapon.fireRate;
    ws.firing = true;
    ws.animTimer = 0.15;
    p.ammo[p.currentWeapon] -= weapon.ammoUse;
    if (p.ammo[p.currentWeapon] < 0)
        p.ammo[p.currentWeapon] = 0;
    // Fire pellets
    for (let i = 0; i < weapon.pellets; i++) {
        const spread = (Math.random() - 0.5) * weapon.spread;
        const fireAngle = p.angle + spread;
        hitEnemy(p.x, p.z, fireAngle, weapon.damage, weapon.range);
    }
    state.screenShake = weapon.name === 'Plasma' ? 3 : 5;
    ws.muzzleFlash = 0.15;
}
function hitEnemy(px, pz, angle, damage, range) {
    // Find closest enemy in line of fire
    let closestDist = range;
    let closestEnemy = null;
    for (let i = 0; i < state.enemies.length; i++) {
        const e = state.enemies[i];
        if (!e.alive)
            continue;
        const dx = e.x - px;
        const dz = e.z - pz;
        const dist = Math.sqrt(dx * dx + dz * dz);
        if (dist > range)
            continue;
        const enemyAngle = Math.atan2(dz, dx);
        let angleDiff = Math.abs(normalizeAngle(enemyAngle - angle));
        // Check if enemy is in crosshair (wider hit for closer enemies)
        const hitAngle = Math.max(0.05, 0.3 / dist);
        if (angleDiff < hitAngle) {
            // Check line of sight
            if (hasLineOfSight(px, pz, e.x, e.z)) {
                if (dist < closestDist) {
                    closestDist = dist;
                    closestEnemy = i;
                }
            }
        }
    }
    if (closestEnemy !== null) {
        const e = state.enemies[closestEnemy];
        const distDmg = damage * Math.max(0.5, 1 - closestDist / range);
        e.hp -= distDmg;
        e.hitFlash = 1.0;
        e.hitFlashTimer = 0.15;
        if (e.hp <= 0) {
            e.alive = false;
            e.state = 'dead';
            e.deathTimer = 2;
            state.player.killCount++;
            if (state.player.killCount >= state.totalEnemies) {
                state.won = true;
                state.gameOver = true;
                showMessage('VICTORY! All enemies eliminated!', 10);
            }
        }
    }
}
function hasLineOfSight(x1, z1, x2, z2) {
    const dx = x2 - x1;
    const dz = z2 - z1;
    const dist = Math.sqrt(dx * dx + dz * dz);
    const steps = Math.ceil(dist * 4);
    for (let i = 0; i < steps; i++) {
        const t = i / steps;
        const cx = x1 + dx * t;
        const cz = z1 + dz * t;
        const mx = Math.floor(cx);
        const mz = Math.floor(cz);
        if (mx < 0 || mx >= state.mapWidth || mz < 0 || mz >= state.mapHeight)
            return false;
        if (state.map[mz][mx] > 0)
            return false;
    }
    return true;
}
function normalizeAngle(a) {
    while (a > Math.PI)
        a -= 2 * Math.PI;
    while (a < -Math.PI)
        a += 2 * Math.PI;
    return a;
}
function updateEnemies(dt) {
    const p = state.player;
    for (const e of state.enemies) {
        if (!e.alive) {
            e.deathTimer -= dt;
            continue;
        }
        // Spawn delay - enemies don't attack immediately
        if (e.spawnDelay > 0) {
            e.spawnDelay -= dt;
            continue;
        }
        // Hit flash
        if (e.hitFlashTimer > 0) {
            e.hitFlashTimer -= dt;
            if (e.hitFlashTimer <= 0)
                e.hitFlash = 0;
        }
        const dx = p.x - e.x;
        const dz = p.z - e.z;
        const dist = Math.sqrt(dx * dx + dz * dz);
        // State transitions
        if (dist < 15 && p.alive) {
            e.state = 'chase';
        }
        else if (dist > 18) {
            e.state = 'idle';
        }
        if (e.state === 'idle')
            continue;
        if (e.state === 'chase') {
            // Move toward player
            const angle = Math.atan2(dz, dx);
            const moveX = Math.cos(angle) * e.speed * dt;
            const moveZ = Math.sin(angle) * e.speed * dt;
            // Collision avoidance
            const newEX = e.x + moveX;
            const newEZ = e.z + moveZ;
            const mx = Math.floor(newEX);
            const mz = Math.floor(newEZ);
            if (mx >= 0 && mx < state.mapWidth && mz >= 0 && mz < state.mapHeight) {
                if (state.map[mz][mx] === 0) {
                    e.x = newEX;
                    e.z = newEZ;
                }
                else if (state.map[Math.floor(e.z)][mx] === 0) {
                    e.x = newEX;
                }
                else if (state.map[mz][Math.floor(e.x)] === 0) {
                    e.z = newEZ;
                }
            }
            // Attack
            if (dist < e.attackRange) {
                e.state = 'attack';
                e.attackCooldown -= dt * 1000;
                if (e.attackCooldown <= 0) {
                    p.health -= e.damage;
                    p.damageFlash = 1.0;
                    state.screenShake = 8;
                    e.attackCooldown = e.attackRate;
                    if (p.health <= 0) {
                        p.health = 0;
                        p.alive = false;
                        p.deadTimer = 3;
                        showMessage('YOU DIED', 2);
                    }
                }
            }
        }
    }
}
function updatePickups(dt) {
    const p = state.player;
    for (const pk of state.pickups) {
        if (!pk.alive) {
            pk.respawnTimer -= dt;
            if (pk.respawnTimer <= 0)
                pk.alive = true;
            continue;
        }
        pk.bobPhase += dt * 3;
        const dx = p.x - pk.x;
        const dz = p.z - pk.z;
        const dist = Math.sqrt(dx * dx + dz * dz);
        if (dist < 0.5 && p.alive) {
            if (pk.type === 'health' || pk.type === 'healthBig') {
                const heal = pk.type === 'healthBig' ? 50 : 25;
                p.health = Math.min(types_1.MAX_HEALTH, p.health + heal);
                showMessage(`+${heal} Health`, 1.5);
            }
            else {
                const amount = pk.type === 'ammoBig' ? 15 : 5;
                p.ammo.pistol = Math.min(types_1.MAX_AMMO.pistol, p.ammo.pistol + amount);
                p.ammo.shotgun = Math.min(types_1.MAX_AMMO.shotgun, p.ammo.shotgun + Math.floor(amount / 2));
                p.ammo.plasma = Math.min(types_1.MAX_AMMO.plasma, p.ammo.plasma + Math.floor(amount / 2));
                showMessage(`+${amount} Ammo`, 1.5);
            }
            pk.alive = false;
            pk.respawnTimer = 15;
        }
    }
}
function updateWeapon(dt) {
    const ws = state.weaponState;
    if (ws.cooldown > 0)
        ws.cooldown -= dt * 1000;
    else
        ws.firing = false;
    if (ws.animTimer > 0)
        ws.animTimer -= dt;
    if (ws.muzzleFlash > 0)
        ws.muzzleFlash -= dt;
}
function updateMessages(dt) {
    if (state.messageTimer > 0) {
        state.messageTimer -= dt;
        if (state.messageTimer <= 0)
            state.message = '';
    }
}
function showMessage(msg, duration) {
    state.message = msg;
    state.messageTimer = duration;
}
function respawnPlayer() {
    state.player.x = 2;
    state.player.z = 2;
    state.player.angle = 0;
    state.player.health = types_1.MAX_HEALTH;
    state.player.alive = true;
    state.player.ammo = { pistol: types_1.MAX_AMMO.pistol, shotgun: types_1.MAX_AMMO.shotgun, plasma: types_1.MAX_AMMO.plasma };
    showMessage('Respawned!', 2);
}
// =========== RENDERING ===========
function render() {
    if (!state.started) {
        drawStartScreen();
        return;
    }
    const p = state.player;
    // Shake offset
    const shakeX = state.screenShake ? (Math.random() - 0.5) * state.screenShake * 2 : 0;
    const shakeY = state.screenShake ? (Math.random() - 0.5) * state.screenShake * 2 : 0;
    // Cast rays
    const numRays = W;
    const halfFov = Math.PI / 6;
    // Z-buffer for sprite clipping
    const zBuf = new Float64Array(numRays);
    // Sky gradient
    const skyGrad = ctx.createLinearGradient(0, 0, 0, H / 2);
    skyGrad.addColorStop(0, '#0a0618');
    skyGrad.addColorStop(0.5, '#1a0a2e');
    skyGrad.addColorStop(1, '#2d1b4e');
    ctx.fillStyle = skyGrad;
    ctx.fillRect(0, 0, W, H / 2);
    // Floor gradient
    const floorGrad = ctx.createLinearGradient(0, H / 2, 0, H);
    floorGrad.addColorStop(0, '#111');
    floorGrad.addColorStop(0.5, '#1a1a1a');
    floorGrad.addColorStop(1, '#333');
    ctx.fillStyle = floorGrad;
    ctx.fillRect(0, H / 2, W, H / 2);
    // Wall colors with detail
    const wallRGB = {
        1: [110, 100, 100], // stone
        2: [160, 90, 50], // brick
        3: [80, 85, 110], // metal
        4: [60, 100, 60], // forest
        5: [130, 110, 70], // door
    };
    for (let i = 0; i < numRays; i++) {
        const rayAngle = p.angle - halfFov + (i / numRays) * (halfFov * 2);
        const hit = (0, renderer_1.castRay)(state, p.x, p.z, rayAngle);
        const perpDist = hit.dist;
        zBuf[i] = perpDist;
        if (!hit.isWall)
            continue;
        const lineH = Math.floor(H / perpDist);
        const drawStart = Math.floor((H - lineH) / 2);
        const x = Math.floor(i + shakeX);
        const yOff = Math.floor(shakeY);
        if (x < 0 || x >= W)
            continue;
        const fog = Math.max(0.12, 1.0 - perpDist / 22);
        const sideShade = hit.side === 1 ? 0.55 : 1.0;
        const base = wallRGB[hit.wallType] || [80, 80, 80];
        const mult = fog * sideShade;
        // Three-band texture: light base, dark stripe, light base
        const cr = Math.floor(base[0] * mult);
        const cg = Math.floor(base[1] * mult);
        const cb = Math.floor(base[2] * mult);
        const dr = Math.floor(cr * 0.65);
        const dg = Math.floor(cg * 0.65);
        const db = Math.floor(cb * 0.65);
        // Top section
        const h1 = Math.max(1, Math.floor(lineH * 0.3));
        ctx.fillStyle = `rgb(${cr},${cg},${cb})`;
        ctx.fillRect(x, drawStart + yOff, 1, h1);
        // Dark band
        const h2 = Math.max(1, Math.floor(lineH * 0.15));
        ctx.fillStyle = `rgb(${dr},${dg},${db})`;
        ctx.fillRect(x, drawStart + yOff + h1, 1, h2);
        // Bottom section
        ctx.fillStyle = `rgb(${cr},${cg},${cb})`;
        ctx.fillRect(x, drawStart + yOff + h1 + h2, 1, lineH - h1 - h2);
        // Edge lines (brick/stone seams)
        if (hit.texX < 0.04 || hit.texX > 0.96) {
            ctx.fillStyle = `rgb(${Math.floor(dr * 0.5)},${Math.floor(dg * 0.5)},${Math.floor(db * 0.5)})`;
            ctx.fillRect(x, drawStart + yOff, 1, lineH);
        }
    }
    const sprites = [];
    const camDirX = Math.cos(p.angle);
    const camDirZ = Math.sin(p.angle);
    const camPlaneX = -Math.sin(p.angle) * Math.tan(Math.PI / 6);
    const camPlaneZ = Math.cos(p.angle) * Math.tan(Math.PI / 6);
    for (const e of state.enemies) {
        if (!e.alive && e.deathTimer <= 0)
            continue;
        const proj = (0, renderer_1.projectEnemy)(state, e, p.x, p.z, camDirX, camDirZ, camPlaneX, camPlaneZ, W, H);
        if (proj)
            sprites.push({ dist: proj.dist, type: 'enemy', data: { enemy: e, proj } });
    }
    for (const pk of state.pickups) {
        if (!pk.alive)
            continue;
        const proj = (0, renderer_1.projectPickup)(state, pk, p.x, p.z, camDirX, camDirZ, camPlaneX, camPlaneZ, W, H);
        if (proj)
            sprites.push({ dist: proj.dist, type: 'pickup', data: { pickup: pk, proj } });
    }
    // Sort back to front
    sprites.sort((a, b) => b.dist - a.dist);
    // Draw sprites
    for (const s of sprites) {
        if (s.type === 'enemy') {
            drawEnemy(s.data.enemy, s.data.proj, zBuf);
        }
        else {
            drawPickupSprite(s.data.pickup, s.data.proj, zBuf);
        }
    }
    // Draw weapon
    drawWeapon();
    // Damage flash
    if (p.damageFlash > 0) {
        ctx.fillStyle = `rgba(255, 0, 0, ${p.damageFlash * 0.4})`;
        ctx.fillRect(0, 0, W, H);
    }
    // Crosshair
    ctx.strokeStyle = '#0f0';
    ctx.lineWidth = 1;
    const cx = W / 2;
    const cy = H / 2;
    ctx.beginPath();
    ctx.moveTo(cx - 8, cy);
    ctx.lineTo(cx - 3, cy);
    ctx.moveTo(cx + 3, cy);
    ctx.lineTo(cx + 8, cy);
    ctx.moveTo(cx, cy - 8);
    ctx.lineTo(cx, cy - 3);
    ctx.moveTo(cx, cy + 3);
    ctx.lineTo(cx, cy + 8);
    ctx.stroke();
    // Minimap
    drawMinimap();
    // Messages
    if (state.message && state.messageTimer > 0) {
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 20px Courier New';
        ctx.textAlign = 'center';
        ctx.fillText(state.message, W / 2, 60);
    }
    // Game over / victory
    if (state.gameOver) {
        ctx.fillStyle = 'rgba(0,0,0,0.7)';
        ctx.fillRect(0, 0, W, H);
        ctx.fillStyle = state.won ? '#0f0' : '#f00';
        ctx.font = 'bold 48px Courier New';
        ctx.textAlign = 'center';
        ctx.fillText(state.won ? 'VICTORY!' : 'GAME OVER', W / 2, H / 2 - 20);
        ctx.fillStyle = '#fff';
        ctx.font = '16px Courier New';
        ctx.fillText(`Enemies killed: ${state.player.killCount}/${state.totalEnemies}`, W / 2, H / 2 + 20);
        ctx.fillText('Press R to restart', W / 2, H / 2 + 50);
        if ((0, input_1.isKeyDown)('KeyR')) {
            // Reset game
            Promise.resolve().then(() => __importStar(require('./level'))).then(({ createInitialState, spawnEnemies, spawnPickups }) => {
                const ns = createInitialState();
                spawnEnemies(ns);
                spawnPickups(ns);
                state = ns;
                state.gameOver = false;
                state.started = false;
            });
        }
    }
}
function drawStartScreen() {
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, W, H);
    // Title
    ctx.fillStyle = '#f00';
    ctx.font = 'bold 48px Courier New';
    ctx.textAlign = 'center';
    ctx.fillText('SHADOW DUNGEON', W / 2, H / 3);
    ctx.fillStyle = '#aaa';
    ctx.font = '16px Courier New';
    ctx.fillText('A Retro FPS Adventure', W / 2, H / 3 + 30);
    ctx.fillStyle = '#0f0';
    ctx.font = '14px Courier New';
    ctx.fillText('WASD - Move | Mouse - Look | Click - Shoot', W / 2, H / 2);
    ctx.fillText('1/2/3 - Switch Weapons | Shift - Sprint', W / 2, H / 2 + 20);
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 20px Courier New';
    ctx.fillText('Press SPACE to Start', W / 2, H / 2 + 70);
}
function drawEnemy(enemy, proj, zBuf) {
    const { screenX, size, clipTop, clipBot } = proj;
    const halfW = Math.floor(size / 2);
    // Check z-buffer for each column
    let visibleCols = 0;
    for (let col = screenX - halfW; col <= screenX + halfW; col++) {
        if (col >= 0 && col < zBuf.length && zBuf[col] > proj.dist) {
            visibleCols++;
        }
    }
    if (visibleCols === 0)
        return;
    // Death animation
    let scale = 1;
    let alpha = 1;
    if (!enemy.alive) {
        scale = enemy.deathTimer / 2;
        alpha = enemy.deathTimer / 2;
    }
    const h = (clipBot - clipTop) * scale;
    const w = size * scale;
    // Enemy colors by type
    const colors = {
        grunt: '#4a2',
        soldier: '#a44',
        brute: '#844',
        boss: '#c0f',
    };
    const baseColor = colors[enemy.type] || '#f00';
    ctx.globalAlpha = alpha;
    // Body
    const bodyTop = clipTop + (clipBot - clipTop) * 0.3;
    ctx.fillStyle = enemy.hitFlash > 0 ? '#fff' : baseColor;
    ctx.fillRect(screenX - w / 2, bodyTop, w, h * 0.7);
    // Head
    ctx.fillStyle = enemy.hitFlash > 0 ? '#fff' : '#da6';
    const headSize = w * 0.6;
    ctx.fillRect(screenX - headSize / 2, clipTop + (clipBot - clipTop) * 0.05, headSize, headSize * 0.7);
    // Eyes
    if (enemy.alive) {
        ctx.fillStyle = '#f00';
        const eyeSize = Math.max(2, headSize * 0.15);
        ctx.fillRect(screenX - headSize * 0.25 - eyeSize / 2, clipTop + (clipBot - clipTop) * 0.15, eyeSize, eyeSize);
        ctx.fillRect(screenX + headSize * 0.25 - eyeSize / 2, clipTop + (clipBot - clipTop) * 0.15, eyeSize, eyeSize);
    }
    // Health bar
    if (enemy.alive && enemy.hp < enemy.maxHp) {
        const barW = w;
        const barH = 3;
        const barY = clipTop - 5;
        ctx.fillStyle = '#300';
        ctx.fillRect(screenX - barW / 2, barY, barW, barH);
        ctx.fillStyle = '#f00';
        ctx.fillRect(screenX - barW / 2, barY, barW * (enemy.hp / enemy.maxHp), barH);
    }
    ctx.globalAlpha = 1;
}
function drawPickupSprite(pickup, proj, zBuf) {
    const { screenX, size } = proj;
    if (screenX < 0 || screenX >= zBuf.length || zBuf[screenX] <= proj.dist)
        return;
    const bob = Math.sin(pickup.bobPhase) * size * 0.15;
    const py = (proj.clipTop + proj.clipBot) / 2 + bob;
    // Glow
    ctx.fillStyle = pickup.type.includes('health') ? 'rgba(0,255,0,0.3)' : 'rgba(255,255,0,0.3)';
    ctx.beginPath();
    ctx.arc(screenX, py, size * 0.8, 0, Math.PI * 2);
    ctx.fill();
    // Icon
    ctx.fillStyle = pickup.type.includes('health') ? '#0f0' : '#ff0';
    ctx.fillRect(screenX - size / 2, py - size / 2, size, size);
    // Inner symbol
    ctx.fillStyle = pickup.type.includes('health') ? '#0a0' : '#aa0';
    ctx.fillRect(screenX - size / 4, py - size / 4, size / 2, size / 2);
}
function drawWeapon() {
    const p = state.player;
    const ws = state.weaponState;
    const weaponX = W / 2 + 60;
    const weaponY = H - 10;
    // Bob animation
    const bobX = Math.sin(performance.now() / 200) * 3;
    const bobY = Math.abs(Math.cos(performance.now() / 200)) * 3;
    // Recoil
    const recoil = ws.animTimer > 0 ? ws.animTimer / 0.15 * 30 : 0;
    const wx = weaponX + bobX;
    const wy = weaponY + bobY - recoil;
    ctx.save();
    ctx.translate(wx, wy);
    if (p.currentWeapon === 'pistol') {
        // Pistol barrel
        ctx.fillStyle = '#555';
        ctx.fillRect(-6, -60, 12, 20);
        ctx.fillStyle = '#666';
        ctx.fillRect(-5, -58, 10, 16);
        // Grip
        ctx.fillStyle = '#754';
        ctx.fillRect(-12, -40, 24, 45);
        // Handle detail
        ctx.fillStyle = '#643';
        ctx.fillRect(-10, -35, 20, 35);
        // Trigger guard
        ctx.strokeStyle = '#555';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(8, -10, 8, 0, Math.PI);
        ctx.stroke();
        // Muzzle flash
        if (ws.muzzleFlash > 0) {
            const flashSize = ws.muzzleFlash * 40;
            ctx.fillStyle = '#ff8';
            ctx.beginPath();
            ctx.arc(0, -65, flashSize, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.arc(0, -65, flashSize * 0.4, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    else if (p.currentWeapon === 'shotgun') {
        // Shotgun barrel
        ctx.fillStyle = '#444';
        ctx.fillRect(-6, -70, 12, 30);
        ctx.fillStyle = '#555';
        ctx.fillRect(-5, -68, 10, 26);
        // Receiver
        ctx.fillStyle = '#543';
        ctx.fillRect(-14, -42, 28, 30);
        // Stock
        ctx.fillStyle = '#654';
        ctx.fillRect(-12, -10, 24, 50);
        // Pump
        ctx.fillStyle = '#444';
        ctx.fillRect(-8, -50, 16, 8);
        // Muzzle flash
        if (ws.muzzleFlash > 0) {
            const flashSize = ws.muzzleFlash * 50;
            ctx.fillStyle = '#fa0';
            ctx.beginPath();
            ctx.arc(0, -75, flashSize, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#ff8';
            ctx.beginPath();
            ctx.arc(0, -75, flashSize * 0.6, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.arc(0, -75, flashSize * 0.3, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    else {
        // Plasma gun
        ctx.fillStyle = '#224';
        ctx.fillRect(-18, -35, 36, 40);
        ctx.fillStyle = '#336';
        ctx.fillRect(-16, -33, 32, 36);
        // Energy core
        ctx.fillStyle = '#226';
        ctx.fillRect(-10, -55, 20, 25);
        // Glowing orb
        ctx.fillStyle = '#0af';
        ctx.beginPath();
        ctx.arc(0, -60, 10, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#0df';
        ctx.beginPath();
        ctx.arc(0, -60, 6, 0, Math.PI * 2);
        ctx.fill();
        // Muzzle flash
        if (ws.muzzleFlash > 0) {
            const flashSize = ws.muzzleFlash * 55;
            ctx.fillStyle = 'rgba(0,255,255,0.6)';
            ctx.beginPath();
            ctx.arc(0, -65, flashSize, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#0ff';
            ctx.beginPath();
            ctx.arc(0, -65, flashSize * 0.7, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.arc(0, -65, flashSize * 0.3, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    ctx.restore();
}
function drawMinimap() {
    const mmSize = 4;
    const mmX = 10;
    const mmY = H - 10 - state.mapHeight * mmSize;
    ctx.globalAlpha = 0.6;
    for (let z = 0; z < state.mapHeight; z++) {
        for (let x = 0; x < state.mapWidth; x++) {
            const tile = state.map[z][x];
            if (tile > 0) {
                ctx.fillStyle = '#555';
                ctx.fillRect(mmX + x * mmSize, mmY + z * mmSize, mmSize, mmSize);
            }
        }
    }
    // Player
    ctx.fillStyle = '#0f0';
    ctx.fillRect(mmX + Math.floor(state.player.x) * mmSize - 1, mmY + Math.floor(state.player.z) * mmSize - 1, 3, 3);
    // Enemies
    for (const e of state.enemies) {
        if (!e.alive)
            continue;
        ctx.fillStyle = '#f00';
        ctx.fillRect(mmX + Math.floor(e.x) * mmSize, mmY + Math.floor(e.z) * mmSize, 2, 2);
    }
    ctx.globalAlpha = 1;
}
function updateHUD() {
    const hud = document.getElementById('hud');
    const p = state.player;
    const weapon = types_1.WEAPONS[p.currentWeapon];
    const healthColor = p.health > 60 ? '#0f0' : p.health > 30 ? '#ff0' : '#f00';
    const ammoColor = p.ammo[p.currentWeapon] > 0 ? '#0ff' : '#f00';
    hud.innerHTML = `
    <div class="hud-item">
      <span class="hud-label">HEALTH</span>
      <span class="hud-value" style="color:${healthColor}">${Math.ceil(p.health)}</span>
    </div>
    <div class="hud-item">
      <span class="hud-label">${weapon.name.toUpperCase()}</span>
      <span class="hud-value" style="color:${ammoColor}">${p.ammo[p.currentWeapon]}</span>
    </div>
    <div class="hud-item">
      <span class="hud-label">KILLS</span>
      <span class="hud-value">${p.killCount}/${state.totalEnemies}</span>
    </div>
    <div class="hud-item">
      <span class="hud-label">WEAPONS</span>
      <span class="hud-value">
        [1]Pistol [2]Shotgun [3]Plasma
      </span>
    </div>
  `;
}
};

// ── module: src/textures.ts ──
__mods["src/textures.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.wallTextures = exports.TEXTURE_SIZE = void 0;
exports.initTextures = initTextures;
exports.getTextureColor = getTextureColor;
exports.getTextureColumn = getTextureColumn;
// Pre-rendered wall textures for each tile type
exports.TEXTURE_SIZE = 64;
exports.wallTextures = [];
function generateStoneTexture() {
    const img = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE);
    for (let y = 0; y < exports.TEXTURE_SIZE; y++) {
        for (let x = 0; x < exports.TEXTURE_SIZE; x++) {
            const i = (y * exports.TEXTURE_SIZE + x) * 4;
            const nx = (x % 32) + (y % 16 < 8 ? 0 : 16);
            // Stone block pattern
            const base = 80 + Math.sin(nx * 0.5 + y * 0.3) * 15;
            const noise = (Math.sin(x * 13.7 + y * 7.3) * 0.5 + 0.5) * 20;
            // Mortar lines
            const mortarH = (y % 16 < 1) ? 0.5 : 1;
            const mortarV = (x % 32 < 1) ? 0.5 : 1;
            const v = (base + noise) * mortarH * mortarV;
            img.data[i] = v * 0.7;
            img.data[i + 1] = v * 0.7;
            img.data[i + 2] = v * 0.8;
            img.data[i + 3] = 255;
        }
    }
    return img;
}
function generateBrickTexture() {
    const img = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE);
    for (let y = 0; y < exports.TEXTURE_SIZE; y++) {
        for (let x = 0; x < exports.TEXTURE_SIZE; x++) {
            const i = (y * exports.TEXTURE_SIZE + x) * 4;
            const brickH = y % 16;
            const offset = (Math.floor(y / 16) % 2) * 16;
            const brickX = (x + offset) % 32;
            // Mortar
            if (brickH < 2 || brickX < 2) {
                img.data[i] = 50;
                img.data[i + 1] = 45;
                img.data[i + 2] = 35;
            }
            else {
                const base = 120 + Math.sin(x * 0.8 + y * 0.5) * 15;
                const noise = (Math.sin(x * 17 + y * 11) * 0.5 + 0.5) * 25;
                img.data[i] = Math.min(255, base + noise + 40);
                img.data[i + 1] = Math.min(255, base + noise - 10);
                img.data[i + 2] = Math.min(255, base + noise - 30);
            }
            img.data[i + 3] = 255;
        }
    }
    return img;
}
function generateMetalTexture() {
    const img = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE);
    for (let y = 0; y < exports.TEXTURE_SIZE; y++) {
        for (let x = 0; x < exports.TEXTURE_SIZE; x++) {
            const i = (y * exports.TEXTURE_SIZE + x) * 4;
            // Horizontal panel lines
            const panel = (y % 32 > 1) ? 1 : 0.5;
            // Rivets
            const rivetDist = Math.sqrt((x % 32 - 16) ** 2 + (y % 32 - 16) ** 2);
            const rivet = rivetDist < 3 ? 1.3 : 1;
            const base = 70 + Math.sin(x * 0.3 + y * 0.1) * 10;
            const noise = (Math.sin(x * 19 + y * 7) * 0.5 + 0.5) * 15;
            const v = (base + noise) * panel * rivet;
            img.data[i] = v * 0.7;
            img.data[i + 1] = v * 0.75;
            img.data[i + 2] = v * 1.1;
            img.data[i + 3] = 255;
        }
    }
    return img;
}
function generateForestTexture() {
    const img = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE);
    for (let y = 0; y < exports.TEXTURE_SIZE; y++) {
        for (let x = 0; x < exports.TEXTURE_SIZE; x++) {
            const i = (y * exports.TEXTURE_SIZE + x) * 4;
            const v1 = Math.sin(x * 0.4 + y * 0.2) * 20;
            const v2 = Math.sin(x * 2.1 + y * 3.7) * 8;
            const base = 60 + v1 + v2;
            img.data[i] = base * 0.5;
            img.data[i + 1] = base * 1.1;
            img.data[i + 2] = base * 0.5;
            img.data[i + 3] = 255;
        }
    }
    return img;
}
function generateDoorTexture() {
    const img = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE);
    for (let y = 0; y < exports.TEXTURE_SIZE; y++) {
        for (let x = 0; x < exports.TEXTURE_SIZE; x++) {
            const i = (y * exports.TEXTURE_SIZE + x) * 4;
            const edge = (x < 4 || x > 59 || y < 4 || y > 59) ? 0.6 : 1;
            const base = 100 + Math.sin(x * 0.5 + y * 0.3) * 10;
            const noise = (Math.sin(x * 11 + y * 13) * 0.5 + 0.5) * 20;
            // Horizontal panels
            const panel = (y % 16 > 2 && y % 16 < 14) ? 1 : 0.7;
            const v = (base + noise) * edge * panel;
            img.data[i] = v * 1.1;
            img.data[i + 1] = v * 0.9;
            img.data[i + 2] = v * 0.5;
            img.data[i + 3] = 255;
        }
    }
    return img;
}
function initTextures() {
    exports.wallTextures[0] = new ImageData(exports.TEXTURE_SIZE, exports.TEXTURE_SIZE); // empty, unused
    exports.wallTextures[1] = generateStoneTexture();
    exports.wallTextures[2] = generateBrickTexture();
    exports.wallTextures[3] = generateMetalTexture();
    exports.wallTextures[4] = generateForestTexture();
    exports.wallTextures[5] = generateDoorTexture();
}
function getTextureColor(tile, texX, side) {
    const tex = exports.wallTextures[tile];
    if (!tex)
        return [100, 100, 100];
    const tx = Math.floor(texX * exports.TEXTURE_SIZE) % exports.TEXTURE_SIZE;
    const ty = Math.floor((Math.random() * 0.3 + 0.5) * exports.TEXTURE_SIZE); // Use full texture height
    // Actually sample at proper texY - we'll iterate y in render
    const idx = (0 * exports.TEXTURE_SIZE + tx) * 4; // placeholder
    return [tex.data[idx], tex.data[idx + 1], tex.data[idx + 2]];
}
// Get full column of texture
function getTextureColumn(tile, texX, lineH) {
    const tex = exports.wallTextures[tile];
    if (!tex)
        return null;
    const tx = Math.floor(texX * exports.TEXTURE_SIZE) % exports.TEXTURE_SIZE;
    const data = new Uint8ClampedArray(lineH * 4);
    for (let y = 0; y < lineH; y++) {
        const ty = Math.floor((y / lineH) * exports.TEXTURE_SIZE) % exports.TEXTURE_SIZE;
        const tIdx = (ty * exports.TEXTURE_SIZE + tx) * 4;
        const dIdx = y * 4;
        data[dIdx] = tex.data[tIdx];
        data[dIdx + 1] = tex.data[tIdx + 1];
        data[dIdx + 2] = tex.data[tIdx + 2];
        data[dIdx + 3] = 255;
    }
    return data;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"ThinkingCap-Qwen3.6-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"75b1bd55-18b6-4778-be17-49c31591ecd4","tokensIn":3026566,"tokensOut":41255,"tokensTotal":3067821,"cost":0,"turns":84,"toolCalls":83,"failedToolCalls":0,"timestamp":"2026-08-03T19:29:03.186Z"} -->