DOOM
ThinkingCap Qwen3.6 27B GGUF · typescript

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"} -->