about summary refs log tree commit diff stats
path: root/html/space
diff options
context:
space:
mode:
Diffstat (limited to 'html/space')
-rw-r--r--html/space/game.js45
-rw-r--r--html/space/gameState.js169
-rw-r--r--html/space/index.html22
-rw-r--r--html/space/input.js83
-rw-r--r--html/space/physics.js253
-rw-r--r--html/space/renderer.js358
6 files changed, 930 insertions, 0 deletions
diff --git a/html/space/game.js b/html/space/game.js
new file mode 100644
index 0000000..ecd7abc
--- /dev/null
+++ b/html/space/game.js
@@ -0,0 +1,45 @@
+// Main game entry point
+import { initRenderer, render } from './renderer.js';
+import { initInput, updateInput } from './input.js';
+import { initPhysics, updatePhysics } from './physics.js';
+import { initGameState, updateGameState } from './gameState.js';
+
+// Game state
+let lastTime = 0;
+let isRunning = true;
+
+// Initialize all systems
+function init() {
+    console.log('Initializing game...');
+    initRenderer();
+    initInput();
+    initPhysics();
+    initGameState();
+    console.log('Game initialized');
+}
+
+// Main game loop using requestAnimationFrame
+function gameLoop(timestamp) {
+    if (!isRunning) return;
+
+    const deltaTime = timestamp - lastTime;
+    lastTime = timestamp;
+
+    // Update game systems
+    updateInput();
+    updatePhysics(deltaTime);
+    updateGameState(deltaTime);
+    render();
+
+    // Debug output
+    if (Math.random() < 0.01) { // Only log occasionally to avoid spam
+        console.log('Game loop running, deltaTime:', deltaTime);
+    }
+
+    requestAnimationFrame(gameLoop);
+}
+
+// Start the game
+console.log('Starting game...');
+init();
+requestAnimationFrame(gameLoop);
\ No newline at end of file
diff --git a/html/space/gameState.js b/html/space/gameState.js
new file mode 100644
index 0000000..620bc1f
--- /dev/null
+++ b/html/space/gameState.js
@@ -0,0 +1,169 @@
+// Game state module for managing game objects and logic
+import { inputState } from './input.js';
+import { getPlayerState } from './physics.js';
+
+// Game objects
+const planets = [];
+const enemyShips = [];
+const projectiles = [];
+let lastEnemySpawn = 0;
+
+// Space dimensions
+const SPACE_SIZE = 10000;  // Increased from implicit 2000
+const PLANET_DISTANCE = 5000;  // Increased from 1000
+const ENEMY_SPAWN_DISTANCE = 3000;  // Increased from 500
+const ENEMY_SPAWN_INTERVAL = 5000; // 5 seconds
+const MAX_ENEMIES = 5;
+
+// Initialize game state
+export function initGameState() {
+    // Create planets
+    planets.push({
+        position: { x: PLANET_DISTANCE, y: 0, z: 0 },
+        radius: 500,  // Increased from 200
+        color: '#3498db'
+    });
+    
+    planets.push({
+        position: { x: -PLANET_DISTANCE, y: PLANET_DISTANCE/2, z: -PLANET_DISTANCE },
+        radius: 400,  // Increased from 150
+        color: '#e74c3c'
+    });
+
+    // Reset other state
+    enemyShips.length = 0;
+    projectiles.length = 0;
+    lastEnemySpawn = Date.now();
+
+    // Create initial enemy ships
+    for (let i = 0; i < 5; i++) {
+        createEnemyShip();
+    }
+}
+
+// Create a new enemy ship
+function createEnemyShip() {
+    const distance = ENEMY_SPAWN_DISTANCE + Math.random() * ENEMY_SPAWN_DISTANCE;
+    const angle = Math.random() * Math.PI * 2;
+    const height = (Math.random() - 0.5) * ENEMY_SPAWN_DISTANCE;
+
+    enemyShips.push({
+        position: {
+            x: Math.cos(angle) * distance,
+            y: height,
+            z: Math.sin(angle) * distance
+        },
+        velocity: {
+            x: (Math.random() - 0.5) * 0.5,  // Reduced from 2
+            y: (Math.random() - 0.5) * 0.5,  // Reduced from 2
+            z: (Math.random() - 0.5) * 0.5   // Reduced from 2
+        },
+        health: 100
+    });
+}
+
+// Update game state
+export function updateGameState(deltaTime) {
+    const currentTime = Date.now();
+    const player = getPlayerState();
+    
+    // Spawn enemies
+    if (currentTime - lastEnemySpawn > ENEMY_SPAWN_INTERVAL && 
+        enemyShips.length < MAX_ENEMIES) {
+        spawnEnemy();
+        lastEnemySpawn = currentTime;
+    }
+    
+    // Update projectiles
+    projectiles.forEach((projectile, index) => {
+        projectile.position.x += projectile.velocity.x * deltaTime;
+        projectile.position.y += projectile.velocity.y * deltaTime;
+        projectile.position.z += projectile.velocity.z * deltaTime;
+
+        // Remove if too old
+        if (currentTime - projectile.createdAt > 5000) {
+            projectiles.splice(index, 1);
+        }
+    });
+    
+    // Update enemy ships
+    enemyShips.forEach((ship, index) => {
+        // Move ships
+        ship.position.x += ship.velocity.x * deltaTime;
+        ship.position.y += ship.velocity.y * deltaTime;
+        ship.position.z += ship.velocity.z * deltaTime;
+
+        // Check if ship is too far away
+        const distance = Math.sqrt(
+            Math.pow(ship.position.x - player.position.x, 2) +
+            Math.pow(ship.position.y - player.position.y, 2) +
+            Math.pow(ship.position.z - player.position.z, 2)
+        );
+
+        if (distance > SPACE_SIZE) {
+            enemyShips.splice(index, 1);
+            createEnemyShip();
+        }
+    });
+
+    // Handle firing
+    if (inputState.firePrimary) {
+        createProjectile('primary');
+    }
+    if (inputState.fireSecondary) {
+        createProjectile('secondary');
+    }
+}
+
+// Create a new projectile
+export function createProjectile(type) {
+    const player = getPlayerState();
+    const speed = type === 'primary' ? 10 : 7.5;  // Reduced from 20/15
+    const damage = type === 'primary' ? 25 : 10;
+
+    const cosY = Math.cos(player.rotation.y);
+    const sinY = Math.sin(player.rotation.y);
+    const cosX = Math.cos(player.rotation.x);
+    const sinX = Math.sin(player.rotation.x);
+
+    projectiles.push({
+        type,
+        position: { ...player.position },
+        velocity: {
+            x: sinY * cosX * speed,
+            y: sinX * speed,
+            z: cosY * cosX * speed
+        },
+        damage,
+        createdAt: Date.now()
+    });
+}
+
+// Spawn a new enemy ship
+function spawnEnemy() {
+    const angle = Math.random() * Math.PI * 2;
+    const distance = ENEMY_SPAWN_DISTANCE;
+    
+    enemyShips.push({
+        position: {
+            x: Math.cos(angle) * distance,
+            y: 0,
+            z: Math.sin(angle) * distance
+        },
+        velocity: {
+            x: (Math.random() - 0.5) * 0.5,
+            y: (Math.random() - 0.5) * 0.5,
+            z: (Math.random() - 0.5) * 0.5
+        },
+        health: 100
+    });
+}
+
+// Get game state for rendering
+export function getGameState() {
+    return {
+        planets: [...planets],
+        enemyShips: [...enemyShips],
+        projectiles: [...projectiles]
+    };
+} 
\ No newline at end of file
diff --git a/html/space/index.html b/html/space/index.html
new file mode 100644
index 0000000..9db977d
--- /dev/null
+++ b/html/space/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Space</title>
+    <style>
+        body {
+            margin: 0;
+            overflow: hidden;
+            background: black;
+        }
+        canvas {
+            display: block;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="gameCanvas"></canvas>
+    <script type="module" src="game.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/html/space/input.js b/html/space/input.js
new file mode 100644
index 0000000..19ea56c
--- /dev/null
+++ b/html/space/input.js
@@ -0,0 +1,83 @@
+// Input handling module
+import { updatePlayerControls } from './physics.js';
+
+let keys = {};
+let mouseX = 0;
+let mouseY = 0;
+
+// Input state that other modules can read
+export const inputState = {
+    thrust: 0,     // forward/backward thrust (W/S)
+    strafe: 0,     // left/right strafe (A/D)
+    yaw: 0,        // left/right rotation (arrow keys)
+    pitch: 0,      // up/down rotation (arrow keys)
+    firePrimary: false,
+    fireSecondary: false
+};
+
+// Initialize input handlers
+export function initInput() {
+    // Keyboard event listeners
+    document.addEventListener('keydown', (e) => {
+        keys[e.key.toLowerCase()] = true;
+    });
+
+    document.addEventListener('keyup', (e) => {
+        keys[e.key.toLowerCase()] = false;
+    });
+
+    // Mouse movement for heading
+    document.addEventListener('mousemove', (e) => {
+        // Calculate mouse position relative to center of canvas
+        const canvas = document.querySelector('canvas');
+        const rect = canvas.getBoundingClientRect();
+        const centerX = rect.left + rect.width / 2;
+        const centerY = rect.top + rect.height / 2;
+        
+        mouseX = (e.clientX - centerX) / (rect.width / 2);
+        mouseY = (e.clientY - centerY) / (rect.height / 2);
+    });
+
+    // Mouse click for primary weapon
+    document.addEventListener('mousedown', (e) => {
+        if (e.button === 0) { // Left click
+            keys['fire'] = true;
+        }
+    });
+
+    document.addEventListener('mouseup', (e) => {
+        if (e.button === 0) { // Left click
+            keys['fire'] = false;
+        }
+    });
+
+    // E key for secondary weapon
+    document.addEventListener('keydown', (e) => {
+        if (e.key.toLowerCase() === 'e') {
+            keys['secondary'] = true;
+        }
+    });
+
+    document.addEventListener('keyup', (e) => {
+        if (e.key.toLowerCase() === 'e') {
+            keys['secondary'] = false;
+        }
+    });
+}
+
+// Update controls based on current input state
+export function updateInput() {
+    const controls = {
+        thrust: keys[' '] || false, // Space bar for thrust
+        up: keys['w'] || false,     // W for upward strafe
+        down: keys['s'] || false,   // S for downward strafe
+        left: keys['a'] || false,   // A for left strafe
+        right: keys['d'] || false,  // D for right strafe
+        fire: keys['fire'] || false,
+        secondary: keys['secondary'] || false,
+        mouseX,
+        mouseY
+    };
+
+    updatePlayerControls(controls);
+} 
\ No newline at end of file
diff --git a/html/space/physics.js b/html/space/physics.js
new file mode 100644
index 0000000..d4dfe55
--- /dev/null
+++ b/html/space/physics.js
@@ -0,0 +1,253 @@
+// Physics module for handling movement and collisions
+import { inputState } from './input.js';
+import { createProjectile } from './gameState.js';
+
+// Constants
+const MAX_THRUST = 0.5;        // Reduced from 2
+const THRUST_ACCELERATION = 0.01;  // Reduced from 0.05
+const DECELERATION = 0.001;    // Reduced from 0.01
+const BASE_ROTATION_SPEED = 0.001;  // Reduced from 0.005
+const ROTATION_ACCELERATION = 0.0005; // Reduced from 0.002
+const ROTATION_DECELERATION = 0.0002; // Reduced from 0.001
+const MOUSE_SENSITIVITY = 0.03; // Increased from 0.01 for sharper turns
+const MAX_SPEED = 1.0; // Maximum speed in any direction
+
+// Weapon constants
+export const PRIMARY_COOLDOWN = 100; // ms between primary shots
+export const SECONDARY_COOLDOWN = 2000; // ms between secondary shots
+export const PRIMARY_BURST_COUNT = 3; // Number of shots in primary burst
+export const PRIMARY_BURST_DELAY = 50; // ms between burst shots
+
+// Player state
+let playerState = {
+    position: { x: 0, y: 0, z: 0 },
+    velocity: { x: 0, y: 0, z: 0 },
+    rotation: { x: 0, y: 0 },
+    thrust: 0,
+    strafe: 0,
+    weapons: {
+        primary: {
+            lastFired: 0,
+            burstCount: 0,
+            burstTimer: 0
+        },
+        secondary: {
+            lastFired: 0
+        }
+    }
+};
+
+// Initialize physics
+export function initPhysics() {
+    // Reset player state
+    playerState.position = { x: 0, y: 0, z: 0 };
+    playerState.velocity = { x: 0, y: 0, z: 0 };
+    playerState.rotation = { x: 0, y: 0 };
+    playerState.thrust = 0;
+    playerState.strafe = 0;
+}
+
+// Helper function to limit speed in a direction
+function limitSpeed(velocity, maxSpeed) {
+    const speed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
+    if (speed > maxSpeed) {
+        const scale = maxSpeed / speed;
+        velocity.x *= scale;
+        velocity.y *= scale;
+        velocity.z *= scale;
+    }
+}
+
+// Update player controls
+export function updatePlayerControls(controls) {
+    // Handle thrust (space bar)
+    if (controls.thrust) {
+        playerState.thrust = Math.min(playerState.thrust + THRUST_ACCELERATION, MAX_THRUST);
+    } else {
+        // Apply deceleration when no thrust input
+        if (playerState.thrust > 0) {
+            playerState.thrust = Math.max(playerState.thrust - DECELERATION, 0);
+        }
+    }
+
+    // Handle vertical strafing (W/S)
+    if (controls.up) {
+        playerState.verticalStrafe = Math.min(playerState.verticalStrafe + THRUST_ACCELERATION, MAX_THRUST);
+    } else if (controls.down) {
+        playerState.verticalStrafe = Math.max(playerState.verticalStrafe - THRUST_ACCELERATION, -MAX_THRUST);
+    } else {
+        // Apply deceleration when no vertical strafe input
+        if (playerState.verticalStrafe > 0) {
+            playerState.verticalStrafe = Math.max(playerState.verticalStrafe - DECELERATION, 0);
+        } else if (playerState.verticalStrafe < 0) {
+            playerState.verticalStrafe = Math.min(playerState.verticalStrafe + DECELERATION, 0);
+        }
+    }
+
+    // Handle horizontal strafing (A/D)
+    if (controls.left) {
+        playerState.horizontalStrafe = Math.min(playerState.horizontalStrafe + THRUST_ACCELERATION, MAX_THRUST);
+    } else if (controls.right) {
+        playerState.horizontalStrafe = Math.max(playerState.horizontalStrafe - THRUST_ACCELERATION, -MAX_THRUST);
+    } else {
+        // Apply deceleration when no horizontal strafe input
+        if (playerState.horizontalStrafe > 0) {
+            playerState.horizontalStrafe = Math.max(playerState.horizontalStrafe - DECELERATION, 0);
+        } else if (playerState.horizontalStrafe < 0) {
+            playerState.horizontalStrafe = Math.min(playerState.horizontalStrafe + DECELERATION, 0);
+        }
+    }
+
+    // Handle mouse-based rotation with smoothing
+    const targetRotationY = controls.mouseX * MOUSE_SENSITIVITY;
+    const targetRotationX = controls.mouseY * MOUSE_SENSITIVITY;
+    
+    // Smooth rotation using lerp with faster response
+    playerState.rotation.y += (targetRotationY - playerState.rotation.y) * 0.2;
+    playerState.rotation.x += (targetRotationX - playerState.rotation.x) * 0.2;
+
+    // Clamp pitch rotation
+    playerState.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, playerState.rotation.x));
+
+    // Handle weapons with cooldowns
+    const currentTime = Date.now();
+    
+    // Primary weapon (burst fire)
+    if (controls.fire) {
+        const primary = playerState.weapons.primary;
+        if (currentTime - primary.lastFired >= PRIMARY_COOLDOWN && primary.burstCount === 0) {
+            primary.burstCount = PRIMARY_BURST_COUNT;
+            primary.burstTimer = currentTime;
+            firePrimaryWeapon();
+            primary.lastFired = currentTime;
+        }
+    }
+
+    // Secondary weapon (single shot with cooldown)
+    if (controls.secondary && currentTime - playerState.weapons.secondary.lastFired >= SECONDARY_COOLDOWN) {
+        fireSecondaryWeapon();
+        playerState.weapons.secondary.lastFired = currentTime;
+    }
+
+    // Handle burst fire timing
+    const primary = playerState.weapons.primary;
+    if (primary.burstCount > 0 && currentTime - primary.burstTimer >= PRIMARY_BURST_DELAY) {
+        firePrimaryWeapon();
+        primary.burstCount--;
+        primary.burstTimer = currentTime;
+    }
+}
+
+// Update physics
+export function updatePhysics(deltaTime) {
+    // Calculate forward and right vectors based on rotation
+    const forward = {
+        x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x),
+        y: -Math.sin(playerState.rotation.x),
+        z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x)
+    };
+
+    const right = {
+        x: Math.cos(playerState.rotation.y),
+        y: 0,
+        z: -Math.sin(playerState.rotation.y)
+    };
+
+    const up = { x: 0, y: 1, z: 0 };
+
+    // Apply thrust in forward direction
+    const thrustVelocity = {
+        x: forward.x * playerState.thrust * deltaTime,
+        y: forward.y * playerState.thrust * deltaTime,
+        z: forward.z * playerState.thrust * deltaTime
+    };
+
+    // Apply horizontal strafe
+    const horizontalStrafeVelocity = {
+        x: right.x * playerState.horizontalStrafe * deltaTime,
+        y: 0,
+        z: right.z * playerState.horizontalStrafe * deltaTime
+    };
+
+    // Apply vertical strafe
+    const verticalStrafeVelocity = {
+        x: 0,
+        y: up.y * playerState.verticalStrafe * deltaTime,
+        z: 0
+    };
+
+    // Add velocities
+    playerState.velocity.x += thrustVelocity.x + horizontalStrafeVelocity.x + verticalStrafeVelocity.x;
+    playerState.velocity.y += thrustVelocity.y + horizontalStrafeVelocity.y + verticalStrafeVelocity.y;
+    playerState.velocity.z += thrustVelocity.z + horizontalStrafeVelocity.z + verticalStrafeVelocity.z;
+
+    // Limit total speed
+    limitSpeed(playerState.velocity, MAX_SPEED);
+
+    // Apply velocity to position
+    playerState.position.x += playerState.velocity.x * deltaTime;
+    playerState.position.y += playerState.velocity.y * deltaTime;
+    playerState.position.z += playerState.velocity.z * deltaTime;
+
+    // Apply friction/drag
+    const drag = 0.99;
+    playerState.velocity.x *= drag;
+    playerState.velocity.y *= drag;
+    playerState.velocity.z *= drag;
+}
+
+// Weapon firing
+function firePrimaryWeapon() {
+    const forward = {
+        x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x),
+        y: -Math.sin(playerState.rotation.x),
+        z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x)
+    };
+
+    createProjectile({
+        position: { ...playerState.position },
+        velocity: {
+            x: forward.x * 10 + playerState.velocity.x,
+            y: forward.y * 10 + playerState.velocity.y,
+            z: forward.z * 10 + playerState.velocity.z
+        },
+        type: 'primary'
+    });
+}
+
+function fireSecondaryWeapon() {
+    const forward = {
+        x: Math.sin(playerState.rotation.y) * Math.cos(playerState.rotation.x),
+        y: -Math.sin(playerState.rotation.x),
+        z: Math.cos(playerState.rotation.y) * Math.cos(playerState.rotation.x)
+    };
+
+    createProjectile({
+        position: { ...playerState.position },
+        velocity: {
+            x: forward.x * 5 + playerState.velocity.x,
+            y: forward.y * 5 + playerState.velocity.y,
+            z: forward.z * 5 + playerState.velocity.z
+        },
+        type: 'secondary'
+    });
+}
+
+// Get current player state
+export function getPlayerState() {
+    return playerState;
+}
+
+// Get weapon cooldown states
+export function getWeaponStates() {
+    const currentTime = Date.now();
+    return {
+        primary: {
+            cooldown: Math.max(0, PRIMARY_COOLDOWN - (currentTime - playerState.weapons.primary.lastFired)),
+            burstCount: playerState.weapons.primary.burstCount
+        },
+        secondary: {
+            cooldown: Math.max(0, SECONDARY_COOLDOWN - (currentTime - playerState.weapons.secondary.lastFired))
+        }
+    };
+} 
\ No newline at end of file
diff --git a/html/space/renderer.js b/html/space/renderer.js
new file mode 100644
index 0000000..04646cf
--- /dev/null
+++ b/html/space/renderer.js
@@ -0,0 +1,358 @@
+// Renderer module using HTML5 Canvas
+import { getPlayerState, getWeaponStates } from './physics.js';
+import { getGameState } from './gameState.js';
+
+// Import weapon constants
+import {
+    PRIMARY_COOLDOWN,
+    SECONDARY_COOLDOWN,
+    PRIMARY_BURST_COUNT,
+    PRIMARY_BURST_DELAY
+} from './physics.js';
+
+let canvas;
+let ctx;
+let width;
+let height;
+
+// Star field
+let starfield = [];  // Declare starfield array
+const NUM_STARS = 2000;  // Increased from 1000
+const STAR_FIELD_DEPTH = 20000;  // Increased from 2000
+
+// HUD constants
+const HUD_COLOR = '#00ff00';
+const HUD_ALPHA = 0.7;
+const RADAR_RADIUS = 100;
+const RADAR_CENTER_X = 100;
+const RADAR_CENTER_Y = 100;
+const RADAR_SCALE = 0.1; // Scale factor for radar display
+const TARGET_LOCK_COLOR = '#ff0000';
+const TARGET_LOCK_THRESHOLD = 20; // Pixels from center to consider locked
+
+// Initialize the renderer
+export function initRenderer() {
+    console.log('Initializing renderer...');
+    canvas = document.getElementById('gameCanvas');
+    ctx = canvas.getContext('2d');
+    
+    // Set canvas size
+    width = canvas.width = window.innerWidth;
+    height = canvas.height = window.innerHeight;
+    
+    // Initialize starfield
+    console.log('Creating starfield with', NUM_STARS, 'stars...');
+    starfield = Array.from({ length: NUM_STARS }, () => ({
+        x: (Math.random() - 0.5) * STAR_FIELD_DEPTH,
+        y: (Math.random() - 0.5) * STAR_FIELD_DEPTH,
+        z: Math.random() * STAR_FIELD_DEPTH,
+        size: Math.random() * 2 + 1
+    }));
+    console.log('Starfield initialized');
+}
+
+// Project 3D point to 2D screen coordinates
+function projectPoint(x, y, z) {
+    if (z <= 0) return null; // Behind camera
+    
+    const scale = 2000 / z; // Increased scale factor
+    return {
+        x: width/2 + x * scale,
+        y: height/2 + y * scale,
+        scale
+    };
+}
+
+// Check if any enemy ship is in targeting range
+function getTargetLock(player, gameState) {
+    const centerX = width / 2;
+    const centerY = height / 2;
+    
+    for (const ship of gameState.enemyShips) {
+        const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z);
+        if (projected) {
+            const distance = Math.sqrt(
+                Math.pow(projected.x - centerX, 2) +
+                Math.pow(projected.y - centerY, 2)
+            );
+            
+            if (distance < TARGET_LOCK_THRESHOLD) {
+                return {
+                    ship,
+                    distance
+                };
+            }
+        }
+    }
+    return null;
+}
+
+// Draw radar/minimap
+function drawRadar(player, gameState, targetLock) {
+    // Save context
+    ctx.save();
+    
+    // Set radar style
+    ctx.strokeStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR;
+    ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
+    ctx.lineWidth = 1;
+    
+    // Draw radar background
+    ctx.beginPath();
+    ctx.arc(RADAR_CENTER_X, RADAR_CENTER_Y, RADAR_RADIUS, 0, Math.PI * 2);
+    ctx.fill();
+    ctx.stroke();
+    
+    // Draw radar grid
+    ctx.beginPath();
+    ctx.moveTo(RADAR_CENTER_X - RADAR_RADIUS, RADAR_CENTER_Y);
+    ctx.lineTo(RADAR_CENTER_X + RADAR_RADIUS, RADAR_CENTER_Y);
+    ctx.moveTo(RADAR_CENTER_X, RADAR_CENTER_Y - RADAR_RADIUS);
+    ctx.lineTo(RADAR_CENTER_X, RADAR_CENTER_Y + RADAR_RADIUS);
+    ctx.stroke();
+    
+    // Draw objects on radar
+    ctx.fillStyle = HUD_COLOR;
+    
+    // Draw planets
+    gameState.planets.forEach(planet => {
+        const dx = (planet.x - player.x) * RADAR_SCALE;
+        const dy = (planet.z - player.z) * RADAR_SCALE;
+        const distance = Math.sqrt(dx * dx + dy * dy);
+        
+        if (distance < RADAR_RADIUS) {
+            ctx.beginPath();
+            ctx.arc(
+                RADAR_CENTER_X + dx,
+                RADAR_CENTER_Y + dy,
+                5,
+                0,
+                Math.PI * 2
+            );
+            ctx.fill();
+        }
+    });
+    
+    // Draw enemy ships
+    gameState.enemyShips.forEach(ship => {
+        const dx = (ship.x - player.x) * RADAR_SCALE;
+        const dy = (ship.z - player.z) * RADAR_SCALE;
+        const distance = Math.sqrt(dx * dx + dy * dy);
+        
+        if (distance < RADAR_RADIUS) {
+            ctx.beginPath();
+            ctx.arc(
+                RADAR_CENTER_X + dx,
+                RADAR_CENTER_Y + dy,
+                4,
+                0,
+                Math.PI * 2
+            );
+            ctx.fill();
+        }
+    });
+    
+    // Restore context
+    ctx.restore();
+}
+
+// Draw speed and direction indicators
+function drawSpeedIndicator(player, targetLock) {
+    ctx.save();
+    ctx.fillStyle = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR;
+    ctx.font = '14px monospace';
+    
+    // Calculate speed from velocity components
+    const speed = Math.sqrt(
+        player.vx * player.vx +
+        player.vy * player.vy +
+        player.vz * player.vz
+    );
+    
+    // Draw speed
+    ctx.fillText(`Speed: ${speed.toFixed(2)}`, 20, height - 40);
+    
+    // Draw direction (using x and z components for heading)
+    const direction = Math.atan2(player.vx, player.vz);
+    ctx.fillText(`Heading: ${(direction * 180 / Math.PI).toFixed(1)}°`, 20, height - 20);
+    
+    ctx.restore();
+}
+
+// Draw enhanced targeting reticle
+function drawTargetingReticle(player, gameState) {
+    ctx.save();
+    
+    // Check for target lock
+    const targetLock = getTargetLock(player, gameState);
+    const currentColor = targetLock ? TARGET_LOCK_COLOR : HUD_COLOR;
+    
+    ctx.strokeStyle = currentColor;
+    ctx.lineWidth = 1;
+    
+    // Outer circle
+    ctx.beginPath();
+    ctx.arc(width/2, height/2, 20, 0, Math.PI * 2);
+    ctx.stroke();
+    
+    // Inner crosshair
+    ctx.beginPath();
+    ctx.moveTo(width/2 - 10, height/2);
+    ctx.lineTo(width/2 + 10, height/2);
+    ctx.moveTo(width/2, height/2 - 10);
+    ctx.lineTo(width/2, height/2 + 10);
+    ctx.stroke();
+    
+    // Target brackets
+    ctx.beginPath();
+    ctx.moveTo(width/2 - 30, height/2 - 30);
+    ctx.lineTo(width/2 - 20, height/2 - 30);
+    ctx.lineTo(width/2 - 20, height/2 - 20);
+    ctx.moveTo(width/2 + 30, height/2 - 30);
+    ctx.lineTo(width/2 + 20, height/2 - 30);
+    ctx.lineTo(width/2 + 20, height/2 - 20);
+    ctx.moveTo(width/2 - 30, height/2 + 30);
+    ctx.lineTo(width/2 - 20, height/2 + 30);
+    ctx.lineTo(width/2 - 20, height/2 + 20);
+    ctx.moveTo(width/2 + 30, height/2 + 30);
+    ctx.lineTo(width/2 + 20, height/2 + 30);
+    ctx.lineTo(width/2 + 20, height/2 + 20);
+    ctx.stroke();
+    
+    // Draw target lock indicator if locked
+    if (targetLock) {
+        // Draw pulsing circle around target
+        const pulseSize = 30 + Math.sin(Date.now() * 0.01) * 5;
+        ctx.beginPath();
+        ctx.arc(width/2, height/2, pulseSize, 0, Math.PI * 2);
+        ctx.stroke();
+        
+        // Draw target distance
+        ctx.fillStyle = currentColor;
+        ctx.font = '14px monospace';
+        ctx.fillText(`Target Lock: ${targetLock.distance.toFixed(1)}`, width/2 - 50, height/2 + 50);
+    }
+    
+    ctx.restore();
+}
+
+// Draw weapon cooldown indicators
+function drawWeaponCooldowns() {
+    const weaponStates = getWeaponStates();
+    ctx.save();
+    ctx.fillStyle = HUD_COLOR;
+    ctx.font = '14px monospace';
+
+    // Primary weapon cooldown (bottom left)
+    const primaryCooldown = weaponStates.primary.cooldown / PRIMARY_COOLDOWN;
+    ctx.fillText('Primary:', 20, height - 80);
+    ctx.fillStyle = `rgba(0, 255, 0, ${primaryCooldown})`;
+    ctx.fillRect(20, height - 70, 100, 10);
+    ctx.strokeStyle = HUD_COLOR;
+    ctx.strokeRect(20, height - 70, 100, 10);
+
+    // Secondary weapon cooldown (bottom left, below primary)
+    const secondaryCooldown = weaponStates.secondary.cooldown / SECONDARY_COOLDOWN;
+    ctx.fillStyle = HUD_COLOR;
+    ctx.fillText('Secondary:', 20, height - 50);
+    ctx.fillStyle = `rgba(0, 255, 0, ${secondaryCooldown})`;
+    ctx.fillRect(20, height - 40, 100, 10);
+    ctx.strokeStyle = HUD_COLOR;
+    ctx.strokeRect(20, height - 40, 100, 10);
+
+    // Draw burst indicator for primary weapon
+    if (weaponStates.primary.burstCount > 0) {
+        ctx.fillStyle = HUD_COLOR;
+        ctx.fillText(`Burst: ${weaponStates.primary.burstCount}`, 20, height - 20);
+    }
+
+    ctx.restore();
+}
+
+// Main render function
+export function render() {
+    const player = getPlayerState();
+    const gameState = getGameState();
+    const targetLock = getTargetLock(player, gameState);
+
+    // Clear canvas
+    ctx.fillStyle = '#000000';
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    
+    // Draw starfield
+    let starsRendered = 0;
+    starfield.forEach(star => {
+        // Calculate star position relative to player
+        let relativeX = star.x - player.x;
+        let relativeY = star.y - player.y;
+        let relativeZ = star.z - player.z;
+        
+        // Apply player rotation
+        let rotatedX = relativeX * Math.cos(player.rotation) - relativeY * Math.sin(player.rotation);
+        let rotatedY = relativeX * Math.sin(player.rotation) + relativeY * Math.cos(player.rotation);
+        let rotatedZ = relativeZ;
+        
+        // Project to screen coordinates
+        if (rotatedZ > 0) {
+            const projected = projectPoint(rotatedX, rotatedY, rotatedZ);
+            if (projected) {
+                const brightness = Math.min(1, 2000 / rotatedZ);
+                ctx.fillStyle = `rgba(255, 255, 255, ${brightness})`;
+                ctx.beginPath();
+                ctx.arc(projected.x, projected.y, star.size * brightness, 0, Math.PI * 2);
+                ctx.fill();
+                starsRendered++;
+            }
+        }
+    });
+    
+    // Debug output
+    if (Math.random() < 0.01) { // Only log occasionally to avoid spam
+        console.log('Stars rendered:', starsRendered);
+    }
+
+    // Draw planets
+    gameState.planets.forEach(planet => {
+        const projected = projectPoint(planet.x - player.x, planet.y - player.y, planet.z - player.z);
+        if (projected) {
+            const radius = planet.radius * projected.scale;
+            ctx.fillStyle = planet.color;
+            ctx.beginPath();
+            ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
+            ctx.fill();
+        }
+    });
+
+    // Draw enemy ships
+    gameState.enemyShips.forEach(ship => {
+        const projected = projectPoint(ship.x - player.x, ship.y - player.y, ship.z - player.z);
+        if (projected) {
+            const size = 20 * projected.scale;
+            ctx.fillStyle = '#ff0000';
+            ctx.beginPath();
+            ctx.moveTo(projected.x, projected.y - size);
+            ctx.lineTo(projected.x + size, projected.y + size);
+            ctx.lineTo(projected.x - size, projected.y + size);
+            ctx.closePath();
+            ctx.fill();
+        }
+    });
+
+    // Draw projectiles
+    gameState.projectiles.forEach(projectile => {
+        const projected = projectPoint(projectile.x - player.x, projectile.y - player.y, projectile.z - player.z);
+        if (projected) {
+            const size = 3 * projected.scale;
+            ctx.fillStyle = projectile.type === 'primary' ? '#ffff00' : '#00ffff';
+            ctx.beginPath();
+            ctx.arc(projected.x, projected.y, size, 0, Math.PI * 2);
+            ctx.fill();
+        }
+    });
+
+    // Draw HUD elements
+    drawRadar(player, gameState, targetLock);
+    drawSpeedIndicator(player, targetLock);
+    drawTargetingReticle(player, gameState);
+    drawWeaponCooldowns();
+} 
\ No newline at end of file