mager-bench

Claude Haiku 4.5 × doom

0.7

DDA raycaster + textures + door + minimap + Z-buffer — the signature challenge

correctness 0.0quality 1.0documentation 1.010435ms
$ cat doom.prompt — what the model was asked
Implement a first-person 3D raycasting engine in a single self-contained HTML file with no external libraries, no external images, and no CDN scripts. This is the hardest challenge in the benchmark. Partial credit is given per requirement met.

## Rendering
- DDA (Digital Differential Analysis) raycasting — not a simplified ray-box approximation
- Fish-eye correction applied to all wall distances
- **Procedurally generated wall textures** using canvas math only (no image files, no data URIs): at least 3 distinct texture patterns (e.g. checkerboard, brick, stripe) assigned to different wall types in the map
- Perspective-correct texture mapping onto wall columns
- Distance-based shading: walls darken smoothly as they recede (multiply shade by 1/distance, clamped)
- Ceiling rendered as a flat dark color; floor as a slightly lighter flat color
- Target: 60fps at 640×480 internal resolution scaled to fill the browser window

## Map
- Hard-coded map of at least 16×16 cells encoded as a 2D array
- Non-trivial layout: at least 3 distinct rooms connected by corridors, one dead end, one secret area
- At least 3 wall types (mapped to the 3 texture patterns)
- One door cell (wall type 4) that opens when the player is within 1.5 cells and presses E; opened doors become passable and render as open archways
- One exit cell — reaching it displays a 'LEVEL COMPLETE — [MM:SS]' overlay
- Player spawn position defined in the map; facing toward the first corridor

## Player
- WASD movement with smooth speed (moveSpeed: 3 cells/sec, rotSpeed: 2 rad/sec)
- Mouse-look for horizontal rotation using the Pointer Lock API (click canvas to lock)
- Collision detection: AABB against all solid walls (player radius: 0.2 cells)
- Field of view: 66 degrees

## Z-buffer
- Maintain a per-column Z-buffer (array of wall distances) for correct depth ordering
- Use it to clip any future sprite/overlay rendering to avoid drawing behind walls

## HUD
- Minimap in top-left corner: draw map cells as colored squares, player as a dot with a direction arrow; scale: 6px per cell
- FPS counter (rolling average over last 30 frames) in top-right corner
- 'Press E to open door' hint rendered in the center-bottom when a door is within 1.5 cells

## Code quality
- Single HTML file, all JS inline in <script> tags, all CSS inline in <style>
- requestAnimationFrame game loop with delta-time movement
- Organized into clearly named functions: initMap(), castRay(), drawWallColumn(), drawHUD(), drawMinimap(), gameLoop(), handleInput()
- No god-object — game state in a plain JS object, renderer functions take state as argument

Scoring: the judge scores each major section (rendering, map, player, HUD, code) independently. A beautiful but mathematically wrong perspective loses on correctness. A correct raycaster with unreadable code loses on quality. Both must be right to score above 8.

run 1 of 1

0.7
correctness 0.0quality 1.0documentation 1.010435ms

# judge: Response is severely truncated mid-function, leaving out nearly all rendering, player, HUD, and code logic — completely unusable and unscoreable as a working implementation.

$ cat claude-haiku-4-5.doom.r1.response
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Raycasting Engine</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            width: 100vw;
            height: 100vh;
            overflow: hidden;
            background: #000;
            font-family: 'Courier New', monospace;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        canvas {
            display: block;
            cursor: pointer;
            background: #000;
        }
        
        #gameContainer {
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <canvas id="gameCanvas"></canvas>
    </div>

    <script>
        // ============================================================================
        // CONSTANTS AND CONFIGURATION
        // ============================================================================
        
        const INTERNAL_WIDTH = 640;
        const INTERNAL_HEIGHT = 480;
        const MOVE_SPEED = 3.0; // cells per second
        const ROT_SPEED = 2.0; // radians per second
        const PLAYER_RADIUS = 0.2; // cells
        const FOV = (66 * Math.PI) / 180; // radians
        const DOOR_RANGE = 1.5; // cells
        const MAX_DISTANCE = 32; // cells
        
        // Wall types
        const WALL_EMPTY = 0;
        const WALL_BRICK = 1;
        const WALL_STONE = 2;
        const WALL_METAL = 3;
        const WALL_DOOR = 4;
        const WALL_EXIT = 5;
        
        // Texture patterns
        const TEXTURE_CHECKERBOARD = 0;
        const TEXTURE_BRICK = 1;
        const TEXTURE_STRIPE = 2;
        
        // ============================================================================
        // GAME STATE OBJECT
        // ============================================================================
        
        const gameState = {
            map: null,
            player: {
                x: 0,
                y: 0,
                angle: 0,
                vx: 0,
                vy: 0,
                vr: 0
            },
            keys: {},
            pointerLocked: false,
            zBuffer: new Float32Array(INTERNAL_WIDTH),
            doorStates: {},
            levelStartTime: Date.now(),
            levelComplete: false,
            frameCount: 0,
            frameTimes: [],
            currentFPS: 0,
            mouseX: 0,
            mouseY: 0
        };
        
        // ============================================================================
        // MAP INITIALIZATION
        // ============================================================================
        
        function initMap() {
            // 20x20 map with multiple rooms, corridors, secret area, and exit
            // 0=empty, 1=brick, 2=stone, 3=metal, 4=door, 5=exit
            gameState.map = [
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,2,2,2,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,4,0,0,2,0,2,0,0,0,3,3,3,0,0,1],
                [1,0,0,0,0,1,0,0,2,2,2,0,0,0,3,0,3,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,3,3,3,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,3,3,0,1],
                [1,0,0,0,0,1,0,0,0,0,2,0,0,2,0,0,3,3,0,1],
                [1,0,0,0,0,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1],
                [1,0,0,0,0,1,4,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,5,0,1],
                [1,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]
            ];
            
            // Initialize door states (all closed initially)
            gameState.doorStates = {};
            for (let y = 0; y < gameState.map.length; y++) {
                for (let x = 0; x < gameState.map[y].length; x++) {
                    if (gameState.map[y][x] === WALL_DOOR) {
                        gameState.doorStates[`${x},${y}`] = false; // false = closed
                    }
                }
            }
            
            // Player spawn: starting room facing corridor
            gameState.player.x = 2.5;
            gameState.player.y = 2.5;
            gameState.player.angle = 0; // facing right
            gameState.levelStartTime = Date.now();
            gameState.levelComplete = false;
        }
        
        // ============================================================================
        // PROCEDURAL TEXTURE GENERATION
        // ============================================================================
        
        function generateCheckerboardTexture(canvas, size, scale) {
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#888888';
            ctx.fillRect(0, 0, size, size);
            
            const squareSize = Math.floor(size / scale);
            ctx.fillStyle = '#444444';
            
            for (let y = 0; y < scale; y++) {
                for (let x