← RPG results

RPG

seed 2.0 code · full

Initial view of RPG

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 91,505 bytes · SHA-256 8f505ac1fb80
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chronicle of the Lost Star - Action RPG</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            background: #1a1a2e; 
            display: flex; 
            justify-content: center; 
            align-items: center; 
            min-height: 100vh;
            font-family: 'Courier New', monospace;
            overflow: hidden;
        }
        #gameContainer {
            position: relative;
            border: 4px solid #4a4a6a;
            box-shadow: 0 0 30px rgba(100, 100, 200, 0.5);
        }
        #gameCanvas {
            display: block;
            image-rendering: pixelated;
            background: #16213e;
        }
        #uiLayer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }
        #uiLayer > * { pointer-events: auto; }
        
        /* HUD Elements */
        #hud {
            position: absolute;
            top: 10px;
            left: 10px;
            right: 10px;
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
        }
        .hearts { display: flex; gap: 4px; }
        .heart {
            width: 28px;
            height: 28px;
            position: relative;
        }
        .heart-full { filter: drop-shadow(0 0 4px #ff4444); }
        .coin-display {
            background: rgba(0,0,0,0.7);
            padding: 8px 16px;
            border-radius: 8px;
            color: #ffd700;
            font-weight: bold;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .area-name {
            position: absolute;
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.8);
            padding: 10px 30px;
            border-radius: 4px;
            color: #fff;
            font-size: 18px;
            opacity: 0;
            transition: opacity 0.5s;
        }
        .area-name.show { opacity: 1; }
        
        /* Inventory Panel */
        #inventoryPanel, #shopPanel, #savePanel {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(20, 20, 40, 0.95);
            border: 3px solid #6a6a9a;
            border-radius: 12px;
            padding: 24px;
            display: none;
            min-width: 400px;
            max-height: 500px;
            overflow-y: auto;
        }
        #inventoryPanel.show, #shopPanel.show, #savePanel.show { display: block; }
        .panel-title {
            color: #aaf;
            font-size: 24px;
            margin-bottom: 16px;
            text-align: center;
            border-bottom: 2px solid #4a4a6a;
            padding-bottom: 10px;
        }
        .inv-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 10px;
        }
        .inv-slot {
            width: 70px;
            height: 70px;
            background: rgba(60, 60, 100, 0.5);
            border: 2px solid #5a5a8a;
            border-radius: 6px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s;
            position: relative;
        }
        .inv-slot:hover {
            border-color: #88f;
            background: rgba(80, 80, 140, 0.6);
        }
        .inv-slot.selected {
            border-color: #ff0;
            box-shadow: 0 0 10px #ff0;
        }
        .item-icon { font-size: 28px; }
        .item-count {
            position: absolute;
            bottom: 4px;
            right: 6px;
            font-size: 12px;
            color: #fff;
            font-weight: bold;
        }
        .item-details {
            margin-top: 16px;
            padding: 12px;
            background: rgba(40, 40, 80, 0.6);
            border-radius: 6px;
            color: #ccc;
            font-size: 14px;
            min-height: 60px;
        }
        .use-btn {
            margin-top: 10px;
            padding: 10px 24px;
            background: #4a4a8a;
            border: 2px solid #6a6aaa;
            border-radius: 6px;
            color: #fff;
            cursor: pointer;
            font-family: inherit;
            font-size: 14px;
        }
        .use-btn:hover { background: #5a5a9a; }
        .use-btn:disabled {
            background: #333;
            border-color: #444;
            color: #666;
            cursor: not-allowed;
        }
        
        /* Shop specific */
        .shop-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 12px;
            background: rgba(50, 50, 100, 0.4);
            border-radius: 6px;
            margin-bottom: 8px;
            cursor: pointer;
            border: 2px solid transparent;
        }
        .shop-item:hover {
            border-color: #88f;
            background: rgba(70, 70, 130, 0.5);
        }
        .shop-item-info { display: flex; align-items: center; gap: 12px; }
        .shop-price { color: #ffd700; font-weight: bold; }
        
        /* Controls hint */
        .controls-hint {
            position: absolute;
            bottom: 10px;
            left: 10px;
            background: rgba(0,0,0,0.7);
            padding: 8px 14px;
            border-radius: 6px;
            color: #aaa;
            font-size: 12px;
        }
        
        /* Dialog box */
        #dialogBox {
            position: absolute;
            bottom: 80px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(20, 20, 50, 0.95);
            border: 3px solid #88a;
            border-radius: 8px;
            padding: 16px 24px;
            color: #fff;
            max-width: 600px;
            display: none;
        }
        #dialogBox.show { display: block; }
        .dialog-speaker {
            color: #aaf;
            font-weight: bold;
            margin-bottom: 6px;
        }
        .dialog-continue {
            color: #888;
            font-size: 12px;
            margin-top: 8px;
            text-align: right;
        }
        
        /* Weapon display */
        #weaponDisplay {
            position: absolute;
            bottom: 10px;
            right: 10px;
            background: rgba(0,0,0,0.7);
            padding: 10px 16px;
            border-radius: 6px;
            color: #fff;
            font-size: 13px;
        }
        .weapon-icon { font-size: 20px; margin-right: 8px; }
        
        /* Notification */
        #notification {
            position: absolute;
            top: 120px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(20, 100, 50, 0.9);
            padding: 10px 24px;
            border-radius: 6px;
            color: #fff;
            opacity: 0;
            transition: opacity 0.3s;
            white-space: nowrap;
        }
        #notification.show { opacity: 1; }
        
        /* Start screen */
        #startScreen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(180deg, #0f0f23 0%, #1a1a3e 50%, #0f0f23 100%);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 100;
        }
        #startScreen.hidden { display: none; }
        .game-title {
            font-size: 48px;
            color: #ccf;
            text-shadow: 0 0 20px #88f, 0 0 40px #448;
            margin-bottom: 10px;
            letter-spacing: 4px;
        }
        .game-subtitle {
            color: #88a;
            margin-bottom: 50px;
            font-size: 18px;
        }
        .menu-btn {
            padding: 16px 48px;
            margin: 8px;
            background: rgba(60, 60, 120, 0.6);
            border: 2px solid #88a;
            border-radius: 8px;
            color: #fff;
            font-size: 18px;
            cursor: pointer;
            font-family: inherit;
            transition: all 0.2s;
        }
        .menu-btn:hover {
            background: rgba(80, 80, 160, 0.8);
            border-color: #aaf;
            box-shadow: 0 0 15px rgba(136, 136, 255, 0.4);
        }
        
        /* Death screen */
        #deathScreen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(50, 10, 10, 0.9);
            display: none;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 100;
        }
        #deathScreen.show { display: flex; }
        
        /* Minimap */
        #minimap {
            position: absolute;
            top: 50px;
            right: 10px;
            width: 120px;
            height: 120px;
            background: rgba(0,0,0,0.6);
            border: 2px solid #4a4a6a;
            border-radius: 4px;
        }
    </style>
</head>
<body>
<div id="gameContainer">
    <canvas id="gameCanvas" width="800" height="600"></canvas>
    <div id="uiLayer">
        <!-- Start Screen -->
        <div id="startScreen">
            <div class="game-title">⚔ CHRONICLE ⚔</div>
            <div class="game-subtitle">of the Lost Star</div>
            <button class="menu-btn" onclick="game.startNewGame()">New Game</button>
            <button class="menu-btn" onclick="game.loadGame()">Continue</button>
            <button class="menu-btn" onclick="game.showControls()">Controls</button>
            <div id="controlsInfo" style="display:none; margin-top:30px; color:#aaa; text-align:center; line-height:2;">
                WASD / Arrows - Move<br>
                SPACE - Attack / Swing Weapon<br>
                E - Interact (Shop, Save Points, Chests)<br>
                Q - Fire Ranged Weapon (once unlocked)<br>
                I - Inventory<br>
                1,2,3,4 - Quick Items<br>
                ESC - Close Menus / Pause
            </div>
        </div>
        
        <!-- Death Screen -->
        <div id="deathScreen">
            <div class="game-title" style="color:#f44;">YOU DIED</div>
            <div class="game-subtitle">The darkness claims another soul...</div>
            <button class="menu-btn" onclick="game.respawn()">Return to Last Save</button>
            <button class="menu-btn" onclick="game.startNewGame()">Start Over</button>
        </div>
        
        <!-- HUD -->
        <div id="hud">
            <div class="hearts" id="heartsContainer"></div>
            <div class="coin-display">
                <span style="font-size:20px;">🪙</span>
                <span id="coinCount">0</span>
            </div>
        </div>
        
        <div class="area-name" id="areaName"></div>
        <div id="notification"></div>
        
        <!-- Minimap -->
        <canvas id="minimap" width="120" height="120"></canvas>
        
        <!-- Weapon Display -->
        <div id="weaponDisplay">
            <span class="weapon-icon" id="weaponIcon">🗡️</span>
            <span id="weaponName">Wooden Sword</span>
        </div>
        
        <div class="controls-hint">
            WASD: Move | SPACE: Attack | E: Interact | I: Inventory
        </div>
        
        <!-- Dialog Box -->
        <div id="dialogBox">
            <div class="dialog-speaker" id="dialogSpeaker"></div>
            <div id="dialogText"></div>
            <div class="dialog-continue">Press SPACE to continue...</div>
        </div>
        
        <!-- Inventory Panel -->
        <div id="inventoryPanel">
            <div class="panel-title">📦 Inventory</div>
            <div class="inv-grid" id="invGrid"></div>
            <div class="item-details" id="itemDetails">
                Select an item to view details.
            </div>
            <button class="use-btn" id="useItemBtn" disabled onclick="game.useSelectedItem()">Use Item</button>
            <button class="use-btn" style="margin-left:10px;" onclick="game.closeInventory()">Close (ESC)</button>
        </div>
        
        <!-- Shop Panel -->
        <div id="shopPanel">
            <div class="panel-title">🏪 Merchant's Shop</div>
            <div id="shopItems"></div>
            <div style="margin-top:16px; color:#ffd700;">
                Your Gold: <span id="shopGold">0</span> 🪙
            </div>
            <button class="use-btn" style="margin-top:16px;" onclick="game.closeShop()">Leave Shop</button>
        </div>
        
        <!-- Save Panel -->
        <div id="savePanel">
            <div class="panel-title">💎 Save Point</div>
            <p style="color:#aaa; margin-bottom:16px; text-align:center;">Rest at this ancient shrine to save your progress.</p>
            <p style="color:#8f8; margin-bottom:20px; text-align:center;">Your health will be fully restored.</p>
            <button class="menu-btn" onclick="game.saveGame()">Save & Rest</button>
            <button class="menu-btn" onclick="game.closeSavePanel()">Cancel</button>
        </div>
    </div>
</div>

<script>
// ==================== AUDIO SYSTEM ====================
class AudioManager {
    constructor() {
        this.ctx = null;
        this.enabled = true;
    }
    
    init() {
        this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    }
    
    play(type) {
        if (!this.enabled || !this.ctx) return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.connect(gain);
        gain.connect(this.ctx.destination);
        
        const now = this.ctx.currentTime;
        switch(type) {
            case 'attack':
                osc.frequency.setValueAtTime(300, now);
                osc.frequency.exponentialRampToValueAtTime(100, now + 0.1);
                gain.gain.setValueAtTime(0.2, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
                osc.start(now);
                osc.stop(now + 0.1);
                break;
            case 'hit':
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(200, now);
                osc.frequency.exponentialRampToValueAtTime(50, now + 0.15);
                gain.gain.setValueAtTime(0.25, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
                osc.start(now);
                osc.stop(now + 0.15);
                break;
            case 'pickup':
                osc.type = 'sine';
                osc.frequency.setValueAtTime(523, now);
                osc.frequency.setValueAtTime(659, now + 0.05);
                osc.frequency.setValueAtTime(784, now + 0.1);
                gain.gain.setValueAtTime(0.15, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
                osc.start(now);
                osc.stop(now + 0.2);
                break;
            case 'step':
                osc.type = 'triangle';
                osc.frequency.setValueAtTime(80 + Math.random() * 40, now);
                gain.gain.setValueAtTime(0.03, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.05);
                osc.start(now);
                osc.stop(now + 0.05);
                break;
            case 'door':
                osc.type = 'square';
                osc.frequency.setValueAtTime(200, now);
                osc.frequency.exponentialRampToValueAtTime(100, now + 0.2);
                gain.gain.setValueAtTime(0.1, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
                osc.start(now);
                osc.stop(now + 0.3);
                break;
            case 'save':
                osc.type = 'sine';
                osc.frequency.setValueAtTime(400, now);
                osc.frequency.setValueAtTime(500, now + 0.1);
                osc.frequency.setValueAtTime(600, now + 0.2);
                osc.frequency.setValueAtTime(800, now + 0.3);
                gain.gain.setValueAtTime(0.2, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.6);
                osc.start(now);
                osc.stop(now + 0.6);
                break;
            case 'hurt':
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(150, now);
                osc.frequency.exponentialRampToValueAtTime(30, now + 0.2);
                gain.gain.setValueAtTime(0.3, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
                osc.start(now);
                osc.stop(now + 0.2);
                break;
            case 'shoot':
                osc.type = 'square';
                osc.frequency.setValueAtTime(600, now);
                osc.frequency.exponentialRampToValueAtTime(200, now + 0.1);
                gain.gain.setValueAtTime(0.15, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
                osc.start(now);
                osc.stop(now + 0.1);
                break;
            case 'chest':
                osc.type = 'sine';
                osc.frequency.setValueAtTime(300, now);
                osc.frequency.setValueAtTime(450, now + 0.1);
                osc.frequency.setValueAtTime(600, now + 0.2);
                gain.gain.setValueAtTime(0.2, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.4);
                osc.start(now);
                osc.stop(now + 0.4);
                break;
            case 'upgrade':
                osc.type = 'sine';
                for (let i = 0; i < 5; i++) {
                    const o = this.ctx.createOscillator();
                    const g = this.ctx.createGain();
                    o.connect(g);
                    g.connect(this.ctx.destination);
                    o.frequency.setValueAtTime(400 + i * 100, now + i * 0.1);
                    g.gain.setValueAtTime(0.15, now + i * 0.1);
                    g.gain.exponentialRampToValueAtTime(0.01, now + i * 0.1 + 0.15);
                    o.start(now + i * 0.1);
                    o.stop(now + i * 0.1 + 0.15);
                }
                return;
        }
    }
}

// ==================== PARTICLE SYSTEM ====================
class Particle {
    constructor(x, y, type) {
        this.x = x;
        this.y = y;
        this.type = type;
        this.life = 1;
        this.decay = 0.02;
        this.vx = (Math.random() - 0.5) * 4;
        this.vy = (Math.random() - 0.5) * 4 - 1;
        this.size = 3 + Math.random() * 4;
        
        switch(type) {
            case 'hit':
                this.color = '#ff4';
                this.decay = 0.08;
                this.size = 2 + Math.random() * 5;
                break;
            case 'coin':
                this.color = '#fd0';
                this.decay = 0.04;
                this.vy = -Math.random() * 3 - 1;
                break;
            case 'heal':
                this.color = '#4f4';
                this.decay = 0.03;
                this.vy = -Math.random() * 2;
                break;
            case 'spark':
                this.color = '#fff';
                this.decay = 0.1;
                this.vx *= 2;
                this.vy *= 2;
                break;
            case 'magic':
                this.color = '#a6f';
                this.decay = 0.025;
                this.vy = -0.5 - Math.random();
                this.size = 4 + Math.random() * 6;
                break;
            case 'dust':
                this.color = '#a86';
                this.decay = 0.015;
                this.vx *= 0.5;
                this.vy = -0.3;
                this.size = 2 + Math.random() * 3;
                break;
        }
    }
    
    update() {
        this.x += this.vx;
        this.y += this.vy;
        this.life -= this.decay;
        this.vx *= 0.98;
        this.vy *= 0.98;
    }
    
    draw(ctx) {
        ctx.globalAlpha = this.life;
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.size * this.life, 0, Math.PI * 2);
        ctx.fill();
        ctx.globalAlpha = 1;
    }
}

// ==================== MAIN GAME ====================
class Game {
    constructor() {
        this.canvas = document.getElementById('gameCanvas');
        this.ctx = this.canvas.getContext('2d');
        this.minimapCanvas = document.getElementById('minimap');
        this.minimapCtx = this.minimapCanvas.getContext('2d');
        this.audio = new AudioManager();
        
        this.keys = {};
        this.lastTime = 0;
        this.gameState = 'menu'; // menu, playing, paused, dialog, inventory, shop, save, dead
        
        this.setupInput();
    }
    
    init() {
        // World definition
        this.world = {
            currentArea: 'village',
            areas: this.createAreas(),
            camera: { x: 0, y: 0 }
        };
        
        // Player
        this.player = {
            x: 400,
            y: 400,
            width: 24,
            height: 32,
            speed: 140,
            facing: 'down',
            animFrame: 0,
            animTimer: 0,
            moving: false,
            
            // Stats
            health: 6,        // 3 hearts × 2 HP each
            maxHealth: 6,     // Will increase to 10 (5 hearts)
            healthContainers: 3,
            
            // Combat
            weapon: 0,        // 0=wooden, 1=iron, 2=master
            attackCooldown: 0,
            attacking: false,
            attackTimer: 0,
            attackAngle: 0,
            
            // Ranged (unlocked mid-game)
            hasRanged: false,
            rangedCooldown: 0,
            
            gold: 0,
            invincible: 0,
            
            // Inventory
            inventory: [],
            selectedItem: null
        };
        
        // Entities
        this.enemies = [];
        this.projectiles = [];
        this.particles = [];
        this.items = [];
        this.interactables = [];
        
        // Dialog
        this.dialogQueue = [];
        this.currentDialog = null;
        
        // Progression tracking
        this.progression = {
            boss1Defeated: false,
            boss2Defeated: false,
            foundRangedWeapon: false,
            weaponUpgrades: 0,
            chestsOpened: [],
            npcsTalked: []
        };
        
        this.loadArea(this.world.currentArea);
    }
    
    createAreas() {
        return {
            village: {
                name: "Starfall Village",
                width: 1200,
                height: 1000,
                bgColor: '#2d4a2d',
                tiles: [],
                exits: [
                    { x: 550, y: 0, w: 100, h: 20, to: 'forest', spawnX: 400, spawnY: 550 },
                    { x: 0, y: 450, w: 20, h: 100, to: 'shop', spawnX: 650, spawnY: 300 },
                ],
                entities: () => [
                    { type: 'npc', x: 300, y: 300, id: 'elder', name: 'Village Elder', icon: '👴', dialog: [
                        "Welcome, young warrior, to Starfall Village.",
                        "A dark star fell from the sky not long ago...",
                        "It shattered into pieces, and monsters began to appear.",
                        "Recover the star fragments in the forest and cave.",
                        "Take this - it might help you on your journey.",
                        "{GIVE:POTION}"
                    ]},
                    { type: 'npc', x: 700, y: 350, id: 'weaponsmith', name: 'Weaponsmith', icon: '🔨', dialog: [
                        "Hey there! I'm the local blacksmith.",
                        "Bring me 100 gold and a Metal Shard, and I'll upgrade your sword!",
                        "{SHOW_UPGRADE}"
                    ]},
                    { type: 'savepoint', x: 400, y: 300, id: 'village_save' },
                    { type: 'chest', x: 900, y: 250, id: 'village_chest_1', content: { type: 'gold', amount: 30 } },
                    { type: 'sign', x: 560, y: 60, text: "← North →\nWhispering Forest" },
                    { type: 'pot', x: 150, y: 200, dropChance: 0.3 },
                    { type: 'pot', x: 180, y: 210, dropChance: 0.3 },
                ],
                enemies: () => [
                    // Low-level enemies near forest edge
                    { type: 'slime', x: 800, y: 150 },
                    { type: 'slime', x: 950, y: 200 },
                ],
                decorations: () => [
                    // Houses
                    { x: 100, y: 150, w: 120, h: 100, color: '#8b4513', type: 'house' },
                    { x: 600, y: 200, w: 100, h: 90, color: '#8b4513', type: 'house' },
                    { x: 250, y: 500, w: 140, h: 110, color: '#654321', type: 'house' },
                    // Trees
                    { x: 50, y: 50, w: 40, h: 50, color: '#1a5a1a', type: 'tree' },
                    { x: 1100, y: 80, w: 40, h: 50, color: '#1a5a1a', type: 'tree' },
                    { x: 1000, y: 700, w: 40, h: 50, color: '#1a5a1a', type: 'tree' },
                    { x: 50, y: 750, w: 40, h: 50, color: '#1a5a1a', type: 'tree' },
                    // Path
                    { x: 550, y: 20, w: 100, h: 600, color: '#8b7355', type: 'path' },
                ]
            },
            forest: {
                name: "Whispering Forest",
                width: 1400,
                height: 1200,
                bgColor: '#1e3a1e',
                exits: [
                    { x: 350, y: 580, w: 100, h: 20, to: 'village', spawnX: 560, spawnY: 80 },
                    { x: 650, y: 0, w: 100, h: 20, to: 'deepforest', spawnX: 400, spawnY: 550 },
                    { x: 1380, y: 500, w: 20, h: 100, to: 'cave', spawnX: 80, spawnY: 300 },
                ],
                entities: () => [
                    { type: 'chest', x: 200, y: 200, id: 'forest_chest_1', content: { type: 'gold', amount: 50 } },
                    { type: 'chest', x: 1100, y: 150, id: 'forest_chest_2', content: { type: 'item', item: 'ANTIDOTE' } },
                    { type: 'chest', x: 800, y: 900, id: 'forest_chest_3', content: { type: 'item', item: 'METAL_SHARD' } },
                    { type: 'savepoint', x: 700, y: 600, id: 'forest_save' },
                    { type: 'sign', x: 380, y: 540, text: "South to\nStarfall Village" },
                    { type: 'npc', x: 1200, y: 400, id: 'hermit', name: 'Forest Hermit', icon: '🧙', dialog: [
                        "You seek the star fragment, don't you?",
                        "Deep in this forest, a great slime guards one piece.",
                        "And in the cave to the east, you'll find another.",
                        "But beware - the cave is dark and dangerous.",
                        "Here, take this herb to help you on your way.",
                        "{GIVE:HERB}"
                    ]},
                ],
                enemies: () => {
                    const e = [
                        { type: 'slime', x: 300, y: 150 },
                        { type: 'slime', x: 500, y: 300 },
                        { type: 'slime', x: 200, y: 400 },
                        { type: 'wolf', x: 800, y: 200 },
                        { type: 'wolf', x: 1000, y: 350 },
                        { type: 'slime', x: 600, y: 800 },
                        { type: 'wolf', x: 900, y: 700 },
                    ];
                    // Add miniboss if not defeated
                    if (!this.progression.boss1Defeated) {
                        e.push({ type: 'bigslime', x: 700, y: 150, isBoss: true });
                    }
                    return e;
                },
                decorations: () => {
                    const d = [];
                    // Lots of trees
                    for (let i = 0; i < 40; i++) {
                        let x = Math.random() * 1300 + 50;
                        let y = Math.random() * 1100 + 50;
                        // Avoid paths and clearings
                        if ((x > 600 && x < 800) || (x > 300 && x < 500 && y > 500 && y < 650)) continue;
                        d.push({ x, y, w: 45 + Math.random() * 20, h: 55 + Math.random() * 20, color: '#0d2d0d', type: 'tree' });
                    }
                    // Main path
                    d.push({ x: 650, y: 20, w: 100, h: 1200, color: '#5a4a35', type: 'path' });
                    d.push({ x: 300, y: 500, w: 500, h: 100, color: '#5a4a35', type: 'path' });
                    return d;
                }
            },
            deepforest: {
                name: "Ancient Grove",
                width: 1000,
                height: 900,
                bgColor: '#0f2a0f',
                exits: [
                    { x: 350, y: 580, w: 100, h: 20, to: 'forest', spawnX: 680, spawnY: 80 },
                ],
                entities: () => {
                    const ent = [
                        { type: 'savepoint', x: 500, y: 500, id: 'grove_save' },
                        { type: 'sign', x: 380, y: 540, text: "Back to Forest" },
                    ];
                    // Ranged weapon chest - only appears after first boss (mid-game)
                    if (this.progression.boss1Defeated && !this.progression.foundRangedWeapon) {
                        ent.push({ type: 'chest', x: 500, y: 200, id: 'ranged_chest', content: { type: 'ranged' }, special: true });
                    }
                    if (!this.progression.boss2Defeated) {
                        ent.push({ type: 'npc', x: 200, y: 300, id: 'spirit', name: 'Forest Spirit', icon: '👻', dialog: [
                            "The guardian of this grove has been corrupted by the dark star's power...",
                            "Only by defeating it can you obtain the second fragment.",
                            "After defeating the Big Slime, a magical weapon appeared in the grove...",
                            "Perhaps it will aid you."
                        ]});
                    }
                    return ent;
                },
                enemies: () => {
                    const e = [
                        { type: 'wolf', x: 300, y: 150 },
                        { type: 'wolf', x: 700, y: 200 },
                        { type: 'slime', x: 800, y: 400 },
                    ];
                    if (!this.progression.boss2Defeated) {
                        e.push({ type: 'guardian', x: 500, y: 120, isBoss: true });
                    }
                    return e;
                },
                decorations: () => [
                    { x: 450, y: 400, w: 100, h: 30, color: '#5a4a35', type: 'path' },
                    ...(() => {
                        const d = [];
                        for (let i = 0; i < 25; i++) {
                            let x = Math.random() * 900 + 50;
                            let y = Math.random() * 800 + 50;
                            if (x > 400 && x < 600 && y > 100 && y < 550) continue;
                            d.push({ x, y, w: 50, h: 60, color: '#081a08', type: 'tree' });
                        }
                        return d;
                    })()
                ]
            },
            cave: {
                name: "Darkstone Cave",
                width: 1000,
                height: 800,
                bgColor: '#2a2535',
                exits: [
                    { x: 0, y: 250, w: 20, h: 100, to: 'forest', spawnX: 1250, spawnY: 530 },
                ],
                entities: () => [
                    { type: 'savepoint', x: 200, y: 200, id: 'cave_save' },
                    { type: 'chest', x: 800, y: 600, id: 'cave_chest_1', content: { type: 'gold', amount: 80 } },
                    { type: 'chest', x: 200, y: 600, id: 'cave_chest_2', content: { type: 'item', item: 'HEART_CONTAINER' } },
                    { type: 'chest', x: 700, y: 200, id: 'cave_chest_3', content: { type: 'item', item: 'METAL_SHARD' } },
                    { type: 'sign', x: 60, y: 220, text: "Exit to Forest" },
                ],
                enemies: () => [
                    { type: 'bat', x: 400, y: 200 },
                    { type: 'bat', x: 500, y: 350 },
                    { type: 'bat', x: 300, y: 500 },
                    { type: 'skeleton', x: 600, y: 300 },
                    { type: 'skeleton', x: 800, y: 450 },
                    { type: 'bat', x: 700, y: 600 },
                ],
                decorations: () => [
                    { x: 400, y: 300, w: 200, h: 200, color: '#1a1520', type: 'rock' },
                    { x: 100, y: 400, w: 80, h: 100, color: '#3a3545', type: 'rock' },
                    { x: 850, y: 100, w: 100, h: 80, color: '#3a3545', type: 'rock' },
                ]
            },
            shop: {
                name: "Merchant's Shop",
                width: 800,
                height: 600,
                bgColor: '#4a3a2a',
                exits: [
                    { x: 680, y: 250, w: 20, h: 100, to: 'village', spawnX: 60, spawnY: 480 },
                ],
                entities: () => [
                    { type: 'npc', x: 350, y: 280, id: 'merchant', name: 'Merchant', icon: '💰', dialog: [
                        "Welcome to my shop! Have a look at my wares.",
                        "{OPEN_SHOP}"
                    ]},
                    { type: 'sign', x: 660, y: 220, text: "Back to Village" },
                ],
                enemies: () => [],
                decorations: () => [
                    { x: 100, y: 100, w: 200, h: 150, color: '#654321', type: 'counter' },
                    { x: 500, y: 100, w: 120, h: 60, color: '#5a3a1a', type: 'shelf' },
                    { x: 500, y: 200, w: 120, h: 60, color: '#5a3a1a', type: 'shelf' },
                    { x: 100, y: 400, w: 200, h: 80, color: '#3a2a1a', type: 'rug' },
                ]
            }
        };
    }
    
    shopItems = [
        { id: 'POTION', name: 'Health Potion', icon: '🧪', price: 30, desc: 'Restores 4 HP (2 full hearts)' },
        { id: 'HERB', name: 'Healing Herb', icon: '🌿', price: 15, desc: 'Restores 2 HP (1 heart)' },
        { id: 'ANTIDOTE', name: 'Antidote', icon: '💊', price: 20, desc: 'Cures poison (not that you need it yet...)' },
        { id: 'METAL_SHARD', name: 'Metal Shard', icon: '💎', price: 50, desc: 'Used for weapon upgrades' },
        { id: 'BOMB', name: 'Bomb', icon: '💣', price: 40, desc: 'Explosive. Useful for... things.' },
    ];
    
    itemData = {
        POTION: { name: 'Health Potion', icon: '🧪', desc: 'Restores 4 HP (2 full hearts).', consumable: true, effect: 'heal', value: 4 },
        HERB: { name: 'Healing Herb', icon: '🌿', desc: 'Restores 2 HP (1 heart).', consumable: true, effect: 'heal', value: 2 },
        ANTIDOTE: { name: 'Antidote', icon: '💊', desc: 'Cures poison.', consumable: true, effect: 'cure', value: 0 },
        METAL_SHARD: { name: 'Metal Shard', icon: '💎', desc: 'A rare shard used for weapon upgrades.', consumable: false },
        HEART_CONTAINER: { name: 'Heart Container', icon: '❤️', desc: 'Permanently increases maximum health by 2 HP (1 heart).', consumable: true, effect: 'container', value: 2 },
        BOMB: { name: 'Bomb', icon: '💣', desc: 'Explosive weapon. Place and run!', consumable: true, effect: 'bomb', value: 0 },
        STAR_FRAGMENT: { name: 'Star Fragment', icon: '⭐', desc: 'A fragment of the fallen star. Collect them all!', consumable: false, key: true },
    };
    
    weaponData = [
        { name: 'Wooden Sword', icon: '🗡️', damage: 2, range: 50, speed: 1.0, color: '#8b4513' },
        { name: 'Iron Sword', icon: '⚔️', damage: 4, range: 55, speed: 1.0, color: '#aaa' },
        { name: 'Master Sword', icon: '✨', damage: 7, range: 65, speed: 1.2, color: '#4af' },
    ];
    
    enemyData = {
        slime: { name: 'Slime', icon: '🟢', health: 4, damage: 1, speed: 40, size: 24, gold: 5, exp: 0 },
        wolf: { name: 'Wolf', icon: '🐺', health: 6, damage: 2, speed: 70, size: 28, gold: 10, exp: 0 },
        bat: { name: 'Bat', icon: '🦇', health: 3, damage: 1, speed: 90, size: 20, gold: 6, exp: 0 },
        skeleton: { name: 'Skeleton', icon: '💀', health: 8, damage: 2, speed: 50, size: 28, gold: 15, exp: 0 },
        bigslime: { name: 'Gelidus', icon: '👾', health: 30, damage: 2, speed: 35, size: 48, gold: 100, exp: 0, boss: true },
        guardian: { name: 'Forest Guardian', icon: '🌲', health: 50, damage: 3, speed: 45, size: 56, gold: 150, exp: 0, boss: true },
    };
    
    loadArea(areaId) {
        const area = this.world.areas[areaId];
        if (!area) return;
        
        this.world.currentArea = areaId;
        this.interactables = [];
        this.enemies = [];
        this.items = [];
        this.projectiles = [];
        
        // Load entities
        area.entities().forEach(e => this.spawnEntity(e));
        area.enemies().forEach(e => this.spawnEnemy(e));
        
        // Show area name
        this.showAreaName(area.name);
    }
    
    spawnEntity(e) {
        // Check if chest was already opened
        if (e.type === 'chest' && this.progression.chestsOpened.includes(e.id)) {
            return;
        }
        
        const entity = {
            ...e,
            width: e.type === 'npc' ? 32 : (e.type === 'chest' ? 36 : 40),
            height: e.type === 'npc' ? 32 : (e.type === 'chest' ? 32 : 40),
            opened: false,
            animTimer: 0
        };
        this.interactables.push(entity);
    }
    
    spawnEnemy(e) {
        const data = this.enemyData[e.type];
        if (!data) return;
        
        this.enemies.push({
            ...e,
            ...data,
            currentHealth: data.health,
            width: data.size,
            height: data.size,
            vx: 0,
            vy: 0,
            state: 'idle',
            stateTimer: 0,
            attackCooldown: 0,
            hitTimer: 0,
            animFrame: 0,
            animTimer: 0,
            startX: e.x,
            startY: e.y
        });
    }
    
    showAreaName(name) {
        const el = document.getElementById('areaName');
        el.textContent = name;
        el.classList.add('show');
        setTimeout(() => el.classList.remove('show'), 3000);
    }
    
    setupInput() {
        window.addEventListener('keydown', (e) => {
            this.keys[e.code] = true;
            
            if (e.code === 'Escape') {
                this.handleEscape();
            }
            if (e.code === 'KeyI' && this.gameState === 'playing') {
                this.openInventory();
            }
            if ((e.code === 'Digit1' || e.code === 'Digit2' || e.code === 'Digit3' || e.code === 'Digit4') && this.gameState === 'playing') {
                this.useQuickSlot(parseInt(e.code.slice(-1)) - 1);
            }
            if (e.code === 'Space') {
                e.preventDefault();
                this.handleSpace();
            }
            if (e.code === 'KeyE' && this.gameState === 'playing') {
                this.tryInteract();
            }
            if (e.code === 'KeyQ' && this.gameState === 'playing' && this.player.hasRanged) {
                this.fireRanged();
            }
        });
        
        window.addEventListener('keyup', (e) => {
            this.keys[e.code] = false;
        });
        
        // Click outside to close panels
        document.getElementById('inventoryPanel').addEventListener('click', (e) => {
            if (e.target.classList.contains('inv-slot')) {
                const idx = parseInt(e.target.dataset.index);
                this.selectInventorySlot(idx);
            }
        });
        
        document.getElementById('shopItems').addEventListener('click', (e) => {
            const item = e.target.closest('.shop-item');
            if (item) {
                const idx = parseInt(item.dataset.index);
                this.tryBuyItem(idx);
            }
        });
    }
    
    handleEscape() {
        if (this.gameState === 'inventory') {
            this.closeInventory();
        } else if (this.gameState === 'shop') {
            this.closeShop();
        } else if (this.gameState === 'save') {
            this.closeSavePanel();
        } else if (this.gameState === 'dialog') {
            // Advance dialog instead of closing
            this.advanceDialog();
        }
    }
    
    handleSpace() {
        if (this.gameState === 'dialog') {
            this.advanceDialog();
        } else if (this.gameState === 'playing') {
            this.tryAttack();
        }
    }
    
    // ==================== GAME FLOW ====================
    startNewGame() {
        this.audio.init();
        document.getElementById('startScreen').classList.add('hidden');
        this.init();
        this.gameState = 'playing';
        
        // Give initial potion
        this.addToInventory('HERB', 1);
        
        this.showNotification("Welcome to Starfall Village!");
        this.updateHUD();
        this.gameLoop(0);
    }
    
    loadGame() {
        const save = localStorage.getItem('lostStarSave');
        if (!save) {
            alert('No save data found!');
            return;
        }
        try {
            const data = JSON.parse(save);
            this.audio.init();
            document.getElementById('startScreen').classList.add('hidden');
            
            // Restore state
            this.player = data.player;
            this.progression = data.progression;
            this.world.currentArea = data.currentArea;
            
            this.interactables = [];
            this.enemies = [];
            this.items = [];
            this.projectiles = [];
            this.particles = [];
            
            const area = this.world.areas[this.world.currentArea];
            area.entities().forEach(e => this.spawnEntity(e));
            area.enemies().forEach(e => this.spawnEnemy(e));
            
            this.player.x = data.playerX || 400;
            this.player.y = data.playerY || 400;
            
            this.gameState = 'playing';
            this.showNotification("Game loaded!");
            this.updateHUD();
            this.showAreaName(area.name);
            this.gameLoop(0);
        } catch(e) {
            alert('Save data corrupted!');
            console.error(e);
        }
    }
    
    saveGame() {
        // Heal player
        this.player.health = this.player.maxHealth;
        this.updateHUD();
        
        const saveData = {
            player: this.player,
            progression: this.progression,
            currentArea: this.world.currentArea,
            playerX: this.player.x,
            playerY: this.player.y,
            timestamp: Date.now()
        };
        localStorage.setItem('lostStarSave', JSON.stringify(saveData));
        
        this.audio.play('save');
        this.spawnParticles(this.player.x, this.player.y, 'magic', 15);
        this.closeSavePanel();
        this.showNotification("Progress saved! Health restored.");
    }
    
    respawn() {
        document.getElementById('deathScreen').classList.remove('show');
        this.loadGame();
    }
    
    showControls() {
        const el = document.getElementById('controlsInfo');
        el.style.display = el.style.display === 'none' ? 'block' : 'none';
    }
    
    // ==================== MAIN LOOP ====================
    gameLoop(timestamp) {
        const dt = Math.min((timestamp - this.lastTime) / 1000, 0.1);
        this.lastTime = timestamp;
        
        if (this.gameState === 'playing') {
            this.update(dt);
        }
        
        this.render();
        this.renderMinimap();
        
        requestAnimationFrame(t => this.gameLoop(t));
    }
    
    update(dt) {
        // Player movement
        this.updatePlayer(dt);
        
        // Combat timers
        if (this.player.attackCooldown > 0) this.player.attackCooldown -= dt;
        if (this.player.attacking) {
            this.player.attackTimer -= dt;
            if (this.player.attackTimer <= 0) {
                this.player.attacking = false;
            }
        }
        if (this.player.rangedCooldown > 0) this.player.rangedCooldown -= dt;
        if (this.player.invincible > 0) this.player.invincible -= dt;
        
        // Enemies
        this.updateEnemies(dt);
        
        // Projectiles
        this.updateProjectiles(dt);
        
        // Particles
        this.particles = this.particles.filter(p => {
            p.update();
            return p.life > 0;
        });
        
        // Check area transitions
        this.checkAreaTransitions();
        
        // Check items on ground
        this.checkItemPickups();
        
        // Update entity animations
        this.interactables.forEach(e => {
            e.animTimer += dt;
        });
        
        // Step sounds
        if (this.player.moving && this.player.animTimer > 0.2) {
            this.player.animTimer = 0;
            this.audio.play('step');
        }
    }
    
    updatePlayer(dt) {
        const p = this.player;
        let dx = 0, dy = 0;
        
        if (this.keys['KeyW'] || this.keys['ArrowUp']) dy -= 1;
        if (this.keys['KeyS'] || this.keys['ArrowDown']) dy += 1;
        if (this.keys['KeyA'] || this.keys['ArrowLeft']) dx -= 1;
        if (this.keys['KeyD'] || this.keys['ArrowRight']) dx += 1;
        
        // Normalize diagonal
        if (dx !== 0 && dy !== 0) {
            dx *= 0.707;
            dy *= 0.707;
        }
        
        p.moving = dx !== 0 || dy !== 0;
        
        if (p.moving) {
            if (Math.abs(dx) > Math.abs(dy)) {
                p.facing = dx > 0 ? 'right' : 'left';
            } else {
                p.facing = dy > 0 ? 'down' : 'up';
            }
            p.animTimer += dt;
        }
        
        // Try move
        const newX = p.x + dx * p.speed * dt;
        const newY = p.y + dy * p.speed * dt;
        
        if (!this.checkCollision(newX, p.y, p.width, p.height)) {
            p.x = newX;
        }
        if (!this.checkCollision(p.x, newY, p.width, p.height)) {
            p.y = newY;
        }
        
        // Clamp to area
        const area = this.world.areas[this.world.currentArea];
        p.x = Math.max(p.width/2, Math.min(area.width - p.width/2, p.x));
        p.y = Math.max(p.height/2, Math.min(area.height - p.height/2, p.y));
        
        // Animation
        if (p.moving) {
            p.animFrame = Math.floor(p.animTimer * 8) % 4;
        } else {
            p.animFrame = 0;
        }
    }
    
    checkCollision(x, y, w, h) {
        const area = this.world.areas[this.world.currentArea];
        const halfW = w / 2;
        const halfH = h / 2;
        
        // Check decorations that block
        for (const dec of area.decorations()) {
            if (dec.type === 'tree' || dec.type === 'rock' || dec.type === 'house' || dec.type === 'counter' || dec.type === 'shelf') {
                if (x + halfW > dec.x && x - halfW < dec.x + dec.w &&
                    y + halfH > dec.y && y - halfH < dec.y + dec.h) {
                    return true;
                }
            }
        }
        
        // Check interactables except signs
        for (const ent of this.interactables) {
            if (ent.type === 'sign' || ent.type === 'savepoint') continue;
            if (x + halfW > ent.x - ent.width/2 && x - halfW < ent.x + ent.width/2 &&
                y + halfH > ent.y - ent.height/2 && y - halfH < ent.y + ent.height/2) {
                return true;
            }
        }
        
        // Check living enemies
        for (const enemy of this.enemies) {
            if (x + halfW > enemy.x - enemy.width/2 && x - halfW < enemy.x + enemy.width/2 &&
                y + halfH > enemy.y - enemy.height/2 && y - halfH < enemy.y + enemy.height/2) {
                return true;
            }
        }
        
        return false;
    }
    
    checkAreaTransitions() {
        const area = this.world.areas[this.world.currentArea];
        const p = this.player;
        
        for (const exit of area.exits) {
            if (p.x > exit.x && p.x < exit.x + exit.w &&
                p.y > exit.y && p.y < exit.y + exit.h) {
                this.audio.play('door');
                this.loadArea(exit.to);
                this.player.x = exit.spawnX;
                this.player.y = exit.spawnY;
                return;
            }
        }
    }
    
    checkItemPickups() {
        const p = this.player;
        this.items = this.items.filter(item => {
            const dist = Math.hypot(item.x - p.x, item.y - p.y);
            if (dist < 30) {
                if (item.gold) {
                    this.player.gold += item.gold;
                    this.showNotification(`+${item.gold} Gold!`);
                    this.audio.play('pickup');
                    this.spawnParticles(item.x, item.y, 'coin', 8);
                } else if (item.itemId) {
                    this.addToInventory(item.itemId, 1);
                    const data = this.itemData[item.itemId];
                    this.showNotification(`Got ${data.name}!`);
                    this.audio.play('pickup');
                    this.spawnParticles(item.x, item.y, 'spark', 6);
                }
                this.updateHUD();
                return false;
            }
            item.animTimer = (item.animTimer || 0) + 0.016;
            item.floatY = Math.sin(item.animTimer * 3) * 3;
            return true;
        });
    }
    
    // ==================== COMBAT ====================
    tryAttack() {
        if (this.player.attackCooldown > 0) return;
        
        const weapon = this.weaponData[this.player.weapon];
        this.player.attacking = true;
        this.player.attackTimer = 0.3 / weapon.speed;
        this.player.attackCooldown = 0.4 / weapon.speed;
        
        this.audio.play('attack');
        
        // Set attack angle based on facing
        const facingAngles = { down: Math.PI/2, up: -Math.PI/2, left: Math.PI, right: 0 };
        this.player.attackAngle = facingAngles[this.player.facing];
        
        // Check enemy hits
        const p = this.player;
        const attackX = p.x + Math.cos(this.player.attackAngle) * 30;
        const attackY = p.y + Math.sin(this.player.attackAngle) * 30;
        
        this.enemies.forEach(enemy => {
            const dist = Math.hypot(enemy.x - attackX, enemy.y - attackY);
            if (dist < weapon.range) {
                this.damageEnemy(enemy, weapon.damage);
            }
        });
        
        // Break pots
        this.interactables = this.interactables.filter(ent => {
            if (ent.type === 'pot') {
                const dist = Math.hypot(ent.x - attackX, ent.y - attackY);
                if (dist < weapon.range) {
                    this.spawnParticles(ent.x, ent.y, 'dust', 6);
                    // Drop
                    if (Math.random() < (ent.dropChance || 0.3)) {
                        this.items.push({ x: ent.x, y: ent.y, gold: 5, animTimer: 0 });
                    }
                    return false;
                }
            }
            return true;
        });
    }
    
    fireRanged() {
        if (this.player.rangedCooldown > 0) return;
        
        this.player.rangedCooldown = 0.8;
        this.audio.play('shoot');
        
        const speed = 300;
        const facingAngles = { down: Math.PI/2, up: -Math.PI/2, left: Math.PI, right: 0 };
        const angle = facingAngles[this.player.facing];
        
        this.projectiles.push({
            x: this.player.x,
            y: this.player.y,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            damage: 3,
            fromPlayer: true,
            lifetime: 2.0,
            size: 8,
            color: '#4af'
        });
    }
    
    damageEnemy(enemy, damage) {
        if (enemy.hitTimer > 0) return;
        
        enemy.currentHealth -= damage;
        enemy.hitTimer = 0.4;
        
        this.audio.play('hit');
        this.spawnParticles(enemy.x, enemy.y, 'hit', 8);
        this.spawnDamageNumber(enemy.x, enemy.y - enemy.height/2, damage, '#ff4');
        
        // Knockback
        const angle = Math.atan2(enemy.y - this.player.y, enemy.x - this.player.x);
        enemy.vx += Math.cos(angle) * 80;
        enemy.vy += Math.sin(angle) * 80;
        
        if (enemy.currentHealth <= 0) {
            this.killEnemy(enemy);
        }
    }
    
    killEnemy(enemy) {
        // Drops
        this.items.push({ x: enemy.x, y: enemy.y, gold: enemy.gold, animTimer: 0 });
        
        // Boss drops special items
        if (enemy.isBoss) {
            if (enemy.type === 'bigslime') {
                this.progression.boss1Defeated = true;
                this.items.push({ x: enemy.x, y: enemy.y + 30, itemId: 'STAR_FRAGMENT', animTimer: 0 });
                this.showNotification("Gelidus has been defeated!");
            } else if (enemy.type === 'guardian') {
                this.progression.boss2Defeated = true;
                this.items.push({ x: enemy.x, y: enemy.y + 30, itemId: 'STAR_FRAGMENT', animTimer: 0 });
                this.items.push({ x: enemy.x + 30, y: enemy.y, itemId: 'HEART_CONTAINER', animTimer: 0 });
                this.showNotification("Forest Guardian has been defeated!");
            }
        }
        
        this.spawnParticles(enemy.x, enemy.y, enemy.boss ? 'magic' : 'spark', enemy.boss ? 20 : 10);
    }
    
    updateEnemies(dt) {
        this.enemies = this.enemies.filter(e => e.currentHealth > 0);
        
        this.enemies.forEach(enemy => {
            if (enemy.hitTimer > 0) enemy.hitTimer -= dt;
            if (enemy.attackCooldown > 0) enemy.attackCooldown -= dt;
            enemy.animTimer += dt;
            enemy.animFrame = Math.floor(enemy.animTimer * 6) % 4;
            
            // AI
            const distToPlayer = Math.hypot(enemy.x - this.player.x, enemy.y - this.player.y);
            
            // Simple state machine
            enemy.stateTimer -= dt;
            
            if (distToPlayer < 180) {
                // Chase
                const angle = Math.atan2(this.player.y - enemy.y, this.player.x - enemy.x);
                enemy.vx = Math.cos(angle) * enemy.speed;
                enemy.vy = Math.sin(angle) * enemy.speed;
                
                // Attack if close
                if (distToPlayer < 35 && enemy.attackCooldown <= 0) {
                    if (this.player.invincible <= 0) {
                        this.damagePlayer(enemy.damage);
                    }
                    enemy.attackCooldown = 1.2;
                }
            } else if (enemy.stateTimer <= 0) {
                // Wander
                enemy.stateTimer = 1 + Math.random() * 2;
                if (Math.random() < 0.6 && Math.hypot(enemy.x - enemy.startX, enemy.y - enemy.startY) < 100) {
                    const angle = Math.random() * Math.PI * 2;
                    enemy.vx = Math.cos(angle) * enemy.speed * 0.5;
                    enemy.vy = Math.sin(angle) * enemy.speed * 0.5;
                } else {
                    // Return to start
                    const angle = Math.atan2(enemy.startY - enemy.y, enemy.startX - enemy.x);
                    enemy.vx = Math.cos(angle) * enemy.speed * 0.5;
                    enemy.vy = Math.sin(angle) * enemy.speed * 0.5;
                }
            }
            
            // Apply velocity with friction
            const newX = enemy.x + enemy.vx * dt;
            const newY = enemy.y + enemy.vy * dt;
            
            if (!this.checkCollision(newX, enemy.y, enemy.width, enemy.height)) {
                enemy.x = newX;
            } else {
                enemy.vx *= -0.5;
            }
            if (!this.checkCollision(enemy.x, newY, enemy.width, enemy.height)) {
                enemy.y = newY;
            } else {
                enemy.vy *= -0.5;
            }
            
            enemy.vx *= 0.9;
            enemy.vy *= 0.9;
            
            // Clamp
            const area = this.world.areas[this.world.currentArea];
            enemy.x = Math.max(enemy.width, Math.min(area.width - enemy.width, enemy.x));
            enemy.y = Math.max(enemy.height, Math.min(area.height - enemy.height, enemy.y));
        });
    }
    
    updateProjectiles(dt) {
        this.projectiles = this.projectiles.filter(proj => {
            proj.x += proj.vx * dt;
            proj.y += proj.vy * dt;
            proj.lifetime -= dt;
            
            if (proj.lifetime <= 0) return false;
            
            const area = this.world.areas[this.world.currentArea];
            if (proj.x < 0 || proj.x > area.width || proj.y < 0 || proj.y > area.height) {
                return false;
            }
            
            if (proj.fromPlayer) {
                for (const enemy of this.enemies) {
                    const dist = Math.hypot(enemy.x - proj.x, enemy.y - proj.y);
                    if (dist < enemy.width/2 + proj.size) {
                        this.damageEnemy(enemy, proj.damage);
                        return false;
                    }
                }
            }
            
            return true;
        });
    }
    
    damagePlayer(damage) {
        this.player.health -= damage;
        this.player.invincible = 1.0;
        
        this.audio.play('hurt');
        this.spawnParticles(this.player.x, this.player.y, 'hit', 10);
        this.spawnDamageNumber(this.player.x, this.player.y - 20, damage, '#f44');
        
        this.updateHUD();
        
        if (this.player.health <= 0) {
            this.gameState = 'dead';
            document.getElementById('deathScreen').classList.add('show');
        }
    }
    
    // ==================== INTERACTION ====================
    tryInteract() {
        const p = this.player;
        
        for (const ent of this.interactables) {
            const dist = Math.hypot(ent.x - p.x, ent.y - p.y);
            if (dist < 50) {
                this.interactWith(ent);
                return;
            }
        }
    }
    
    interactWith(ent) {
        switch(ent.type) {
            case 'npc':
                this.startDialog(ent);
                break;
            case 'chest':
                this.openChest(ent);
                break;
            case 'savepoint':
                this.openSavePanel();
                break;
            case 'sign':
                this.showNotification(ent.text);
                break;
        }
    }
    
    startDialog(npc) {
        // Check if NPC has special dialog
        if (!this.progression.npcsTalked.includes(npc.id)) {
            this.progression.npcsTalked.push(npc.id);
        }
        
        this.dialogQueue = [...npc.dialog];
        this.currentNPC = npc;
        this.advanceDialog();
    }
    
    advanceDialog() {
        if (this.dialogQueue.length === 0) {
            this.closeDialog();
            return;
        }
        
        const line = this.dialogQueue.shift();
        
        // Special commands
        if (line.startsWith('{GIVE:')) {
            const itemId = line.slice(6, -1);
            this.addToInventory(itemId, 1);
            const data = this.itemData[itemId];
            this.showNotification(`Received ${data.name}!`);
            this.audio.play('pickup');
            this.advanceDialog();
            return;
        }
        if (line === '{OPEN_SHOP}') {
            this.closeDialog();
            this.openShop();
            return;
        }
        if (line === '{SHOW_UPGRADE}') {
            this.closeDialog();
            this.showUpgradeOption();
            return;
        }
        
        document.getElementById('dialogSpeaker').textContent = this.currentNPC ? this.currentNPC.name : '';
        document.getElementById('dialogText').textContent = line;
        document.getElementById('dialogBox').classList.add('show');
        this.gameState = 'dialog';
    }
    
    closeDialog() {
        document.getElementById('dialogBox').classList.remove('show');
        this.gameState = 'playing';
        this.currentNPC = null;
    }
    
    openChest(chest) {
        if (chest.opened || this.progression.chestsOpened.includes(chest.id)) return;
        
        chest.opened = true;
        this.progression.chestsOpened.push(chest.id);
        this.audio.play('chest');
        
        const content = chest.content;
        if (content.type === 'gold') {
            this.player.gold += content.amount;
            this.showNotification(`Found ${content.amount} Gold!`);
            this.spawnParticles(chest.x, chest.y, 'coin', 12);
        } else if (content.type === 'item') {
            if (content.item === 'HEART_CONTAINER') {
                // Special: immediately apply
                this.player.healthContainers++;
                this.player.maxHealth += 2;
                this.player.health += 2;
                this.showNotification("Heart Container acquired!\nMax health increased!");
                this.audio.play('upgrade');
                this.spawnParticles(chest.x, chest.y, 'heal', 20);
            } else {
                this.addToInventory(content.item, 1);
                const data = this.itemData[content.item];
                this.showNotification(`Found ${data.name}!`);
            }
        } else if (content.type === 'ranged') {
            this.player.hasRanged = true;
            this.progression.foundRangedWeapon = true;
            this.showNotification("Found the STAR BOW!\nPress Q to shoot!");
            this.audio.play('upgrade');
            this.spawnParticles(chest.x, chest.y, 'magic', 25);
        }
        
        this.updateHUD();
    }
    
    showUpgradeOption() {
        // Check eligibility
        const canUpgrade = (this.player.weapon < 2) && 
            (this.getItemCount('METAL_SHARD') >= 1) && 
            (this.player.gold >= (this.player.weapon === 0 ? 100 : 200));
        
        const cost = this.player.weapon === 0 ? 100 : 200;
        const nextWeapon = this.weaponData[this.player.weapon + 1];
        
        if (this.player.weapon >= 2) {
            this.showNotification("Your weapon is already fully upgraded!");
        } else if (!canUpgrade) {
            const haveShards = this.getItemCount('METAL_SHARD');
            this.showNotification(`Need: ${cost} Gold + 1 Metal Shard\nYou have: ${this.player.gold} Gold + ${haveShards} Shard(s)`);
        } else {
            // Do upgrade
            this.removeItem('METAL_SHARD', 1);
            this.player.gold -= cost;
            this.player.weapon++;
            this.progression.weaponUpgrades++;
            
            this.audio.play('upgrade');
            this.spawnParticles(this.player.x, this.player.y, 'spark', 20);
            this.showNotification(`Weapon upgraded to ${nextWeapon.name}!`);
            this.updateHUD();
            this.updateWeaponDisplay();
        }
    }
    
    // ==================== INVENTORY ====================
    addToInventory(itemId, count) {
        const existing = this.player.inventory.find(i => i.id === itemId);
        if (existing) {
            existing.count += count;
        } else {
            this.player.inventory.push({ id: itemId, count });
        }
    }
    
    getItemCount(itemId) {
        const item = this.player.inventory.find(i => i.id === itemId);
        return item ? item.count : 0;
    }
    
    removeItem(itemId, count) {
        const item = this.player.inventory.find(i => i.id === itemId);
        if (item) {
            item.count -= count;
            if (item.count <= 0) {
                this.player.inventory = this.player.inventory.filter(i => i.id !== itemId);
            }
        }
    }
    
    openInventory() {
        this.gameState = 'inventory';
        this.player.selectedItem = null;
        this.renderInventory();
        document.getElementById('inventoryPanel').classList.add('show');
    }
    
    closeInventory() {
        this.gameState = 'playing';
        document.getElementById('inventoryPanel').classList.remove('show');
    }
    
    renderInventory() {
        const grid = document.getElementById('invGrid');
        grid.innerHTML = '';
        
        // 12 slots (3 rows × 4 cols)
        for (let i = 0; i < 12; i++) {
            const slot = document.createElement('div');
            slot.className = 'inv-slot';
            slot.dataset.index = i;
            
            if (i < this.player.inventory.length) {
                const invItem = this.player.inventory[i];
                const data = this.itemData[invItem.id];
                slot.innerHTML = `
                    <span class="item-icon">${data.icon}</span>
                    ${invItem.count > 1 ? `<span class="item-count">×${invItem.count}</span>` : ''}
                `;
                if (this.player.selectedItem === i) {
                    slot.classList.add('selected');
                }
            }
            grid.appendChild(slot);
        }
    }
    
    selectInventorySlot(idx) {
        if (idx >= this.player.inventory.length) return;
        
        this.player.selectedItem = idx;
        this.renderInventory();
        
        const invItem = this.player.inventory[idx];
        const data = this.itemData[invItem.id];
        
        document.getElementById('itemDetails').innerHTML = `
            <div style="font-size:18px; margin-bottom:6px; color:#fff;">
                ${data.icon} ${data.name} ${invItem.count > 1 ? `(×${invItem.count})` : ''}
            </div>
            <div>${data.desc}</div>
        `;
        
        const btn = document.getElementById('useItemBtn');
        btn.disabled = !data.consumable;
    }
    
    useSelectedItem() {
        if (this.player.selectedItem === null) return;
        const invItem = this.player.inventory[this.player.selectedItem];
        if (!invItem) return;
        
        const data = this.itemData[invItem.id];
        if (!data.consumable) return;
        
        // Apply effect
        if (data.effect === 'heal') {
            const healAmount = Math.min(data.value, this.player.maxHealth - this.player.health);
            if (healAmount <= 0) {
                this.showNotification("Already at full health!");
                return;
            }
            this.player.health += healAmount;
            this.spawnParticles(this.player.x, this.player.y, 'heal', 10);
            this.audio.play('pickup');
            this.showNotification(`Healed ${healAmount} HP!`);
        } else if (data.effect === 'container') {
            // Shouldn't happen in inventory - applied on pickup
            this.player.healthContainers++;
            this.player.maxHealth += 2;
            this.player.health += 2;
            this.audio.play('upgrade');
            this.showNotification("Max health increased!");
        }
        
        this.removeItem(invItem.id, 1);
        this.updateHUD();
        this.renderInventory();
        
        // Refresh selection
        if (this.player.selectedItem >= this.player.inventory.length) {
            this.player.selectedItem = null;
            document.getElementById('itemDetails').textContent = 'Select an item to view details.';
            document.getElementById('useItemBtn').disabled = true;
        } else {
            this.selectInventorySlot(this.player.selectedItem);
        }
        
        this.closeInventory();
        this.gameState = 'playing';
    }
    
    useQuickSlot(slot) {
        // Quick slots map to first 4 inventory items
        if (slot < this.player.inventory.length) {
            const invItem = this.player.inventory[slot];
            const data = this.itemData[invItem.id];
            if (data.consumable) {
                this.player.selectedItem = slot;
                this.useSelectedItem();
            }
        }
    }
    
    // ==================== SHOP ====================
    openShop() {
        this.gameState = 'shop';
        this.renderShop();
        document.getElementById('shopPanel').classList.add('show');
    }
    
    closeShop() {
        this.gameState = 'playing';
        document.getElementById('shopPanel').classList.remove('show');
    }
    
    renderShop() {
        const container = document.getElementById('shopItems');
        container.innerHTML = '';
        document.getElementById('shopGold').textContent = this.player.gold;
        
        this.shopItems.forEach((item, idx) => {
            const div = document.createElement('div');
            div.className = 'shop-item';
            div.dataset.index = idx;
            
            const canBuy = this.player.gold >= item.price;
            
            div.innerHTML = `
                <div class="shop-item-info">
                    <span style="font-size:24px; margin-right:12px;">${item.icon}</span>
                    <div>
                        <div style="color:#fff; font-weight:bold;">${item.name}</div>
                        <div style="color:#888; font-size:12px;">${item.desc}</div>
                    </div>
                </div>
                <div class="shop-price" style="${canBuy ? '' : 'color:#666;'}">${item.price} 🪙</div>
            `;
            container.appendChild(div);
        });
    }
    
    tryBuyItem(idx) {
        const item = this.shopItems[idx];
        if (this.player.gold >= item.price) {
            this.player.gold -= item.price;
            this.addToInventory(item.id, 1);
            this.audio.play('pickup');
            this.showNotification(`Purchased ${item.name}!`);
            this.updateHUD();
            this.renderShop();
        } else {
            this.showNotification("Not enough gold!");
        }
    }
    
    // ==================== SAVE PANEL ====================
    openSavePanel() {
        this.gameState = 'save';
        document.getElementById('savePanel').classList.add('show');
    }
    
    closeSavePanel() {
        this.gameState = 'playing';
        document.getElementById('savePanel').classList.remove('show');
    }
    
    // ==================== UI UPDATES ====================
    updateHUD() {
        // Hearts
        const heartsContainer = document.getElementById('heartsContainer');
        heartsContainer.innerHTML = '';
        
        for (let i = 0; i < this.player.healthContainers; i++) {
            const heartHealth = Math.max(0, Math.min(2, this.player.health - i * 2));
            const heartDiv = document.createElement('div');
            heartDiv.className = 'heart';
            
            // 0 = empty, 1 = half, 2 = full
            let heartChar = '🖤';
            if (heartHealth >= 2) heartChar = '❤️';
            else if (heartHealth >= 1) heartChar = '💔';
            
            heartDiv.innerHTML = `<span style="font-size:26px;${heartHealth >= 2 ? 'filter:drop-shadow(0 0 4px #f44);' : ''}">${heartChar}</span>`;
            heartsContainer.appendChild(heartDiv);
        }
        
        // Empty slots (show progression)
        const maxContainers = 5;
        for (let i = this.player.healthContainers; i < maxContainers; i++) {
            const heartDiv = document.createElement('div');
            heartDiv.className = 'heart';
            heartDiv.innerHTML = `<span style="font-size:26px;opacity:0.3;">🖤</span>`;
            heartsContainer.appendChild(heartDiv);
        }
        
        // Gold
        document.getElementById('coinCount').textContent = this.player.gold;
    }
    
    updateWeaponDisplay() {
        const weapon = this.weaponData[this.player.weapon];
        document.getElementById('weaponIcon').textContent = weapon.icon;
        document.getElementById('weaponName').textContent = weapon.name + (this.player.hasRanged ? ' + 🏹' : '');
    }
    
    showNotification(text) {
        const el = document.getElementById('notification');
        el.innerHTML = text.replace(/\n/g, '<br>');
        el.classList.add('show');
        setTimeout(() => el.classList.remove('show'), 3000);
    }
    
    spawnParticles(x, y, type, count) {
        for (let i = 0; i < count; i++) {
            this.particles.push(new Particle(x, y, type));
        }
    }
    
    spawnDamageNumber(x, y, value, color) {
        // Simple text particle
        const p = new Particle(x, y - 10, 'spark');
        p.color = color;
        p.text = '-' + value;
        p.vy = -2;
        p.decay = 0.015;
        p.size = 0;
        this.particles.push(p);
    }
    
    // ==================== RENDERING ====================
    render() {
        const ctx = this.ctx;
        const area = this.world.areas[this.world.currentArea];
        
        // Camera follows player
        const camX = this.player.x - this.canvas.width / 2;
        const camY = this.player.y - this.canvas.height / 2;
        this.world.camera.x = Math.max(0, Math.min(area.width - this.canvas.width, camX));
        this.world.camera.y = Math.max(0, Math.min(area.height - this.canvas.height, camY));
        
        ctx.save();
        ctx.translate(-this.world.camera.x, -this.world.camera.y);
        
        // Background
        ctx.fillStyle = area.bgColor;
        ctx.fillRect(0, 0, area.width, area.height);
        
        // Grid pattern
        ctx.fillStyle = 'rgba(255,255,255,0.02)';
        for (let x = 0; x < area.width; x += 32) {
            for (let y = 0; y < area.height; y += 32) {
                if ((x + y) % 64 === 0) {
                    ctx.fillRect(x, y, 32, 32);
                }
            }
        }
        
        // Exits
        area.exits.forEach(exit => {
            ctx.fillStyle = 'rgba(100, 200, 255, 0.3)';
            ctx.fillRect(exit.x, exit.y, exit.w, exit.h);
            ctx.strokeStyle = '#4af';
            ctx.lineWidth = 2;
            ctx.strokeRect(exit.x, exit.y, exit.w, exit.h);
        });
        
        // Decorations (behind player)
        area.decorations().forEach(dec => {
            if (dec.type === 'path' || dec.type === 'rug') {
                ctx.fillStyle = dec.color;
                if (dec.type === 'rug') {
                    ctx.globalAlpha = 0.6;
                }
                ctx.fillRect(dec.x, dec.y, dec.w, dec.h);
                ctx.globalAlpha = 1;
            }
        });
        
        // Items on ground
        this.items.forEach(item => {
            ctx.save();
            ctx.translate(item.x, item.y + (item.floatY || 0));
            
            ctx.shadowColor = item.gold ? '#fd0' : '#fff';
            ctx.shadowBlur = 10;
            
            ctx.font = '20px sans-serif';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            
            if (item.gold) {
                ctx.fillText('🪙', 0, 0);
            } else if (item.itemId) {
                const data = this.itemData[item.itemId];
                ctx.fillText(data.icon, 0, 0);
            }
            
            ctx.restore();
        });
        
        // Interactables (render some behind, some in front order matters)
        const behindPlayer = [];
        const inFrontPlayer = [];
        
        this.interactables.forEach(ent => {
            if (ent.y < this.player.y) {
                behindPlayer.push(ent);
            } else {
                inFrontPlayer.push(ent);
            }
        });
        
        behindPlayer.forEach(ent => this.renderInteractable(ctx, ent));
        
        // Enemies
        const enemiesBehind = [];
        const enemiesFront = [];
        this.enemies.forEach(e => {
            if (e.y < this.player.y) enemiesBehind.push(e);
            else enemiesFront.push(e);
        });
        
        enemiesBehind.forEach(e => this.renderEnemy(ctx, e));
        
        // Player
        this.renderPlayer(ctx);
        
        enemiesFront.forEach(e => this.renderEnemy(ctx, e));
        inFrontPlayer.forEach(ent => this.renderInteractable(ctx, ent));
        
        // Decorations in front
        area.decorations().forEach(dec => {
            if (dec.type === 'tree' || dec.type === 'rock' || dec.type === 'house' || dec.type === 'counter' || dec.type === 'shelf') {
                // Only draw those that should be in front based on Y vs player... 
                // Actually draw all with Y-based ordering consideration but trees are tall
                const baseY = dec.y + dec.h;
                if (baseY > this.player.y) {
                    this.renderDecoration(ctx, dec);
                }
            }
        });
        
        // Actually re-draw tall decorations properly with bottom-edge Y sorting
        area.decorations().forEach(dec => {
            if (dec.type === 'tree') {
                const bottomY = dec.y + dec.h;
                if (bottomY >= this.player.y - 10) {
                    this.renderDecoration(ctx, dec);
                }
            }
        });
        
        // Attack arc
        if (this.player.attacking) {
            const p = this.player;
            const weapon = this.weaponData[p.weapon];
            
            ctx.save();
            ctx.translate(p.x, p.y);
            ctx.rotate(p.attackAngle);
            
            // Weapon swipe
            ctx.globalAlpha = 0.6;
            ctx.fillStyle = weapon.color;
            ctx.beginPath();
            ctx.moveTo(15, -15);
            ctx.lineTo(weapon.range, -8);
            ctx.lineTo(weapon.range, 8);
            ctx.lineTo(15, 15);
            ctx.closePath();
            ctx.fill();
            
            ctx.restore();
        }
        
        // Projectiles
        this.projectiles.forEach(proj => {
            ctx.save();
            ctx.translate(proj.x, proj.y);
            
            ctx.shadowColor = proj.color;
            ctx.shadowBlur = 10;
            ctx.fillStyle = proj.color;
            ctx.beginPath();
            ctx.arc(0, 0, proj.size, 0, Math.PI * 2);
            ctx.fill();
            
            // Trail
            ctx.globalAlpha = 0.4;
            for (let i = 1; i <= 3; i++) {
                ctx.beginPath();
                ctx.arc(-proj.vx * 0.02 * i, -proj.vy * 0.02 * i, proj.size * (1 - i * 0.2), 0, Math.PI * 2);
                ctx.fill();
            }
            
            ctx.restore();
        });
        
        // Particles
        this.particles.forEach(p => {
            if (p.text) {
                ctx.save();
                ctx.globalAlpha = p.life;
                ctx.font = 'bold 16px sans-serif';
                ctx.fillStyle = p.color;
                ctx.textAlign = 'center';
                ctx.fillText(p.text, p.x, p.y);
                ctx.restore();
            } else {
                p.draw(ctx);
            }
        });
        
        ctx.restore();
        
        // Screen flash when invincible
        if (this.player.invincible > 0) {
            const flash = Math.sin(this.player.invincible * 20) > 0;
            if (flash) {
                ctx.fillStyle = 'rgba(255, 50, 50, 0.2)';
                ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
            }
        }
    }
    
    renderPlayer(ctx) {
        const p = this.player;
        
        ctx.save();
        ctx.translate(p.x, p.y);
        
        // Shadow
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.beginPath();
        ctx.ellipse(0, p.height/2 - 4, 12, 6, 0, 0, Math.PI * 2);
        ctx.fill();
        
        // Body
        const bobY = p.moving ? Math.sin(p.animFrame * Math.PI / 2) * 2 : 0;
        
        ctx.fillStyle = '#4488ff';
        ctx.fillRect(-10, -8 + bobY, 20, 24);
        
        // Head
        ctx.fillStyle = '#ffcc99';
        ctx.beginPath();
        ctx.arc(0, -14 + bobY, 10, 0, Math.PI * 2);
        ctx.fill();
        
        // Hair
        ctx.fillStyle = '#8b4513';
        ctx.beginPath();
        ctx.arc(0, -18 + bobY, 10, Math.PI, 0);
        ctx.fill();
        
        // Eyes based on facing
        ctx.fillStyle = '#000';
        if (p.facing === 'left') {
            ctx.fillRect(-6, -15 + bobY, 3, 3);
        } else if (p.facing === 'right') {
            ctx.fillRect(3, -15 + bobY, 3, 3);
        } else {
            ctx.fillRect(-4, -15 + bobY, 2, 3);
            ctx.fillRect(2, -15 + bobY, 2, 3);
        }
        
        // Weapon at side
        const weapon = this.weaponData[p.weapon];
        ctx.font = '14px sans-serif';
        ctx.textAlign = 'center';
        if (!p.attacking) {
            ctx.fillText(weapon.icon, 14, 4 + bobY);
        }
        
        // Ranged indicator
        if (p.hasRanged) {
            ctx.font = '10px sans-serif';
            ctx.fillText('🏹', -16, 8 + bobY);
        }
        
        ctx.restore();
    }
    
    renderEnemy(ctx, enemy) {
        ctx.save();
        ctx.translate(enemy.x, enemy.y);
        
        // Shadow
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.beginPath();
        ctx.ellipse(0, enemy.height/2 - 2, enemy.width/2 - 4, 6, 0, 0, Math.PI * 2);
        ctx.fill();
        
        // Hit flash
        if (enemy.hitTimer > 0) {
            ctx.globalAlpha = 0.5 + Math.sin(enemy.hitTimer * 30) * 0.5;
        }
        
        const bobY = Math.sin(enemy.animFrame * Math.PI / 2) * 2;
        
        // Boss indicator
        if (enemy.boss) {
            ctx.font = `${enemy.width - 4}px sans-serif`;
        } else {
            ctx.font = `${enemy.width}px sans-serif`;
        }
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(enemy.icon, 0, bobY);
        
        // Health bar
        if (enemy.currentHealth < enemy.health) {
            const barW = enemy.width;
            const barH = 4;
            const pct = enemy.currentHealth / enemy.health;
            
            ctx.fillStyle = '#333';
            ctx.fillRect(-barW/2, -enemy.height/2 - 10, barW, barH);
            ctx.fillStyle = enemy.boss ? '#f4f' : '#f44';
            ctx.fillRect(-barW/2, -enemy.height/2 - 10, barW * pct, barH);
        }
        
        ctx.restore();
    }
    
    renderInteractable(ctx, ent) {
        ctx.save();
        ctx.translate(ent.x, ent.y);
        
        const bobY = Math.sin(ent.animTimer * 2) * 2;
        
        // Glow for important things
        if (ent.type === 'savepoint') {
            ctx.shadowColor = '#4af';
            ctx.shadowBlur = 15 + Math.sin(ent.animTimer * 3) * 5;
        } else if (ent.type === 'chest' && !ent.opened) {
            ctx.shadowColor = '#fd0';
            ctx.shadowBlur = 10;
        } else if (ent.special) {
            ctx.shadowColor = '#f4f';
            ctx.shadowBlur = 20;
        }
        
        ctx.font = '28px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        
        switch(ent.type) {
            case 'npc':
                ctx.fillText(ent.icon, 0, bobY);
                // Name tag
                ctx.font = '11px sans-serif';
                ctx.fillStyle = '#fff';
                ctx.shadowBlur = 0;
                ctx.fillText(ent.name, 0, -24);
                break;
            case 'chest':
                ctx.fillText(ent.opened ? '📭' : '📦', 0, 0);
                break;
            case 'savepoint':
                ctx.fillText('💎', 0, bobY);
                break;
            case 'sign':
                ctx.fillText('🪧', 0, 0);
                break;
            case 'pot':
                ctx.font = '20px sans-serif';
                ctx.fillText('🏺', 0, 0);
                break;
        }
        
        ctx.restore();
    }
    
    renderDecoration(ctx, dec) {
        ctx.save();
        
        switch(dec.type) {
            case 'tree':
                // Trunk
                ctx.fillStyle = '#5a3a1a';
                ctx.fillRect(dec.x + dec.w/2 - 6, dec.y + dec.h - 15, 12, 20);
                // Foliage
                ctx.fillStyle = dec.color;
                ctx.beginPath();
                ctx.moveTo(dec.x + dec.w/2, dec.y);
                ctx.lineTo(dec.x, dec.y + dec.h - 10);
                ctx.lineTo(dec.x + dec.w, dec.y + dec.h - 10);
                ctx.closePath();
                ctx.fill();
                // Round top
                ctx.beginPath();
                ctx.arc(dec.x + dec.w/2, dec.y + dec.h * 0.35, dec.w * 0.45, 0, Math.PI * 2);
                ctx.fill();
                break;
            case 'rock':
                ctx.fillStyle = dec.color;
                ctx.beginPath();
                ctx.moveTo(dec.x + dec.w * 0.1, dec.y + dec.h);
                ctx.lineTo(dec.x, dec.y + dec.h * 0.6);
                ctx.lineTo(dec.x + dec.w * 0.3, dec.y);
                ctx.lineTo(dec.x + dec.w * 0.7, dec.y + dec.h * 0.1);
                ctx.lineTo(dec.x + dec.w, dec.y + dec.h * 0.5);
                ctx.lineTo(dec.x + dec.w * 0.9, dec.y + dec.h);
                ctx.closePath();
                ctx.fill();
                // Highlight
                ctx.fillStyle = 'rgba(255,255,255,0.1)';
                ctx.beginPath();
                ctx.arc(dec.x + dec.w * 0.4, dec.y + dec.h * 0.3, dec.w * 0.2, 0, Math.PI * 2);
                ctx.fill();
                break;
            case 'house':
                // Base
                ctx.fillStyle = dec.color;
                ctx.fillRect(dec.x, dec.y + dec.h * 0.35, dec.w, dec.h * 0.65);
                // Roof
                ctx.fillStyle = '#6b2a00';
                ctx.beginPath();
                ctx.moveTo(dec.x - 10, dec.y + dec.h * 0.35);
                ctx.lineTo(dec.x + dec.w/2, dec.y - 10);
                ctx.lineTo(dec.x + dec.w + 10, dec.y + dec.h * 0.35);
                ctx.closePath();
                ctx.fill();
                // Door
                ctx.fillStyle = '#3a1a00';
                ctx.fillRect(dec.x + dec.w/2 - 12, dec.y + dec.h - 35, 24, 35);
                // Doorknob
                ctx.fillStyle = '#da1';
                ctx.beginPath();
                ctx.arc(dec.x + dec.w/2 + 6, dec.y + dec.h - 18, 3, 0, Math.PI * 2);
                ctx.fill();
                // Window
                ctx.fillStyle = '#88ccff';
                ctx.fillRect(dec.x + 15, dec.y + dec.h * 0.5, 20, 20);
                ctx.fillRect(dec.x + dec.w - 35, dec.y + dec.h * 0.5, 20, 20);
                // Cross
                ctx.strokeStyle = '#5a3a1a';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(dec.x + 25, dec.y + dec.h * 0.5);
                ctx.lineTo(dec.x + 25, dec.y + dec.h * 0.5 + 20);
                ctx.moveTo(dec.x + 15, dec.y + dec.h * 0.5 + 10);
                ctx.lineTo(dec.x + 35, dec.y + dec.h * 0.5 + 10);
                ctx.stroke();
                break;
            case 'counter':
                ctx.fillStyle = dec.color;
                ctx.fillRect(dec.x, dec.y, dec.w, dec.h);
                ctx.fillStyle = '#7a5a3a';
                ctx.fillRect(dec.x, dec.y, dec.w, 15);
                // Items on counter
                ctx.font = '24px sans-serif';
                ctx.fillText('🧪', dec.x + 40, dec.y + 50);
                ctx.fillText('🌿', dec.x + 90, dec.y + 55);
                ctx.fillText('💎', dec.x + 140, dec.y + 48);
                break;
            case 'shelf':
                ctx.fillStyle = dec.color;
                ctx.fillRect(dec.x, dec.y, dec.w, dec.h);
                ctx.fillStyle = '#4a2a0a';
                ctx.fillRect(dec.x, dec.y, dec.w, 8);
                ctx.fillRect(dec.x, dec.y + dec.h - 8, dec.w, 8);
                break;
        }
        
        ctx.restore();
    }
    
    renderMinimap() {
        const mctx = this.minimapCtx;
        const area = this.world.areas[this.world.currentArea];
        const scale = 120 / Math.max(area.width, area.height);
        
        mctx.fillStyle = 'rgba(0,0,0,0.8)';
        mctx.fillRect(0, 0, 120, 120);
        
        // Background color
        mctx.fillStyle = area.bgColor;
        mctx.globalAlpha = 0.5;
        mctx.fillRect(0, 0, area.width * scale, area.height * scale);
        mctx.globalAlpha = 1;
        
        // Decorations (blocking)
        area.decorations().forEach(dec => {
            if (dec.type === 'tree' || dec.type === 'rock' || dec.type === 'house') {
                mctx.fillStyle = dec.type === 'tree' ? '#0a2a0a' : '#333';
                mctx.fillRect(dec.x * scale, dec.y * scale, Math.max(2, dec.w * scale), Math.max(2, dec.h * scale));
            }
        });
        
        // Exits
        area.exits.forEach(exit => {
            mctx.fillStyle = '#4af';
            mctx.fillRect(exit.x * scale, exit.y * scale, Math.max(3, exit.w * scale), Math.max(3, exit.h * scale));
        });
        
        // Interactables
        this.interactables.forEach(ent => {
            if (ent.type === 'savepoint') {
                mctx.fillStyle = '#4af';
            } else if (ent.type === 'chest' && !ent.opened) {
                mctx.fillStyle = '#fd0';
            } else if (ent.type === 'npc') {
                mctx.fillStyle = '#fa4';
            } else {
                return;
            }
            mctx.beginPath();
            mctx.arc(ent.x * scale, ent.y * scale, 3, 0, Math.PI * 2);
            mctx.fill();
        });
        
        // Enemies
        this.enemies.forEach(enemy => {
            mctx.fillStyle = enemy.boss ? '#f4f' : '#f44';
            const size = enemy.boss ? 4 : 2;
            mctx.beginPath();
            mctx.arc(enemy.x * scale, enemy.y * scale, size, 0, Math.PI * 2);
            mctx.fill();
        });
        
        // Player
        mctx.fillStyle = '#4f4';
        mctx.beginPath();
        mctx.arc(this.player.x * scale, this.player.y * scale, 4, 0, Math.PI * 2);
        mctx.fill();
        
        // Viewport
        mctx.strokeStyle = 'rgba(255,255,255,0.5)';
        mctx.lineWidth = 1;
        mctx.strokeRect(
            this.world.camera.x * scale,
            this.world.camera.y * scale,
            this.canvas.width * scale,
            this.canvas.height * scale
        );
    }
}

const game = new Game();

// Start screen music / context wakeup
document.addEventListener('click', () => {
    if (!game.audio.ctx) {
        game.audio.init();
    }
}, { once: true });
</script>
</body>
</html>
<!-- agent-meta {"model":"bytedance-seed/seed-2.0-code","provider":"openrouter","persona":"full","sessionId":"176f28cc-c72d-43eb-b423-dd9a82581232","tokensIn":445867,"tokensOut":30789,"tokensTotal":476656,"cost":0.3153005,"turns":14,"toolCalls":13,"failedToolCalls":0,"timestamp":"2026-08-15T21:06:27.885Z"} -->