about summary refs log tree commit diff stats
path: root/html/rogue/js
diff options
context:
space:
mode:
Diffstat (limited to 'html/rogue/js')
-rw-r--r--html/rogue/js/animation.js53
-rw-r--r--html/rogue/js/camera.js145
-rw-r--r--html/rogue/js/config.js115
-rw-r--r--html/rogue/js/debug.js113
-rw-r--r--html/rogue/js/events.js16
-rw-r--r--html/rogue/js/fow.js85
-rw-r--r--html/rogue/js/game.js121
-rw-r--r--html/rogue/js/hex.js98
-rw-r--r--html/rogue/js/input.js19
-rw-r--r--html/rogue/js/inventory-ui.js63
-rw-r--r--html/rogue/js/items.js63
-rw-r--r--html/rogue/js/player.js281
-rw-r--r--html/rogue/js/renderer.js120
-rw-r--r--html/rogue/js/rogue.js144
-rw-r--r--html/rogue/js/state.js17
-rw-r--r--html/rogue/js/utils.js25
-rw-r--r--html/rogue/js/world.js1054
17 files changed, 1090 insertions, 1442 deletions
diff --git a/html/rogue/js/animation.js b/html/rogue/js/animation.js
new file mode 100644
index 0000000..d682b01
--- /dev/null
+++ b/html/rogue/js/animation.js
@@ -0,0 +1,53 @@
+const Animation = {
+    // Track loaded images with their paths as keys
+    images: new Map(),
+    
+    // Load an image and return a promise
+    loadImage(path) {
+        if (this.images.has(path)) {
+            return Promise.resolve(this.images.get(path));
+        }
+        
+        return new Promise((resolve, reject) => {
+            const img = new Image();
+            img.onload = () => {
+                this.images.set(path, img);
+                resolve(img);
+            };
+            img.onerror = () => reject(new Error(`Failed to load image: ${path}`));
+            img.src = path;
+        });
+    },
+    
+    // Animation class to handle sprite animations
+    createAnimation(frames, frameTime) {
+        return {
+            frames,
+            frameTime,
+            currentFrame: 0,
+            lastFrameTime: 0,
+            
+            update(currentTime) {
+                if (currentTime - this.lastFrameTime >= this.frameTime) {
+                    this.currentFrame = (this.currentFrame + 1) % this.frames.length;
+                    this.lastFrameTime = currentTime;
+                }
+                return this.frames[this.currentFrame];
+            }
+        };
+    },
+    
+    // Add this new method to scale sprites to fit within bounds
+    scaleToFit(image, maxWidth, maxHeight) {
+        const scale = Math.min(
+            maxWidth / image.width,
+            maxHeight / image.height
+        );
+        
+        return {
+            width: image.width * scale,
+            height: image.height * scale,
+            scale
+        };
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/camera.js b/html/rogue/js/camera.js
index e5d5d14..05e0f28 100644
--- a/html/rogue/js/camera.js
+++ b/html/rogue/js/camera.js
@@ -1,56 +1,93 @@
-const createCamera = (x, y) => ({
-    x,
-    y,
-    width: window.innerWidth,
-    height: window.innerHeight,
-    // Define the dead zone (the area where camera won't move)
-    deadZone: {
-        x: window.innerWidth * 0.3, // 30% of screen width
-        y: window.innerHeight * 0.3, // 30% of screen height
-    }
-});
-
-const updateCamera = (camera, target) => {
-    // Calculate the center point of the screen
-    const screenCenterX = camera.x + camera.width / 2;
-    const screenCenterY = camera.y + camera.height / 2;
-
-    // Calculate the distance from the target to the screen center
-    const distanceX = target.x - screenCenterX;
-    const distanceY = target.y - screenCenterY;
-
-    // Calculate the dead zone boundaries
-    const deadZoneLeft = -camera.deadZone.x / 2;
-    const deadZoneRight = camera.deadZone.x / 2;
-    const deadZoneTop = -camera.deadZone.y / 2;
-    const deadZoneBottom = camera.deadZone.y / 2;
-
-    // Calculate new camera position with smooth following
-    let newX = camera.x;
-    let newY = camera.y;
+const Camera = {
+    x: 0,
+    y: 0,
+    
+    centerOn(hex) {
+        const pixelCoord = HexGrid.toPixel(hex);
+        
+        // Calculate desired camera position
+        this.x = pixelCoord.x - state.canvas.width / 2;
+        this.y = pixelCoord.y - state.canvas.height / 2;
+        
+        // Calculate grid dimensions
+        const gridPixelWidth = Config.hex.GRID_SIZE * Config.hex.WIDTH;
+        const gridPixelHeight = Config.hex.GRID_SIZE * Config.hex.HEIGHT;
+        
+        // Calculate grid bounds (accounting for centered grid)
+        const minX = -gridPixelWidth / 2;
+        const maxX = gridPixelWidth / 2 - state.canvas.width;
+        const minY = -gridPixelHeight / 2;
+        const maxY = gridPixelHeight / 2 - state.canvas.height;
+        
+        // Keep camera within grid bounds
+        this.x = Math.max(minX, Math.min(this.x, maxX));
+        this.y = Math.max(minY, Math.min(this.y, maxY));
+        
+        // Round to prevent sub-pixel rendering
+        this.x = Math.round(this.x);
+        this.y = Math.round(this.y);
+    },
 
-    // Horizontal camera movement
-    if (distanceX < deadZoneLeft) {
-        newX += distanceX - deadZoneLeft;
-    } else if (distanceX > deadZoneRight) {
-        newX += distanceX - deadZoneRight;
+    smoothFollow(target) {
+        const targetPixel = HexGrid.toPixel(target);
+        const screenX = Math.round(targetPixel.x - this.x);
+        const screenY = Math.round(targetPixel.y - this.y);
+        
+        const centerX = state.canvas.width / 2;
+        const centerY = state.canvas.height / 2;
+        
+        // Determine if we're on a narrow screen
+        const isNarrowScreen = state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD;
+        
+        // Calculate dynamic deadzones based on screen size
+        const deadzoneX = Math.min(
+            Math.max(
+                state.canvas.width * (
+                    isNarrowScreen ? 
+                    Config.camera.DEADZONE_RATIO_X.NARROW : 
+                    Config.camera.DEADZONE_RATIO_X.WIDE
+                ),
+                Config.camera.MIN_DEADZONE
+            ),
+            Config.camera.MAX_DEADZONE
+        );
+        
+        const deadzoneY = Math.min(
+            Math.max(state.canvas.height * Config.camera.DEADZONE_RATIO_Y, 
+                Config.camera.MIN_DEADZONE
+            ),
+            Config.camera.MAX_DEADZONE
+        );
+        
+        const deltaX = screenX - centerX;
+        const deltaY = screenY - centerY;
+        
+        // Use more aggressive follow speed for narrow screens
+        const followSpeed = isNarrowScreen ? 
+            Config.camera.FOLLOW_SPEED * 1.5 : 
+            Config.camera.FOLLOW_SPEED;
+        
+        // Ensure camera moves if player is near screen edges
+        if (Math.abs(deltaX) > deadzoneX) {
+            const adjustX = deltaX - (deltaX > 0 ? deadzoneX : -deadzoneX);
+            this.x += Math.round(adjustX * followSpeed);
+        }
+        
+        if (Math.abs(deltaY) > deadzoneY) {
+            const adjustY = deltaY - (deltaY > 0 ? deadzoneY : -deadzoneY);
+            this.y += Math.round(adjustY * followSpeed);
+        }
+        
+        // Calculate grid bounds (accounting for centered grid)
+        const gridPixelWidth = Config.hex.GRID_SIZE * Config.hex.WIDTH;
+        const gridPixelHeight = Config.hex.GRID_SIZE * Config.hex.HEIGHT;
+        const minX = -gridPixelWidth / 2;
+        const maxX = gridPixelWidth / 2 - state.canvas.width;
+        const minY = -gridPixelHeight / 2;
+        const maxY = gridPixelHeight / 2 - state.canvas.height;
+        
+        // Keep camera within grid bounds
+        this.x = Math.max(minX, Math.min(this.x, maxX));
+        this.y = Math.max(minY, Math.min(this.y, maxY));
     }
-
-    // Vertical camera movement
-    if (distanceY < deadZoneTop) {
-        newY += distanceY - deadZoneTop;
-    } else if (distanceY > deadZoneBottom) {
-        newY += distanceY - deadZoneBottom;
-    }
-
-    // Add subtle smoothing to camera movement
-    const smoothing = 0.1;
-    newX = camera.x + (newX - camera.x) * smoothing;
-    newY = camera.y + (newY - camera.y) * smoothing;
-
-    return {
-        ...camera,
-        x: newX,
-        y: newY
-    };
-};
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/config.js b/html/rogue/js/config.js
index 77100e1..6ed925f 100644
--- a/html/rogue/js/config.js
+++ b/html/rogue/js/config.js
@@ -1,32 +1,97 @@
-const CONFIG = {
+const Config = {
+    colors: {
+        BACKGROUND: 'rgba(135, 207, 235, 1)',
+        GRID: 'rgba(0, 0, 0, 0.25)',
+        PLAYER: 'red',
+        HEX_FILL: '#ffffff',
+        FOG: {
+            HIDDEN: 'rgba(0, 0, 0, 1)',
+            REVEALED: 'rgba(0, 0, 0, 0.25)',
+            GRID_DIM: 'rgba(0, 0, 0, 0.0)'
+        },
+        UI: {
+            INVENTORY: {
+                BACKGROUND: 'rgba(0, 0, 0, 0.7)',
+                WINDOW: '#ffffff',
+                TEXT: '#000000'
+            }
+        },
+        ITEMS: {
+            COIN: '#FFD700',
+            GEM: '#50C878'
+        }
+    },
+    
+    hex: {
+        SIZE: 40, // Size of a single hex
+        GRID_SIZE: 30, // Number of hexes in the grid (width/height)
+        get WIDTH() { // Computed hex width
+            return this.SIZE * 2;
+        },
+        get HEIGHT() { // Computed hex height
+            return Math.sqrt(3) * this.SIZE;
+        }
+    },
+
+    game: {
+        FPS: 60,
+        get FRAME_TIME() {
+            return 1000 / this.FPS;
+        }
+    },
+
     player: {
-        width: 32,
-        height: 48,
-        speed: 5,
-        jumpForce: 12,
-        gravity: 0.5,
-        color: '#ff0000'
+        MOVE_SPEED: 0.1,
+        SIZE_RATIO: 1/3,
+        VISION_RANGE: 3,
+        SPRITE_SCALE: 0.8,
+        ANIMATION_SPEED: 500
     },
-    world: {
-        groundHeight: 12,
-        wilderness: {
-            vegetation: {
-                grass: {
-                    colors: ['#3a5', '#294'],
-                    hatch: {
-                        angle: Math.PI / 4,
-                        variation: Math.PI / 6,
-                        spacing: 4,
-                        length: 8,
-                        margin: 2
-                    }
-                }
+
+    camera: {
+        FOLLOW_SPEED: 0.1,
+        DEADZONE_RATIO_X: {
+            NARROW: 0.1,
+            WIDE: 0.2
+        },
+        DEADZONE_RATIO_Y: 0.2,
+        MIN_DEADZONE: 30,
+        MAX_DEADZONE: 200,
+        NARROW_SCREEN_THRESHOLD: 600
+    },
+
+    ui: {
+        inventory: {
+            PADDING: 20,
+            WIDTH: 300,
+            HEIGHT: 400,
+            TITLE_FONT: '20px Arial',
+            ITEM_FONT: '16px Arial',
+            ITEM_SPACING: 30,
+            TITLE_OFFSET: 20,
+            ITEMS_START_OFFSET: 60
+        }
+    },
+    
+    items: {
+        SPAWN_COUNT: 10,
+        types: {
+            COIN: {
+                name: 'Coin',
+                size: 0.2
+            },
+            GEM: {
+                name: 'Gem',
+                size: 0.25
             }
         }
     },
-    camera: {
-        width: window.innerWidth,
-        height: window.innerHeight,
-        followSpeed: 0.1
+
+    fog: {
+        states: {
+            HIDDEN: { alpha: 1.0 },
+            REVEALED: { alpha: 0.5 },
+            VISIBLE: { alpha: 0 }
+        }
     }
 }; 
\ No newline at end of file
diff --git a/html/rogue/js/debug.js b/html/rogue/js/debug.js
new file mode 100644
index 0000000..f2e0b02
--- /dev/null
+++ b/html/rogue/js/debug.js
@@ -0,0 +1,113 @@
+const Debug = {
+    isEnabled: false,
+    lastFrameTime: performance.now(),
+    frameCount: 0,
+    fps: 0,
+    fpsUpdateInterval: 500, // Update FPS display every 500ms
+    lastFpsUpdate: 0,
+    
+    init() {
+        // Add keyboard listener for debug toggle
+        window.addEventListener('keydown', (e) => {
+            if (e.key.toLowerCase() === 'd') {
+                this.isEnabled = !this.isEnabled;
+            }
+        });
+    },
+    
+    update(currentTime) {
+        if (!this.isEnabled) return;
+        
+        this.frameCount++;
+        
+        // Update FPS counter every 500ms
+        if (currentTime - this.lastFpsUpdate >= this.fpsUpdateInterval) {
+            this.fps = Math.round((this.frameCount * 1000) / (currentTime - this.lastFpsUpdate));
+            this.frameCount = 0;
+            this.lastFpsUpdate = currentTime;
+        }
+        
+        this.lastFrameTime = currentTime;
+    },
+    
+    draw(ctx) {
+        if (!this.isEnabled) return;
+        
+        const padding = 30;
+        const lineHeight = 20;
+        let y = padding;
+        
+        // Save context state
+        ctx.save();
+        
+        // Set up debug text style
+        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
+        ctx.fillRect(0, 0, 300, 200);
+        ctx.font = '14px monospace';
+        ctx.fillStyle = '#00FF00';
+        
+        // Display debug information
+        const debugInfo = [
+            `FPS: ${this.fps}`,
+            `Camera: (${Math.round(Camera.x)}, ${Math.round(Camera.y)})`,
+            `Player Hex: (${player.position.q}, ${player.position.r})`,
+            `Screen: ${state.canvas.width}x${state.canvas.height}`,
+            `Inventory Items: ${player.inventory.length}`,
+            `Revealed Hexes: ${FogOfWar.revealed.size}`,
+            `Moving: ${player.target ? 'Yes' : 'No'}`,
+            `Narrow Screen: ${state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD}`
+        ];
+        
+        debugInfo.forEach(info => {
+            ctx.fillText(info, padding, y);
+            y += lineHeight;
+        });
+        
+        // Draw deadzone visualization
+        if (player.target) {
+            const isNarrowScreen = state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD;
+            const deadzoneX = Math.min(
+                Math.max(
+                    state.canvas.width * (
+                        isNarrowScreen ? 
+                        Config.camera.DEADZONE_RATIO_X.NARROW : 
+                        Config.camera.DEADZONE_RATIO_X.WIDE
+                    ),
+                    Config.camera.MIN_DEADZONE
+                ),
+                Config.camera.MAX_DEADZONE
+            );
+            const deadzoneY = Math.min(
+                Math.max(state.canvas.height * Config.camera.DEADZONE_RATIO_Y, 
+                    Config.camera.MIN_DEADZONE
+                ),
+                Config.camera.MAX_DEADZONE
+            );
+            
+            // Draw camera deadzone
+            ctx.strokeStyle = 'rgba(255, 162, 0, 1)';
+            ctx.lineWidth = 2;
+            ctx.strokeRect(
+                state.canvas.width/2 - deadzoneX,
+                state.canvas.height/2 - deadzoneY,
+                deadzoneX * 2,
+                deadzoneY * 2
+            );
+
+            // Draw a small cross at the center of the camera deadzone
+            const centerX = state.canvas.width / 2;
+            const centerY = state.canvas.height / 2;
+            const crossSize = 10; // Size of the cross arms
+
+            ctx.beginPath();
+            ctx.moveTo(centerX - crossSize, centerY);
+            ctx.lineTo(centerX + crossSize, centerY);
+            ctx.moveTo(centerX, centerY - crossSize);
+            ctx.lineTo(centerX, centerY + crossSize);
+            ctx.stroke();
+        }
+        
+        // Restore context state
+        ctx.restore();
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/events.js b/html/rogue/js/events.js
new file mode 100644
index 0000000..9ae8241
--- /dev/null
+++ b/html/rogue/js/events.js
@@ -0,0 +1,16 @@
+const EventSystem = {
+    listeners: new Map(),
+    
+    on(event, callback) {
+        if (!this.listeners.has(event)) {
+            this.listeners.set(event, new Set());
+        }
+        this.listeners.get(event).add(callback);
+    },
+    
+    emit(event, data) {
+        if (this.listeners.has(event)) {
+            this.listeners.get(event).forEach(callback => callback(data));
+        }
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/fow.js b/html/rogue/js/fow.js
new file mode 100644
index 0000000..291c862
--- /dev/null
+++ b/html/rogue/js/fow.js
@@ -0,0 +1,85 @@
+const FogOfWar = {
+    // Set of revealed hex coordinates (as strings)
+    revealed: new Set(),
+    
+    // Configuration
+    VISION_RANGE: Config.player.VISION_RANGE,
+    
+    init() {
+        this.revealed.clear();
+        this.updateVisibility(player.position);
+    },
+    
+    // Convert hex to string key for Set storage
+    hexToKey(hex) {
+        return `${hex.q},${hex.r}`;
+    },
+    
+    // Check if a hex is currently visible
+    isVisible(hex) {
+        const playerPos = player.getCurrentPosition();
+        const distance = this.getHexDistance(hex, playerPos);
+        return distance <= this.VISION_RANGE;
+    },
+    
+    // Check if a hex has been revealed
+    isRevealed(hex) {
+        return this.revealed.has(this.hexToKey(hex));
+    },
+    
+    // Calculate distance between two hexes
+    getHexDistance(a, b) {
+        return Math.max(
+            Math.abs(a.q - b.q),
+            Math.abs(a.r - b.r),
+            Math.abs((a.q + a.r) - (b.q + b.r))
+        );
+    },
+    
+    // Update visibility based on player position
+    updateVisibility(center) {
+        // Get all hexes within vision range
+        for (let q = -this.VISION_RANGE; q <= this.VISION_RANGE; q++) {
+            for (let r = -this.VISION_RANGE; r <= this.VISION_RANGE; r++) {
+                const hex = {
+                    q: center.q + q,
+                    r: center.r + r
+                };
+                
+                if (this.getHexDistance(center, hex) <= this.VISION_RANGE) {
+                    this.revealed.add(this.hexToKey(hex));
+                }
+            }
+        }
+    },
+    
+    getFogState(hex) {
+        if (!this.isRevealed(hex)) return Config.fog.states.HIDDEN;
+        if (!this.isVisible(hex)) return Config.fog.states.REVEALED;
+        return Config.fog.states.VISIBLE;
+    },
+    
+    // Draw fog of war effect
+    draw(ctx) {
+        HexGrid.getViewportHexes().forEach(hex => {
+            const fogState = this.getFogState(hex);
+            if (fogState.alpha > 0) {
+                const screen = HexGrid.toScreenCoordinates(hex, Camera);
+                
+                // Draw fog fill
+                ctx.fillStyle = fogState === Config.fog.states.HIDDEN ? 
+                    Config.colors.FOG.HIDDEN : 
+                    Config.colors.FOG.REVEALED;
+                HexGrid.drawHexPath(ctx, screen.x, screen.y, HexGrid.SIZE, 1);
+                ctx.fill();
+                
+                // Draw grid lines only for revealed but not visible hexes
+                if (fogState === Config.fog.states.REVEALED) {
+                    ctx.strokeStyle = Config.colors.FOG.GRID_DIM;
+                    ctx.lineWidth = 1;
+                    ctx.stroke();
+                }
+            }
+        });
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/game.js b/html/rogue/js/game.js
new file mode 100644
index 0000000..48133d8
--- /dev/null
+++ b/html/rogue/js/game.js
@@ -0,0 +1,121 @@
+const FPS = 60;
+const FRAME_TIME = 1000 / FPS;
+let lastFrameTime = 0;
+
+const state = {
+    canvas: null,
+    ctx: null
+};
+
+async function init() {
+    state.canvas = document.getElementById('gameCanvas');
+    state.ctx = state.canvas.getContext('2d');
+    
+    await player.init();
+    Debug.init();
+    
+    function resize() {
+        state.canvas.width = window.innerWidth;
+        state.canvas.height = window.innerHeight;
+        Camera.centerOn(player.position);
+    }
+    
+    window.addEventListener('resize', resize);
+    resize();
+    state.canvas.addEventListener('click', handleClick);
+    
+    requestAnimationFrame(gameLoop);
+    FogOfWar.init();
+    Items.init();
+}
+
+function drawHex(ctx, x, y, hex) {
+    const screen = HexGrid.toScreenCoordinates(hex, Camera);
+    
+    // Only draw if hex is visible on screen (with padding)
+    if (!HexGrid.isInViewport(screen.x, screen.y, state.canvas)) {
+        return;
+    }
+    
+    // Skip drawing completely if hex hasn't been revealed
+    if (!FogOfWar.isRevealed(hex)) {
+        return;
+    }
+    
+    // Draw the hex fill
+    HexGrid.drawHexPath(ctx, screen.x, screen.y);
+    ctx.fillStyle = Config.colors.HEX_FILL;
+    ctx.fill();
+    
+    // Only draw grid lines for currently visible hexes
+    // (fog of war will handle the grid lines for revealed but not visible hexes)
+    if (FogOfWar.isVisible(hex)) {
+        ctx.strokeStyle = Config.colors.GRID;
+        ctx.lineWidth = 1;
+        ctx.stroke();
+    }
+}
+
+function gameLoop(currentTime) {
+    requestAnimationFrame(gameLoop);
+    
+    if (currentTime - lastFrameTime < Config.game.FRAME_TIME) {
+        return;
+    }
+    
+    const deltaTime = Math.min(currentTime - lastFrameTime, Config.game.FRAME_TIME * 2);
+    lastFrameTime = currentTime;
+
+    // Update debug information
+    Debug.update(currentTime);
+
+    // Clear with background
+    state.ctx.fillStyle = Config.colors.BACKGROUND;
+    state.ctx.fillRect(0, 0, state.canvas.width, state.canvas.height);
+    
+    // Round camera position to prevent sub-pixel rendering
+    Camera.x = Math.round(Camera.x);
+    Camera.y = Math.round(Camera.y);
+    
+    player.update();
+    Camera.smoothFollow(player.getCurrentPosition());
+    
+    if (player.hasMoved) {
+        FogOfWar.updateVisibility(player.position);
+        player.hasMoved = false;
+    }
+    
+    // Draw everything in one pass to prevent flicker
+    HexGrid.getViewportHexes().forEach(hex => {
+        const pixel = HexGrid.toPixel(hex);
+        drawHex(state.ctx, Math.round(pixel.x), Math.round(pixel.y), hex);
+    });
+    
+    Items.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
+    player.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
+    FogOfWar.draw(state.ctx);
+    InventoryUI.draw(state.ctx);
+    Debug.draw(state.ctx);
+}
+
+function handleClick(event) {
+    if (InventoryUI.isOpen) {
+        InventoryUI.toggleInventory();
+        return;
+    }
+    
+    const rect = state.canvas.getBoundingClientRect();
+    const x = event.clientX - rect.left;
+    const y = event.clientY - rect.top;
+    
+    const hexCoord = HexGrid.fromPixel(x + Camera.x, y + Camera.y);
+    
+    // Check if clicking on player's position
+    if (hexCoord.q === player.position.q && hexCoord.r === player.position.r) {
+        InventoryUI.toggleInventory();
+    } else {
+        player.moveTo(hexCoord);
+    }
+}
+
+init().catch(console.error);
\ No newline at end of file
diff --git a/html/rogue/js/hex.js b/html/rogue/js/hex.js
new file mode 100644
index 0000000..fa08e3d
--- /dev/null
+++ b/html/rogue/js/hex.js
@@ -0,0 +1,98 @@
+// This that witchy shit -- we be hexin!
+
+const HexGrid = {
+    get SIZE() { return Config.hex.SIZE },
+    get WIDTH() { return Config.hex.WIDTH },
+    get HEIGHT() { return Config.hex.HEIGHT },
+    get GRID_SIZE() { return Config.hex.GRID_SIZE },
+    COLOR: Config.colors.GRID,
+
+    // hex to pixel
+    toPixel(hex) {
+        const x = this.SIZE * (3/2 * hex.q);
+        const y = this.SIZE * (Math.sqrt(3)/2 * hex.q + Math.sqrt(3) * hex.r);
+        return {x, y};
+    },
+
+    // pixel to hex
+    fromPixel(x, y) {
+        const q = (2/3 * x) / this.SIZE;
+        const r = (-1/3 * x + Math.sqrt(3)/3 * y) / this.SIZE;
+        return this.round(q, r);
+    },
+
+    // Round hex coordinates to nearest hex
+    round(q, r) {
+        let x = q;
+        let z = r;
+        let y = -x-z;
+
+        let rx = Math.round(x);
+        let ry = Math.round(y);
+        let rz = Math.round(z);
+
+        const x_diff = Math.abs(rx - x);
+        const y_diff = Math.abs(ry - y);
+        const z_diff = Math.abs(rz - z);
+
+        if (x_diff > y_diff && x_diff > z_diff) {
+            rx = -ry-rz;
+        } else if (y_diff > z_diff) {
+            ry = -rx-rz;
+        } else {
+            rz = -rx-ry;
+        }
+
+        return {q: rx, r: rz};
+    },
+
+    // Is this hex in the viewport?
+    getViewportHexes() {
+        const hexes = [];
+        const halfGrid = Math.floor(this.GRID_SIZE / 2);
+        
+        for (let r = -halfGrid; r < halfGrid; r++) {
+            for (let q = -halfGrid; q < halfGrid; q++) {
+                hexes.push({q, r});
+            }
+        }
+        return hexes;
+    },
+
+    // Check if a hex is passable
+    isPassable(hex) {
+        const halfGrid = Math.floor(this.GRID_SIZE / 2);
+        return Math.abs(hex.q) <= halfGrid && Math.abs(hex.r) <= halfGrid;
+    },
+
+    // Centralized hex drawing function
+    drawHexPath(ctx, x, y, size = this.SIZE, padding = 0) {
+        ctx.beginPath();
+        for (let i = 0; i < 6; i++) {
+            const angle = 2 * Math.PI / 6 * i;
+            const xPos = Math.round(x + (size + padding) * Math.cos(angle));
+            const yPos = Math.round(y + (size + padding) * Math.sin(angle));
+            if (i === 0) {
+                ctx.moveTo(xPos, yPos);
+            } else {
+                ctx.lineTo(xPos, yPos);
+            }
+        }
+        ctx.closePath();
+    },
+
+    toScreenCoordinates(hex, camera) {
+        const pixel = this.toPixel(hex);
+        return {
+            x: Math.round(pixel.x - camera.x),
+            y: Math.round(pixel.y - camera.y)
+        };
+    },
+
+    isInViewport(screenX, screenY, canvas) {
+        return !(screenX < -this.WIDTH || 
+                screenX > canvas.width + this.WIDTH ||
+                screenY < -this.HEIGHT || 
+                screenY > canvas.height + this.HEIGHT);
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/input.js b/html/rogue/js/input.js
deleted file mode 100644
index 047321c..0000000
--- a/html/rogue/js/input.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const setupInputHandlers = (canvas, gameState) => {
-    let mouseMoveThrottle;
-    
-    canvas.addEventListener('mousemove', (e) => {
-        if (!mouseMoveThrottle) {
-            mouseMoveThrottle = setTimeout(() => {
-                gameState.debug.mouseX = e.clientX + gameState.camera.x;
-                gameState.debug.mouseY = e.clientY + gameState.camera.y;
-                mouseMoveThrottle = null;
-            }, 16);
-        }
-    });
-
-    window.addEventListener('keydown', (e) => {
-        if (e.key === 'd') {
-            gameState.debug.enabled = !gameState.debug.enabled;
-        }
-    });
-}; 
\ No newline at end of file
diff --git a/html/rogue/js/inventory-ui.js b/html/rogue/js/inventory-ui.js
new file mode 100644
index 0000000..c7ce63c
--- /dev/null
+++ b/html/rogue/js/inventory-ui.js
@@ -0,0 +1,63 @@
+const InventoryUI = {
+    isOpen: false,
+    
+    toggleInventory() {
+        this.isOpen = !this.isOpen;
+    },
+    
+    // Helper function to count items by type
+    getItemCounts() {
+        const counts = new Map();
+        
+        player.inventory.forEach(item => {
+            const itemName = item.type.name;
+            counts.set(itemName, (counts.get(itemName) || 0) + 1);
+        });
+        
+        return counts;
+    },
+    
+    draw(ctx) {
+        if (!this.isOpen) return;
+        
+        // Draw semi-transparent background
+        ctx.fillStyle = Config.colors.UI.INVENTORY.BACKGROUND;
+        ctx.fillRect(0, 0, state.canvas.width, state.canvas.height);
+        
+        // Calculate positions ensuring integer values
+        const padding = Config.ui.inventory.PADDING;
+        const width = Config.ui.inventory.WIDTH;
+        const height = Config.ui.inventory.HEIGHT;
+        const x = Math.round((state.canvas.width - width) / 2);
+        const y = Math.round((state.canvas.height - height) / 2);
+        
+        // Draw window background
+        ctx.fillStyle = Config.colors.UI.INVENTORY.WINDOW;
+        ctx.fillRect(x, y, width, height);
+        
+        // Set text rendering properties for sharper text
+        ctx.textBaseline = 'top';
+        ctx.textAlign = 'left';
+        ctx.imageSmoothingEnabled = false;
+        
+        // Draw title
+        ctx.fillStyle = Config.colors.UI.INVENTORY.TEXT;
+        ctx.font = Config.ui.inventory.TITLE_FONT;
+        const titleX = Math.round(x + padding);
+        const titleY = Math.round(y + padding);
+        ctx.fillText('Inventory', titleX, titleY);
+        
+        // Get item counts and draw items with quantities
+        const itemCounts = this.getItemCounts();
+        let index = 0;
+        
+        itemCounts.forEach((count, itemName) => {
+            const itemX = Math.round(x + padding);
+            const itemY = Math.round(y + Config.ui.inventory.ITEMS_START_OFFSET + 
+                                   (index * Config.ui.inventory.ITEM_SPACING));
+            ctx.font = Config.ui.inventory.ITEM_FONT;
+            ctx.fillText(`${itemName} x${count}`, itemX, itemY);
+            index++;
+        });
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/items.js b/html/rogue/js/items.js
new file mode 100644
index 0000000..dc6dc1e
--- /dev/null
+++ b/html/rogue/js/items.js
@@ -0,0 +1,63 @@
+const Items = {
+    items: new Map(), // Map of items on the grid, key is "q,r"
+    
+    // Item types
+    TYPES: {
+        COIN: {
+            name: 'Coin',
+            color: '#FFD700',
+            size: 0.2 // Size relative to hex
+        },
+        GEM: {
+            name: 'Gem',
+            color: '#50C878',
+            size: 0.25
+        }
+    },
+    
+    // Initialize items on the map
+    init() {
+        this.items.clear();
+        
+        // Add some random items
+        for (let i = 0; i < 10; i++) {
+            const q = Math.floor(Math.random() * HexGrid.GRID_SIZE - HexGrid.GRID_SIZE/2);
+            const r = Math.floor(Math.random() * HexGrid.GRID_SIZE - HexGrid.GRID_SIZE/2);
+            
+            // Don't place items at player start position
+            if (q !== 0 || r !== 0) {
+                const type = Math.random() < 0.5 ? this.TYPES.COIN : this.TYPES.GEM;
+                this.addItem(q, r, type);
+            }
+        }
+    },
+    
+    // Add an item to the map
+    addItem(q, r, type) {
+        this.items.set(`${q},${r}`, { type, q, r });
+    },
+    
+    // Remove an item from the map
+    removeItem(q, r) {
+        return this.items.delete(`${q},${r}`);
+    },
+    
+    // Get item at position
+    getItem(q, r) {
+        return this.items.get(`${q},${r}`);
+    },
+    
+    // Draw all items
+    draw(ctx, hexToPixel, camera, HEX_SIZE) {
+        this.items.forEach(item => {
+            const pixelPos = hexToPixel({ q: item.q, r: item.r });
+            const screenX = pixelPos.x - camera.x;
+            const screenY = pixelPos.y - camera.y;
+            
+            ctx.fillStyle = item.type.color;
+            ctx.beginPath();
+            ctx.arc(screenX, screenY, HEX_SIZE * item.type.size, 0, Math.PI * 2);
+            ctx.fill();
+        });
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/player.js b/html/rogue/js/player.js
index 270b26f..efecbdf 100644
--- a/html/rogue/js/player.js
+++ b/html/rogue/js/player.js
@@ -1,64 +1,241 @@
-const createPlayer = (x, y) => ({
-    x,
-    y,
-    width: 32,
-    height: 32,
-    velocityX: 0,
-    velocityY: 0,
-    speed: 300,
-    jumping: false
-});
+// Player state and controls
+const player = {
+    position: { q: 0, r: 0 },    // Current hex position
+    target: null,                 // Target hex to move to
+    path: [],                     // Array of hex coordinates to follow
+    movementProgress: 0,          // Progress of current movement (0 to 1)
+    moveSpeed: Config.player.MOVE_SPEED,              // Movement speed (0 to 1 per frame)
+    inventory: [],
+    
+    // Animation properties
+    animation: null,
+    sprites: [],
+    
+    // Initialize player
+    async init() {
+        this.position = { q: 0, r: 0 };
+        this.target = null;
+        this.path = [];
+        this.inventory = [];
+        
+        // Load sprites
+        try {
+            const [sprite1, sprite2] = await Promise.all([
+                Animation.loadImage('assets/home/goblin-01.png'),
+                Animation.loadImage('assets/home/goblin-02.png')
+            ]);
+            
+            this.sprites = [sprite1, sprite2];
+            this.animation = Animation.createAnimation(this.sprites, 500); // 500ms per frame
+        } catch (error) {
+            console.error('Failed to load player sprites:', error);
+        }
+        
+        return this;
+    },
 
-const updatePlayer = (player, deltaTime) => {
-    const keys = getKeys();
-    const seconds = deltaTime / 1000;
+    // Check if a hex coordinate is within grid bounds
+    isValidHex(hex) {
+        const halfGrid = Math.floor(HexGrid.GRID_SIZE / 2);
+        return Math.abs(hex.q) <= halfGrid && Math.abs(hex.r) <= halfGrid;
+    },
 
-    let velocityX = 0;
-    let velocityY = player.velocityY;
+    // Get neighbors that share an edge with the given hex
+    getEdgeNeighbors(hex) {
+        const directions = [
+            {q: 1, r: 0},   // East
+            {q: 0, r: 1},   // Southeast
+            {q: -1, r: 1},  // Southwest
+            {q: -1, r: 0},  // West
+            {q: 0, r: -1},  // Northwest
+            {q: 1, r: -1}   // Northeast
+        ];
+        
+        // Only return neighbors that are within grid bounds
+        return directions
+            .map(dir => ({
+                q: hex.q + dir.q,
+                r: hex.r + dir.r
+            }))
+            .filter(hex => this.isValidHex(hex));
+    },
 
-    // Horizontal movement
-    if (keys.ArrowLeft) velocityX -= player.speed;
-    if (keys.ArrowRight) velocityX += player.speed;
+    // Find path from current position to target
+    findPath(targetHex) {
+        const start = this.position;
+        const goal = targetHex;
+        
+        // Simple breadth-first search
+        const queue = [[start]];
+        const visited = new Set();
+        const key = hex => `${hex.q},${hex.r}`;
+        visited.add(key(start));
+        
+        while (queue.length > 0) {
+            const path = queue.shift();
+            const current = path[path.length - 1];
+            
+            if (current.q === goal.q && current.r === goal.r) {
+                return path;
+            }
+            
+            const neighbors = this.getEdgeNeighbors(current);
+            for (const neighbor of neighbors) {
+                const neighborKey = key(neighbor);
+                if (!visited.has(neighborKey)) {
+                    visited.add(neighborKey);
+                    queue.push([...path, neighbor]);
+                }
+            }
+        }
+        
+        return null; // No path found
+    },
 
-    // Simple jumping (can be improved)
-    if (keys.ArrowUp && !player.jumping) {
-        velocityY = -500;
-    }
-
-    // Apply gravity
-    velocityY += 980 * seconds; // 980 pixels/second²
+    // Start moving to a target hex
+    moveTo(targetHex) {
+        // Only start new movement if we're not already moving and target is valid
+        if (!this.target) {
+            // Check if target is within grid bounds
+            if (!this.isValidHex(targetHex)) {
+                return; // Ignore movement request if target is out of bounds
+            }
 
-    // Update position
-    const x = player.x + velocityX * seconds;
-    const y = player.y + velocityY * seconds;
+            const path = this.findPath(targetHex);
+            if (path) {
+                // Filter out any path points that would go out of bounds
+                this.path = path.slice(1).filter(hex => this.isValidHex(hex));
+                if (this.path.length > 0) {
+                    this.target = this.path.shift();
+                    this.movementProgress = 0;
+                }
+            }
+        }
+    },
 
-    // Create updated player state
-    let updatedPlayer = {
-        ...player,
-        x,
-        y,
-        velocityX,
-        velocityY
-    };
+    // Add item to inventory
+    addToInventory(item) {
+        this.inventory.push(item);
+    },
 
-    // Handle collisions with the world
-    return handleWorldCollisions(updatedPlayer, window.gameState.world);
-};
+    // Check for and collect items
+    checkForItems() {
+        const item = Items.getItem(this.position.q, this.position.r);
+        if (item) {
+            Items.removeItem(this.position.q, this.position.r);
+            this.addToInventory(item);
+        }
+    },
 
-const renderPlayer = (ctx, player) => {
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(player.x, player.y, player.width, player.height);
-};
+    // Update player position
+    update() {
+        if (this.target) {
+            this.movementProgress += this.moveSpeed;
+            
+            if (this.movementProgress >= 1) {
+                this.position = this.target;
+                this.target = null;
+                this.movementProgress = 0;
+                this.hasMoved = true;
+                
+                // Check for items when reaching new position
+                this.checkForItems();
+                
+                if (this.path.length > 0) {
+                    this.target = this.path.shift();
+                    this.movementProgress = 0;
+                }
+            }
+        }
+    },
 
-// Key handling
-const keys = {};
+    // Get current interpolated position
+    getCurrentPosition() {
+        if (!this.target) {
+            return this.position;
+        }
 
-window.addEventListener('keydown', (e) => {
-    keys[e.key] = true;
-});
+        // Interpolate between current position and target
+        return {
+            q: this.position.q + (this.target.q - this.position.q) * this.movementProgress,
+            r: this.position.r + (this.target.r - this.position.r) * this.movementProgress
+        };
+    },
 
-window.addEventListener('keyup', (e) => {
-    keys[e.key] = false;
-});
+    // Draw the player
+    draw(ctx, hexToPixel, camera, HEX_SIZE) {
+        const currentPos = this.getCurrentPosition();
+        const pixelPos = hexToPixel(currentPos);
+        const screenX = pixelPos.x - camera.x;
+        const screenY = pixelPos.y - camera.y;
 
-const getKeys = () => ({...keys});
+        if (this.animation && this.sprites.length > 0) {
+            // Get current sprite from animation
+            const currentSprite = this.animation.update(performance.now());
+            
+            // Scale sprite to fit within hex
+            // Use slightly smaller than hex size to ensure it fits visually
+            const hexInnerSize = HEX_SIZE * 0.8; // 80% of hex size
+            const { width, height, scale } = Animation.scaleToFit(
+                currentSprite, 
+                hexInnerSize * 2, // width
+                hexInnerSize * Math.sqrt(3) // height (hex height)
+            );
+            
+            // Calculate position to center the sprite in the hex
+            const spriteX = screenX - width / 2;
+            const spriteY = screenY - height / 2;
+            
+            // Save context state
+            ctx.save();
+            
+            // Optional: add a small bounce effect when moving
+            if (this.target) {
+                const bounce = Math.sin(performance.now() / 100) * 2;
+                ctx.translate(spriteX, spriteY + bounce);
+            } else {
+                ctx.translate(spriteX, spriteY);
+            }
+            
+            // Draw the sprite
+            ctx.drawImage(
+                currentSprite,
+                0, 0,
+                width,
+                height
+            );
+            
+            // Restore context state
+            ctx.restore();
+            
+            // Debug: draw hex bounds if debug is enabled
+            if (Debug.isEnabled) {
+                ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
+                ctx.beginPath();
+                HexGrid.drawHexPath(ctx, screenX, screenY, HEX_SIZE * 0.8);
+                ctx.stroke();
+            }
+        } else {
+            // Fallback to circle if sprites aren't loaded
+            ctx.fillStyle = Config.colors.PLAYER;
+            ctx.beginPath();
+            ctx.arc(screenX, screenY, HEX_SIZE * Config.player.SIZE_RATIO, 0, Math.PI * 2);
+            ctx.fill();
+        }
+        
+        // Draw path if needed
+        if (this.path.length > 0) {
+            ctx.strokeStyle = Config.colors.PLAYER + '4D';
+            ctx.beginPath();
+            let lastPos = this.target || this.position;
+            this.path.forEach(point => {
+                const from = hexToPixel(lastPos);
+                const to = hexToPixel(point);
+                ctx.moveTo(from.x - camera.x, from.y - camera.y);
+                ctx.lineTo(to.x - camera.x, to.y - camera.y);
+                lastPos = point;
+            });
+            ctx.stroke();
+        }
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/renderer.js b/html/rogue/js/renderer.js
index 617b4c7..3e64666 100644
--- a/html/rogue/js/renderer.js
+++ b/html/rogue/js/renderer.js
@@ -1,97 +1,29 @@
-// Rendering constants
-const RENDER_CONSTANTS = {
-    VIEWPORT_BUFFER: 100,
-    SKY_COLORS: {
-        TOP: '#1a1a2e',
-        UPPER_MID: '#2d1b3d',
-        LOWER_MID: '#462639',
-        BOTTOM: '#1f1f2f'
+const Renderer = {
+    drawHex(ctx, hex, x, y, size, fillStyle, strokeStyle) {
+        ctx.beginPath();
+        for (let i = 0; i < 6; i++) {
+            const angle = 2 * Math.PI / 6 * i;
+            const xPos = x + size * Math.cos(angle);
+            const yPos = y + size * Math.sin(angle);
+            if (i === 0) ctx.moveTo(xPos, yPos);
+            else ctx.lineTo(xPos, yPos);
+        }
+        ctx.closePath();
+        
+        if (fillStyle) {
+            ctx.fillStyle = fillStyle;
+            ctx.fill();
+        }
+        if (strokeStyle) {
+            ctx.strokeStyle = strokeStyle;
+            ctx.stroke();
+        }
     },
-    GROUND_COLOR: '#4a4',
-    DEBUG_FONT: '14px monospace',
-    DEBUG_COLOR: '#ffffff'
-};
-
-// Helper functions for rendering
-const createSkyGradient = (ctx, groundY) => {
-    const gradient = ctx.createLinearGradient(0, 0, 0, groundY);
-    gradient.addColorStop(0, RENDER_CONSTANTS.SKY_COLORS.TOP);
-    gradient.addColorStop(0.4, RENDER_CONSTANTS.SKY_COLORS.UPPER_MID);
-    gradient.addColorStop(0.7, RENDER_CONSTANTS.SKY_COLORS.LOWER_MID);
-    gradient.addColorStop(1, RENDER_CONSTANTS.SKY_COLORS.BOTTOM);
-    return gradient;
-};
-
-const getViewBounds = (camera) => ({
-    left: camera.x - RENDER_CONSTANTS.VIEWPORT_BUFFER,
-    right: camera.x + camera.width + RENDER_CONSTANTS.VIEWPORT_BUFFER,
-    top: camera.y - RENDER_CONSTANTS.VIEWPORT_BUFFER,
-    bottom: camera.y + camera.height + RENDER_CONSTANTS.VIEWPORT_BUFFER
-});
-
-const isInView = (x, viewBounds) => 
-    x > viewBounds.left && x < viewBounds.right;
-
-// Layer rendering functions
-const renderBackground = (ctx, state, groundY, viewBounds) => {
-    // Save the current transform
-    ctx.save();
     
-    // Reset transform for viewport-fixed sky and initial black fill
-    ctx.setTransform(1, 0, 0, 1, 0, 0);
-    
-    // Sky (fixed to viewport)
-    ctx.fillStyle = state.cachedGradient;
-    ctx.fillRect(0, 0, ctx.canvas.width, groundY);
-
-    // Initial black fill for the bottom of the viewport
-    ctx.fillStyle = '#000000';
-    ctx.fillRect(0, groundY, ctx.canvas.width, ctx.canvas.height - groundY + 1);
-
-    // Restore transform for world-space rendering
-    ctx.restore();
-    
-    // Additional black fill that follows the camera (extends far to left and right)
-    ctx.fillStyle = '#000000';
-    ctx.fillRect(
-        viewBounds.left - 2000,
-        groundY,
-        viewBounds.right - viewBounds.left + 4000,
-        ctx.canvas.height
-    );
-};
-
-const renderBackgroundObjects = (ctx, state, groundY, viewBounds) => {
-    state.world.backgroundTrees
-        .filter(tree => isInView(tree.x, viewBounds))
-        .forEach(tree => renderTree(ctx, tree, groundY));
-    
-    state.world.backgroundRocks
-        .filter(rock => isInView(rock.x, viewBounds))
-        .forEach(rock => renderRock(ctx, rock, groundY));
-    
-    state.world.backgroundGrass
-        .filter(grass => isInView(grass.x, viewBounds))
-        .forEach(grass => renderGrass(ctx, grass, groundY, state.time));
-};
-
-const renderForegroundObjects = (ctx, state, groundY, viewBounds) => {
-    state.world.foregroundTrees
-        .filter(tree => isInView(tree.x, viewBounds))
-        .forEach(tree => renderTree(ctx, tree, groundY));
-    
-    state.world.foregroundRocks
-        .filter(rock => isInView(rock.x, viewBounds))
-        .forEach(rock => renderRock(ctx, rock, groundY));
-    
-    state.world.foregroundGrass
-        .filter(grass => isInView(grass.x, viewBounds))
-        .forEach(grass => renderGrass(ctx, grass, groundY, state.time));
-};
-
-const renderDebugInfo = (ctx, state) => {
-    ctx.fillStyle = RENDER_CONSTANTS.DEBUG_COLOR;
-    ctx.font = RENDER_CONSTANTS.DEBUG_FONT;
-    const text = `x: ${Math.round(state.debug.mouseX)}, y: ${Math.round(state.debug.mouseY)}`;
-    ctx.fillText(text, 10, ctx.canvas.height - 20);
+    drawCircle(ctx, x, y, radius, fillStyle) {
+        ctx.fillStyle = fillStyle;
+        ctx.beginPath();
+        ctx.arc(x, y, radius, 0, Math.PI * 2);
+        ctx.fill();
+    }
 }; 
\ No newline at end of file
diff --git a/html/rogue/js/rogue.js b/html/rogue/js/rogue.js
deleted file mode 100644
index 64716a4..0000000
--- a/html/rogue/js/rogue.js
+++ /dev/null
@@ -1,144 +0,0 @@
-window.gameState = null;
-
-const initGame = () => {
-    const canvas = document.getElementById('gameCanvas');
-    const ctx = canvas.getContext('2d');
-    
-    // Target frame rate
-    const FPS = 60;
-    const FRAME_TIME = 1000 / FPS;
-    let lastFrameTime = 0;
-
-    // Set canvas to full viewport size
-    const resizeCanvas = () => {
-        canvas.width = window.innerWidth;
-        canvas.height = window.innerHeight;
-        // Clear any cached gradients when resizing
-        if (window.gameState) {
-            window.gameState.cachedGradient = null;
-        }
-    };
-
-    window.addEventListener('resize', resizeCanvas);
-    resizeCanvas();
-
-    // Calculate initial player position at ground level
-    const groundY = canvas.height - CONFIG.world.groundHeight;
-    const initialPlayerY = groundY - CONFIG.player.height;
-
-    // Game state
-    let gameState = {
-        time: 0,
-        player: createPlayer(100, initialPlayerY),
-        camera: createCamera(0, 0),
-        world: createWorld(),
-        debug: {
-            enabled: false,
-            mouseX: 0,
-            mouseY: 0
-        }
-    };
-    
-    // Make gameState globally accessible
-    window.gameState = gameState;
-
-    // Set up input handlers
-    setupInputHandlers(canvas, gameState);
-
-    // Game loop
-    const gameLoop = (timestamp) => {
-        // Check if enough time has passed since last frame
-        if (timestamp - lastFrameTime < FRAME_TIME) {
-            requestAnimationFrame(gameLoop);
-            return;
-        }
-
-        // Calculate delta time (capped at 1 second to prevent huge jumps)
-        const deltaTime = Math.min(timestamp - lastFrameTime, 1000);
-        lastFrameTime = timestamp;
-        gameState.time = timestamp;
-
-        // Clear the entire canvas
-        ctx.clearRect(0, 0, canvas.width, canvas.height);
-
-        // Update
-        gameState = updateGame(gameState, deltaTime);
-
-        // Render
-        render(ctx, gameState);
-
-        // Schedule next frame
-        requestAnimationFrame(gameLoop);
-    };
-
-    // Start the game loop
-    lastFrameTime = performance.now();
-    requestAnimationFrame(gameLoop);
-};
-
-const updateGame = (state, deltaTime) => {
-    const updatedPlayer = updatePlayer(state.player, deltaTime);
-    const updatedCamera = updateCamera(state.camera, updatedPlayer);
-
-    return {
-        ...state,
-        player: updatedPlayer,
-        camera: updatedCamera
-    };
-};
-
-const render = (ctx, state) => {
-    const groundY = ctx.canvas.height - state.world.groundHeight;
-
-    // Cache sky gradient
-    if (!state.cachedGradient) {
-        state.cachedGradient = createSkyGradient(ctx, groundY);
-    }
-
-    const viewBounds = getViewBounds(state.camera);
-
-    // Apply camera transform
-    ctx.save();
-    ctx.translate(-state.camera.x, -state.camera.y);
-
-    // Clear the transformed canvas area
-    ctx.clearRect(
-        viewBounds.left,
-        0,
-        viewBounds.right - viewBounds.left,
-        ctx.canvas.height
-    );
-
-    // Render layers
-    renderBackground(ctx, state, groundY, viewBounds);
-    renderBackgroundObjects(ctx, state, groundY, viewBounds);
-    
-    // Render platforms
-    state.world.platforms.forEach(platform => {
-        ctx.fillStyle = platform.color;
-        ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
-    });
-
-    renderPlayer(ctx, state.player);
-    renderForegroundObjects(ctx, state, groundY, viewBounds);
-
-    // Render ground line
-    ctx.fillStyle = RENDER_CONSTANTS.GROUND_COLOR;
-    ctx.fillRect(
-        state.camera.x - 1000,
-        groundY,
-        ctx.canvas.width + 2000,
-        1
-    );
-
-    // Handle debug rendering
-    if (state.debug.enabled) {
-        ctx.restore();
-        renderDebugInfo(ctx, state);
-    } else {
-        ctx.restore();
-    }
-};
-
-// Initialize the game when the window loads
-window.addEventListener('load', initGame);
diff --git a/html/rogue/js/state.js b/html/rogue/js/state.js
new file mode 100644
index 0000000..f5c713d
--- /dev/null
+++ b/html/rogue/js/state.js
@@ -0,0 +1,17 @@
+const GameState = {
+    canvas: null,
+    ctx: null,
+    lastFrameTime: 0,
+    
+    init() {
+        this.canvas = document.getElementById('gameCanvas');
+        this.ctx = this.canvas.getContext('2d');
+        this.lastFrameTime = 0;
+    },
+    
+    resize() {
+        this.canvas.width = window.innerWidth;
+        this.canvas.height = window.innerHeight;
+        Camera.centerOn(player.position);
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/utils.js b/html/rogue/js/utils.js
new file mode 100644
index 0000000..bd329fb
--- /dev/null
+++ b/html/rogue/js/utils.js
@@ -0,0 +1,25 @@
+const Utils = {
+    hexToKey(hex) {
+        return `${hex.q},${hex.r}`;
+    },
+    
+    keyToHex(key) {
+        const [q, r] = key.split(',').map(Number);
+        return { q, r };
+    },
+    
+    // Screen/canvas coordinate utilities
+    screenToCanvas(x, y, camera) {
+        return {
+            x: x + camera.x,
+            y: y + camera.y
+        };
+    },
+    
+    canvasToScreen(x, y, camera) {
+        return {
+            x: x - camera.x,
+            y: y - camera.y
+        };
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/world.js b/html/rogue/js/world.js
deleted file mode 100644
index 2b2529a..0000000
--- a/html/rogue/js/world.js
+++ /dev/null
@@ -1,1054 +0,0 @@
-// Color palettes and configurations
-const NATURE_COLORS = {
-    GREENS: [
-        '#11aa33', // Darker forest green
-        '#22bb44', // Medium forest green
-        '#33cc55', // Bright forest green
-        '#118844', // Deep sea green
-        '#22aa66', // Sage green
-        '#11bb55', // Deep pine
-        '#229955', // Ocean green
-        '#33bb66', // Fresh spring green
-        '#117744', // Dark emerald
-    ],
-    TRUNK_COLORS: {
-        LIGHT: '#664422',
-        MEDIUM: '#553311',
-        DARK: '#442200'
-    }
-};
-
-const GRASS_CONFIG = {
-    MIN_BLADES: 4,
-    MAX_BLADES: 8,
-    SPREAD_FACTOR: 0.7,
-    RUSTLE_SPEED: 300,
-    WAVE_SPEED: 1000,
-    GLOW_SPEED: 500,
-    BASE_BLADE_WIDTH: 5
-};
-
-const FIR_CONFIG = {
-    ROW_COUNT: 40,
-    FEATHERS_PER_ROW: 24,
-    EDGE_FEATHER_COUNT: 40,
-    FEATHER_WIDTH: 2,
-    TAPER_POWER: 0.8,
-    CENTER_NEEDLES: 5,     // Number of needles in center column
-    NEEDLE_SPACING: 1.5,   // Spacing between center needles
-    UPWARD_TILT: 0.1,     // Amount of upward tilt (in radians)
-    ANGLE_VARIATION: 0.05, // Variation in needle angles
-    LENGTH_MULTIPLIER: 0.8 // Base length multiplier for needles
-};
-
-// Define world object types
-const WORLD_OBJECTS = {
-    PLATFORM: 'platform',
-    FIR_BACKGROUND: 'fir_background',
-    FIR_FOREGROUND: 'fir_foreground',
-    MAPLE_BACKGROUND: 'maple_background',
-    MAPLE_FOREGROUND: 'maple_foreground',
-    ROCK_BACKGROUND: 'rock_background',
-    ROCK_FOREGROUND: 'rock_foreground',
-    GRASS_BACKGROUND: 'grass_background',
-    GRASS_FOREGROUND: 'grass_foreground'
-};
-
-// Separate configurations for different tree types
-const FIR_TYPES = {
-    SMALL: {
-        type: 'fir',
-        width: 100,
-        height: 230,
-        canopyOffset: 45,
-        canopyColor: '#22aa44',
-        trunkColor: '#553311'
-    },
-    LARGE: {
-        type: 'fir',
-        width: 150,
-        height: 320,
-        canopyOffset: 65,
-        canopyColor: '#11aa44',
-        trunkColor: '#442200'
-    }
-};
-
-const MAPLE_TYPES = {
-    SMALL: {
-        type: 'maple',
-        width: 100,
-        height: 330,
-        trunkHeight: 60,
-        canopyRadius: 35,
-        leafClusters: 5,
-        canopyColor: '#33aa22', // Bright green
-        trunkColor: '#664422'
-    },
-    MEDIUM: {
-        type: 'maple',
-        width: 130,
-        height: 360,
-        trunkHeight: 70,
-        canopyRadius: 45,
-        leafClusters: 6,
-        canopyColor: '#228833', // Deep forest green
-        trunkColor: '#553311'
-    },
-    LARGE: {
-        type: 'maple',
-        width: 160,
-        height: 400,
-        trunkHeight: 85,
-        canopyRadius: 55,
-        leafClusters: 7,
-        canopyColor: '#115522', // Dark green
-        trunkColor: '#553311'
-    },
-    BRIGHT: {
-        type: 'maple',
-        width: 140,
-        height: 380,
-        trunkHeight: 75,
-        canopyRadius: 50,
-        leafClusters: 6,
-        canopyColor: '#44bb33', // Vibrant green
-        trunkColor: '#664422'
-    },
-    SAGE: {
-        type: 'maple',
-        width: 150,
-        height: 490,
-        trunkHeight: 80,
-        canopyRadius: 52,
-        leafClusters: 6,
-        canopyColor: '#225544', // Sage green
-        trunkColor: '#553311'
-    }
-};
-
-// Rock configurations
-const ROCK_TYPES = {
-    SMALL: {
-        width: 40,
-        height: 30,
-        color: '#666',
-        highlights: '#888'
-    },
-    MEDIUM: {
-        width: 70,
-        height: 45,
-        color: '#555',
-        highlights: '#777'
-    },
-    LARGE: {
-        width: 100,
-        height: 60,
-        color: '#444',
-        highlights: '#666'
-    }
-};
-
-// Add grass configurations
-const GRASS_TYPES = {
-    TALL: {
-        type: 'grass',
-        width: 30,
-        height: 40,
-        color: '#33aa55',
-        shadowColor: '#229944'
-    },
-    SHORT: {
-        type: 'grass',
-        width: 20,
-        height: 25,
-        color: '#33bb66',
-        shadowColor: '#22aa55'
-    },
-    GOLDEN_TALL: {
-        type: 'grass',
-        width: 30,
-        height: 40,
-        color: '#eebb33',
-        shadowColor: '#cc9922'
-    },
-    GOLDEN_SHORT: {
-        type: 'grass',
-        width: 20,
-        height: 25,
-        color: '#ffcc44',
-        shadowColor: '#ddaa33'
-    },
-    BLUE_TALL: {
-        type: 'grass',
-        width: 30,
-        height: 40,
-        color: '#44aaff',
-        shadowColor: '#2299ff',
-        glowing: true
-    },
-    BLUE_SHORT: {
-        type: 'grass',
-        width: 20,
-        height: 25,
-        color: '#55bbff',
-        shadowColor: '#33aaff',
-        glowing: true
-    }
-};
-
-// Utility functions
-const utils = {
-    getRandomColorFromPalette: (palette, seed1, seed2) => {
-        const colorIndex = Math.floor(seededRandom(seed1, seed2) * palette.length);
-        return palette[colorIndex];
-    },
-
-    getBladeCount: (x, height) => {
-        const randomValue = Math.abs(seededRandom(x, height));
-        return GRASS_CONFIG.MIN_BLADES + 
-            Math.round(randomValue * (GRASS_CONFIG.MAX_BLADES - GRASS_CONFIG.MIN_BLADES));
-    },
-
-    calculateTaper: (t) => Math.pow(1 - t, FIR_CONFIG.TAPER_POWER)
-};
-
-// Create a world with platforms, trees, and rocks
-const createWorld = () => {
-    const world = {
-        groundHeight: 12,
-        // Separate arrays for different layers
-        backgroundTrees: [
-            // Far left trees
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: -1500,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: -1200,
-                config: MAPLE_TYPES.SAGE
-            },
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: -900,
-                config: FIR_TYPES.SMALL
-            },
-            // Existing trees
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: -400,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: -250,
-                config: MAPLE_TYPES.BRIGHT
-            },
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: 50,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: 250,
-                config: MAPLE_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: 500,
-                config: FIR_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: 650,
-                config: MAPLE_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: 900,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: 1100,
-                config: MAPLE_TYPES.LARGE
-            },
-            // Far right trees
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: 1400,
-                config: MAPLE_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.FIR_BACKGROUND,
-                x: 1700,
-                config: FIR_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
-                x: 2000,
-                config: MAPLE_TYPES.LARGE
-            }
-        ],
-        backgroundRocks: [
-            // Far left rocks
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: -1300,
-                config: ROCK_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: -1000,
-                config: ROCK_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: -300,
-                config: ROCK_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: -100,
-                config: ROCK_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 150,
-                config: ROCK_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 400,
-                config: ROCK_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 750,
-                config: ROCK_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 1000,
-                config: ROCK_TYPES.SMALL
-            },
-            // Far right rocks
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 1600,
-                config: ROCK_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_BACKGROUND,
-                x: 1900,
-                config: ROCK_TYPES.SMALL
-            }
-        ],
-        platforms: [
-            // {
-            //     type: WORLD_OBJECTS.PLATFORM,
-            //     x: 300,
-            //     y: 300,
-            //     width: 200,
-            //     height: 20,
-            //     color: '#484'
-            // },
-            // {
-            //     type: WORLD_OBJECTS.PLATFORM,
-            //     x: 600,
-            //     y: 200,
-            //     width: 200,
-            //     height: 20,
-            //     color: '#484'
-            // },
-            // {
-            //     type: WORLD_OBJECTS.PLATFORM,
-            //     x: -200,
-            //     y: 250,
-            //     width: 200,
-            //     height: 20,
-            //     color: '#484'
-            // }
-        ],
-        foregroundTrees: [
-            // Far left trees
-            {
-                type: WORLD_OBJECTS.FIR_FOREGROUND,
-                x: -1400,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
-                x: -1100,
-                config: MAPLE_TYPES.SMALL
-            },
-            // Existing trees
-            {
-                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
-                x: -150,
-                config: MAPLE_TYPES.BRIGHT
-            },
-            {
-                type: WORLD_OBJECTS.FIR_FOREGROUND,
-                x: 200,
-                config: FIR_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
-                x: 450,
-                config: MAPLE_TYPES.SAGE
-            },
-            {
-                type: WORLD_OBJECTS.FIR_FOREGROUND,
-                x: 800,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
-                x: 1200,
-                config: MAPLE_TYPES.SMALL
-            },
-            // Far right trees
-            {
-                type: WORLD_OBJECTS.FIR_FOREGROUND,
-                x: 1500,
-                config: FIR_TYPES.LARGE
-            },
-            {
-                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
-                x: 1800,
-                config: MAPLE_TYPES.SMALL
-            }
-        ],
-        foregroundRocks: [
-            // Far left rocks
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: -1000,
-                config: ROCK_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: 0,
-                config: ROCK_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: 300,
-                config: ROCK_TYPES.MEDIUM
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: 700,
-                config: ROCK_TYPES.SMALL
-            },
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: 950,
-                config: ROCK_TYPES.LARGE
-            },
-            // Far right rocks
-            {
-                type: WORLD_OBJECTS.ROCK_FOREGROUND,
-                x: 1500,
-                config: ROCK_TYPES.LARGE
-            }
-        ],
-        backgroundGrass: [
-            // Far left grass
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -1400, config: GRASS_TYPES.BLUE_TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -1100, config: GRASS_TYPES.GOLDEN_SHORT },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -950, config: GRASS_TYPES.TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -350, config: GRASS_TYPES.SHORT },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -180, config: GRASS_TYPES.GOLDEN_TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 100, config: GRASS_TYPES.TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 320, config: GRASS_TYPES.BLUE_SHORT },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 580, config: GRASS_TYPES.GOLDEN_SHORT },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 750, config: GRASS_TYPES.BLUE_TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 950, config: GRASS_TYPES.TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1050, config: GRASS_TYPES.GOLDEN_TALL },
-            // Far right grass
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1500, config: GRASS_TYPES.BLUE_SHORT },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1750, config: GRASS_TYPES.GOLDEN_TALL },
-            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1850, config: GRASS_TYPES.SHORT }
-        ],
-        foregroundGrass: [
-            // Far left grass
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -1250, config: GRASS_TYPES.TALL },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -1050, config: GRASS_TYPES.BLUE_SHORT },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -280, config: GRASS_TYPES.BLUE_TALL },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -50, config: GRASS_TYPES.GOLDEN_SHORT },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 150, config: GRASS_TYPES.TALL },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 420, config: GRASS_TYPES.BLUE_SHORT },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 680, config: GRASS_TYPES.GOLDEN_TALL },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 880, config: GRASS_TYPES.SHORT },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1150, config: GRASS_TYPES.BLUE_TALL },
-            // Far right grass
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1650, config: GRASS_TYPES.GOLDEN_TALL },
-            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1850, config: GRASS_TYPES.BLUE_SHORT }
-        ],
-        // Track grass animation states
-        grassStates: {},
-        // Add methods for quick spatial lookups
-        getObjectsInView: function(bounds) {
-            return {
-                backgroundTrees: this.backgroundTrees.filter(tree => 
-                    tree.x > bounds.left && tree.x < bounds.right
-                ),
-                // ... similar for other object types
-            };
-        }
-    };
-    
-    return world;
-};
-
-// Add seededRandom function
-const seededRandom = (x, y) => {
-    const dot = x * 12.9898 + y * 78.233;
-    return (Math.sin(dot) * 43758.5453123) % 1;
-};
-
-// Add hatching helper function
-const addHatchingPattern = (ctx, x, y, width, height, color) => {
-    const spacing = 4; // Space between hatch lines
-    const length = 10;  // Length of each hatch line
-    const margin = 4; // Increased margin from edges
-    const baseAngle = Math.PI * 1.5; // Vertical angle (pointing down)
-    const angleVariation = Math.PI / 12; // Reduced angle variation
-    
-    // Define darker brown shades
-    const brownShades = [
-        '#442211', // Dark brown
-        '#553322', // Medium dark brown
-        '#443311', // Reddish dark brown
-        '#332211', // Very dark brown
-        '#554422', // Warm dark brown
-    ];
-    
-    // Create clipping path for trunk
-    ctx.save();
-    ctx.beginPath();
-    ctx.rect(
-        x - width/2,
-        y - height,
-        width,
-        height
-    );
-    ctx.clip();
-    
-    // Calculate bounds with increased safety margin
-    const bounds = {
-        minX: x - width/2 + margin,
-        maxX: x + width/2 - margin,
-        minY: y - height + margin,
-        maxY: y - margin
-    };
-
-    // Draw hatching
-    ctx.lineWidth = 1;
-    for (let hatchX = bounds.minX; hatchX < bounds.maxX; hatchX += spacing) {
-        for (let hatchY = bounds.minY; hatchY < bounds.maxY; hatchY += spacing) {
-            // Use position for random variation
-            const seed1 = hatchX * 13.37;
-            const seed2 = hatchY * 7.89;
-            
-            // Random variations with reduced range
-            const variation = {
-                x: (seededRandom(seed1, seed2) - 0.5) * 1.5, // Reduced variation
-                y: (seededRandom(seed2, seed1) - 0.5) * 1.5, // Reduced variation
-                angle: baseAngle + (seededRandom(seed1 + seed2, seed2 - seed1) - 0.5) * angleVariation
-            };
-
-            // Pick a random brown shade
-            const colorIndex = Math.floor(seededRandom(seed1 * seed2, seed2 * seed1) * brownShades.length);
-            ctx.strokeStyle = brownShades[colorIndex];
-
-            // Draw hatch line
-            ctx.beginPath();
-            ctx.moveTo(
-                hatchX + variation.x,
-                hatchY + variation.y
-            );
-            ctx.lineTo(
-                hatchX + Math.cos(variation.angle) * length + variation.x,
-                hatchY + Math.sin(variation.angle) * length + variation.y
-            );
-            ctx.stroke();
-        }
-    }
-    
-    ctx.restore();
-};
-
-class FirTreeRenderer {
-    static renderTrunk(ctx, x, width, height, groundY, trunkColor) {
-        const trunkWidth = width/4;
-        const trunkHeight = height;
-        
-        ctx.fillStyle = trunkColor;
-        ctx.fillRect(
-            x - trunkWidth/2,
-            groundY - trunkHeight,
-            trunkWidth,
-            trunkHeight
-        );
-        
-        addHatchingPattern(ctx, x, groundY, trunkWidth, trunkHeight, trunkColor);
-    }
-
-    static renderCenterNeedles(ctx, x, rowY, taper, featherLength, greenColors) {
-        for (let i = -FIR_CONFIG.CENTER_NEEDLES; i <= FIR_CONFIG.CENTER_NEEDLES; i++) {
-            const offset = i * (FIR_CONFIG.NEEDLE_SPACING * 1.5);
-            
-            // Left needle
-            FirTreeRenderer.renderSingleNeedle(
-                ctx, x + offset, rowY, Math.PI, taper, featherLength, greenColors
-            );
-            
-            // Right needle
-            FirTreeRenderer.renderSingleNeedle(
-                ctx, x + offset, rowY, 0, taper, featherLength, greenColors
-            );
-        }
-    }
-
-    static renderSingleNeedle(ctx, x, y, baseAngle, taper, length, colors) {
-        const angleVar = Math.PI * 0.02;
-        const angle = baseAngle + (angleVar * seededRandom(x, y) - angleVar/2);
-        
-        ctx.strokeStyle = utils.getRandomColorFromPalette(colors, x, y);
-        ctx.beginPath();
-        ctx.moveTo(x, y);
-        ctx.lineTo(
-            x + Math.cos(angle) * length * taper,
-            y + Math.sin(angle) * length * taper
-        );
-        ctx.stroke();
-    }
-
-    static renderRowNeedles(ctx, x, rowY, rowWidth, featherCount, t, taper, featherLength, greenColors) {
-        for (let i = 0; i < featherCount; i++) {
-            const featherT = i / (featherCount - 1);
-            const startX = x - rowWidth/2 + rowWidth * featherT;
-            
-            if (Math.abs(startX - x) < 1) continue;
-            
-            const relativeX = (startX - x) / (rowWidth/2);
-            const baseAngle = relativeX < 0 ? Math.PI : 0;
-            const upwardTilt = Math.PI * FIR_CONFIG.UPWARD_TILT * t;
-            const angle = baseAngle + 
-                (FIR_CONFIG.ANGLE_VARIATION * seededRandom(startX, rowY) - FIR_CONFIG.ANGLE_VARIATION/2) - 
-                upwardTilt;
-            
-            const lengthMultiplier = FIR_CONFIG.LENGTH_MULTIPLIER + (1 - t) * 0.3;
-            const finalLength = featherLength * lengthMultiplier * taper * 
-                (0.9 + seededRandom(startX * rowY, rowY) * 0.2);
-            
-            ctx.strokeStyle = utils.getRandomColorFromPalette(greenColors, startX, rowY);
-            ctx.beginPath();
-            ctx.moveTo(startX, rowY);
-            ctx.lineTo(
-                startX + Math.cos(angle) * finalLength,
-                rowY + Math.sin(angle) * finalLength
-            );
-            ctx.stroke();
-        }
-    }
-
-    static renderEdgeNeedles(ctx, x, baseWidth, baseY, tipY, featherLength, greenColors) {
-        for (let i = 0; i < FIR_CONFIG.EDGE_FEATHER_COUNT; i++) {
-            const t = i / (FIR_CONFIG.EDGE_FEATHER_COUNT - 1);
-            const taper = utils.calculateTaper(t);
-            
-            if (taper <= 0.1) continue;
-            
-            // Left edge
-            FirTreeRenderer.renderEdgeNeedle(
-                ctx, x, baseWidth, baseY, tipY, t, taper, 
-                featherLength, Math.PI, greenColors, true
-            );
-            
-            // Right edge
-            FirTreeRenderer.renderEdgeNeedle(
-                ctx, x, baseWidth, baseY, tipY, t, taper, 
-                featherLength, 0, greenColors, false
-            );
-        }
-    }
-
-    static renderEdgeNeedle(ctx, x, baseWidth, baseY, tipY, t, taper, length, baseAngle, colors, isLeft) {
-        const sign = isLeft ? -1 : 1;
-        const needleX = x + sign * (baseWidth/2 * taper - (baseWidth/2 * taper) * t);
-        const needleY = baseY - (baseY - tipY) * t;
-        
-        const upwardTilt = Math.PI * 0.1 * t;
-        const angle = baseAngle + 
-            (Math.PI * 0.05 * seededRandom(needleX, needleY) - Math.PI * 0.025) - 
-            upwardTilt;
-        
-        ctx.strokeStyle = utils.getRandomColorFromPalette(colors, needleX, needleY);
-        ctx.beginPath();
-        ctx.moveTo(needleX, needleY);
-        ctx.lineTo(
-            needleX + Math.cos(angle) * length * taper * 1.2,
-            needleY + Math.sin(angle) * length * taper * 1.2
-        );
-        ctx.stroke();
-    }
-}
-
-// Helper function to darken/lighten colors
-const shadeColor = (color, percent) => {
-    const num = parseInt(color.replace('#', ''), 16);
-    const amt = Math.round(2.55 * percent);
-    const R = (num >> 16) + amt;
-    const G = (num >> 8 & 0x00FF) + amt;
-    const B = (num & 0x0000FF) + amt;
-    return '#' + (0x1000000 +
-        (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
-        (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
-        (B < 255 ? (B < 1 ? 0 : B) : 255)
-    ).toString(16).slice(1);
-};
-
-// Add new maple tree render function
-const renderMapleTree = (ctx, tree, groundY) => {
-    const { x, config } = tree;
-    const { 
-        width, height, trunkHeight, canopyRadius,
-        leafClusters, trunkColor, canopyColor 
-    } = config;
-    
-    // Draw trunk base with narrower width (changed from width/4 to width/5)
-    const trunkWidth = width/5.5;
-    ctx.fillStyle = trunkColor;
-    ctx.fillRect(
-        x - trunkWidth/2,
-        groundY - trunkHeight,
-        trunkWidth,
-        trunkHeight
-    );
-    
-    // Add trunk hatching with updated width
-    addHatchingPattern(
-        ctx,
-        x,
-        groundY,
-        trunkWidth,
-        trunkHeight,
-        trunkColor
-    );
-
-    // Function to create an irregular polygon
-    const createIrregularPolygon = (centerX, centerY, radius, sides, seed1, seed2) => {
-        ctx.beginPath();
-        
-        // Create points array first to allow smoothing
-        const points = [];
-        for (let i = 0; i < sides; i++) {
-            const angle = (i / sides) * Math.PI * 2;
-            
-            // Even more subtle variation range (0.95-1.05)
-            const radiusVariation = 0.95 + seededRandom(seed1 * i, seed2 * i) * 0.10;
-            const pointRadius = radius * radiusVariation;
-            
-            points.push({
-                x: centerX + Math.cos(angle) * pointRadius,
-                y: centerY + Math.sin(angle) * pointRadius
-            });
-        }
-        
-        // Start the path
-        ctx.moveTo(points[0].x, points[0].y);
-        
-        // Draw smooth curves through all points
-        for (let i = 0; i < points.length; i++) {
-            const current = points[i];
-            const next = points[(i + 1) % points.length];
-            const nextNext = points[(i + 2) % points.length];
-            
-            // Calculate control points for bezier curve with more smoothing
-            const cp1x = current.x + (next.x - points[(i - 1 + points.length) % points.length].x) / 4;
-            const cp1y = current.y + (next.y - points[(i - 1 + points.length) % points.length].y) / 4;
-            const cp2x = next.x - (nextNext.x - current.x) / 4;
-            const cp2y = next.y - (nextNext.y - current.y) / 4;
-            
-            // Draw bezier curve
-            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, next.x, next.y);
-        }
-        
-        ctx.closePath();
-    };
-
-    // Draw single canopy as irregular polygon
-    const sides = Math.floor(48 + seededRandom(x, groundY) * 16); // 48-64 sides
-    const mainY = groundY - trunkHeight - canopyRadius;
-    
-    ctx.fillStyle = canopyColor;
-    createIrregularPolygon(
-        x,
-        mainY,
-        canopyRadius * 1.4, // Increased size
-        sides,
-        x,
-        groundY
-    );
-    ctx.fill();
-};
-
-// Main tree render function that handles both types
-const renderTree = (ctx, tree, groundY) => {
-    if (tree.config.type === 'fir') {
-        renderFirTree(ctx, tree, groundY);
-    } else if (tree.config.type === 'maple') {
-        renderMapleTree(ctx, tree, groundY);
-    }
-};
-
-const renderRock = (ctx, rock, groundY) => {
-    const { x, config } = rock;
-    const { width, height, color, highlights } = config;
-    
-    // Draw main rock shape (slightly irregular pentagon)
-    ctx.fillStyle = color;
-    ctx.beginPath();
-    ctx.moveTo(x - width/2, groundY);
-    ctx.lineTo(x - width/2 + width/6, groundY - height);
-    ctx.lineTo(x + width/3, groundY - height);
-    ctx.lineTo(x + width/2, groundY - height/2);
-    ctx.lineTo(x + width/2, groundY);
-    ctx.closePath();
-    ctx.fill();
-
-    // Add highlights
-    ctx.fillStyle = highlights;
-    ctx.beginPath();
-    ctx.moveTo(x - width/4, groundY - height);
-    ctx.lineTo(x, groundY - height);
-    ctx.lineTo(x + width/6, groundY - height/2);
-    ctx.lineTo(x - width/6, groundY - height/2);
-    ctx.closePath();
-    ctx.fill();
-};
-
-// Add grass rendering function
-const renderGrass = (ctx, grass, groundY, time) => {
-    const { x, config } = grass;
-    const { width, height, color, shadowColor, glowing } = config;
-    
-    // Initialize or get grass state
-    const stateKey = `grass_${x}`;
-    if (!window.gameState.world.grassStates[stateKey]) {
-        window.gameState.world.grassStates[stateKey] = GrassState.create(x, height);
-    }
-    const state = GrassState.validate(window.gameState.world.grassStates[stateKey]);
-    
-    // Update interaction
-    const player = window.gameState.player;
-    const distanceToPlayer = Math.abs(player.x + player.width / 2 - x);
-    GrassState.update(state, distanceToPlayer, width);
-
-    // Handle rendering
-    setupGlowEffect(ctx, glowing, color, time);
-    renderGrassBlades(ctx, state, x, width, height, groundY, time, color, shadowColor);
-    resetGlowEffect(ctx, glowing);
-};
-
-const setupGlowEffect = (ctx, glowing, color, time) => {
-    if (glowing) {
-        ctx.save();
-        ctx.shadowColor = color;
-        ctx.shadowBlur = 10 + Math.sin(time/GRASS_CONFIG.GLOW_SPEED) * 3;
-    }
-};
-
-const resetGlowEffect = (ctx, glowing) => {
-    if (glowing) {
-        ctx.restore();
-    }
-};
-
-const renderGrassBlades = (ctx, state, x, width, height, groundY, time, color, shadowColor) => {
-    const bladeWidth = width / GRASS_CONFIG.BASE_BLADE_WIDTH * 0.7;
-    
-    for (let i = 0; i < state.bladeCount; i++) {
-        renderSingleBlade(
-            ctx, i, state, x, width, height, groundY, time,
-            bladeWidth, color, shadowColor
-        );
-    }
-};
-
-const renderSingleBlade = (
-    ctx, index, state, x, width, height, groundY, time,
-    bladeWidth, color, shadowColor
-) => {
-    const centerOffset = (index - (state.bladeCount - 1) / 2) * 
-        (width * GRASS_CONFIG.SPREAD_FACTOR / state.bladeCount);
-    const bladeX = x + centerOffset;
-    
-    const rustleOffset = Math.sin(time / GRASS_CONFIG.RUSTLE_SPEED + index) * 5 * state.rustleAmount;
-    const baseWave = Math.sin(time / GRASS_CONFIG.WAVE_SPEED + index) * 2;
-    
-    drawBladeCurve(
-        ctx, bladeX, groundY, height, rustleOffset, baseWave,
-        bladeWidth, index % 2 === 0 ? color : shadowColor
-    );
-};
-
-const drawBladeCurve = (
-    ctx, bladeX, groundY, height, rustleOffset, baseWave,
-    bladeWidth, color
-) => {
-    const cp1x = bladeX + rustleOffset + baseWave;
-    const cp1y = groundY - height / 2;
-    const cp2x = bladeX + rustleOffset * 1.5 + baseWave;
-    const cp2y = groundY - height;
-    
-    ctx.fillStyle = color;
-    ctx.beginPath();
-    ctx.moveTo(bladeX, groundY);
-    ctx.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y);
-    ctx.quadraticCurveTo(cp1x + bladeWidth, cp1y, bladeX + bladeWidth, groundY);
-    ctx.fill();
-};
-
-// Collision detection helper
-const checkCollision = (player, platform) => {
-    return player.x < platform.x + platform.width &&
-           player.x + player.width > platform.x &&
-           player.y < platform.y + platform.height &&
-           player.y + player.height > platform.y;
-};
-
-// World physics helper
-const handleWorldCollisions = (player, world) => {
-    let onGround = false;
-    
-    // Check ground collision first
-    const groundY = window.innerHeight - world.groundHeight;
-    if (player.y + player.height > groundY) {
-        player.y = groundY - player.height;
-        player.velocityY = 0;
-        onGround = true;
-    }
-    
-    // Then check platform collisions
-    for (const platform of world.platforms) {
-        if (checkCollision(player, platform)) {
-            // Calculate overlap on each axis
-            const overlapX = Math.min(
-                player.x + player.width - platform.x,
-                platform.x + platform.width - player.x
-            );
-            const overlapY = Math.min(
-                player.y + player.height - platform.y,
-                platform.y + platform.height - player.y
-            );
-
-            // Resolve collision on the axis with smallest overlap
-            if (overlapX < overlapY) {
-                // Horizontal collision
-                if (player.x < platform.x) {
-                    player.x = platform.x - player.width;
-                } else {
-                    player.x = platform.x + platform.width;
-                }
-                player.velocityX = 0;
-            } else {
-                // Vertical collision
-                if (player.y < platform.y) {
-                    player.y = platform.y - player.height;
-                    player.velocityY = 0;
-                    onGround = true;
-                } else {
-                    player.y = platform.y + platform.height;
-                    player.velocityY = 0;
-                }
-            }
-        }
-    }
-    
-    return { ...player, jumping: !onGround };
-};
-
-class GrassState {
-    static create(x, height) {
-        return {
-            rustleAmount: 0,
-            rustleDecay: 0.95,
-            bladeCount: utils.getBladeCount(x, height)
-        };
-    }
-
-    static validate(state) {
-        if (!state.bladeCount || state.bladeCount < GRASS_CONFIG.MIN_BLADES) {
-            state.bladeCount = GRASS_CONFIG.MIN_BLADES;
-        }
-        return state;
-    }
-
-    static update(state, distanceToPlayer, interactionDistance) {
-        if (distanceToPlayer < interactionDistance) {
-            state.rustleAmount = Math.min(1, state.rustleAmount + 0.3);
-        } else {
-            state.rustleAmount *= state.rustleDecay;
-        }
-    }
-}
-
-const renderFirTree = (ctx, tree, groundY) => {
-    const { x, config } = tree;
-    const { width, height, canopyOffset, trunkColor, canopyColor } = config;
-    
-    // Setup
-    const greenColors = NATURE_COLORS.GREENS.filter(color => color !== canopyColor);
-    ctx.lineWidth = FIR_CONFIG.FEATHER_WIDTH;
-    
-    // Render trunk
-    FirTreeRenderer.renderTrunk(
-        ctx, x, width, height - (height - canopyOffset)/2, 
-        groundY, trunkColor
-    );
-    
-    const drawFeatheredTree = (baseWidth, baseY, tipY) => {
-        const featherLength = baseWidth * 0.3;
-        
-        for (let row = 0; row < FIR_CONFIG.ROW_COUNT; row++) {
-            const t = row / (FIR_CONFIG.ROW_COUNT - 1);
-            const rowY = baseY - (baseY - tipY) * t;
-            
-            const taper = utils.calculateTaper(t);
-            const rowWidth = baseWidth * taper;
-            
-            if (rowWidth < 2) continue;
-            
-            const featherCount = Math.max(2, Math.floor(FIR_CONFIG.FEATHERS_PER_ROW * taper));
-            
-            // Render center column of needles
-            FirTreeRenderer.renderCenterNeedles(ctx, x, rowY, taper, featherLength, greenColors);
-            
-            // Render row needles
-            FirTreeRenderer.renderRowNeedles(
-                ctx, x, rowY, rowWidth, featherCount, t, 
-                taper, featherLength, greenColors
-            );
-        }
-        
-        // Render edge needles
-        FirTreeRenderer.renderEdgeNeedles(
-            ctx, x, baseWidth, baseY, tipY, featherLength, greenColors
-        );
-    };
-
-    // Draw complete tree
-    drawFeatheredTree(
-        width * 1.2,
-        groundY - canopyOffset,
-        groundY - height * 1.1
-    );
-};