AI Code Test
Minecraft
Review the original prompt, inspect the generated code, and compare how different AI models handled the same coding task.
Back to All Tests
Minecraft
You are an expert game developer tasked with creating a simplified game inspired by Minecraft, but with the following constraints and requirements. Constraints - Code Limit: The entire game must be implemented within 1000 lines of code. Prioritize essential features and optimizations to stay within this limit. - Technology: Use HTML, CSS, Javascript and Three.js. You must load them without using modules. Use a traditional script tag import.
Gemini Flash 3.5 (Standard)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voxel Craft 3D</title>
<!-- Importing Three.js via standard script tag -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
user-select: none;
background-color: #87CEEB;
}
#canvas-container {
width: 100%;
height: 100%;
}
/* UI Overlay */
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
transform: translate(-50%, -50%);
pointer-events: none;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background: white;
mix-blend-mode: difference;
}
#crosshair::before { top: 4px; left: 0; width: 10px; height: 2px; }
#crosshair::after { top: 0; left: 4px; width: 2px; height: 10px; }
#hud {
position: absolute;
top: 20px;
left: 20px;
color: white;
text-shadow: 2px 2px 0px rgba(0,0,0,0.5);
font-size: 16px;
font-family: monospace;
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 5px;
}
/* Screens */
.screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
transition: opacity 0.3s ease;
}
#loading-screen {
background-color: #1a1a1a;
color: white;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #333;
border-top: 5px solid #4CAF50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
#menu-screen {
background-color: rgba(0,0,0,0.6);
color: white;
display: none;
cursor: pointer;
}
.btn {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 18px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
font-weight: bold;
box-shadow: 0 4px #2E7D32;
}
.btn:active {
transform: translateY(4px);
box-shadow: 0 0 #2E7D32;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="ui-layer">
<div id="crosshair"></div>
<div id="hud">
<div>POS: X: <span id="pos-x">0</span>, Y: <span id="pos-y">0</span>, Z: <span id="pos-z">0</span></div>
<div>CHUNK: <span id="chunk-coord">0, 0</span></div>
<div>BLOCKS ACTIVE: <span id="block-count">0</span></div>
</div>
</div>
<div id="loading-screen" class="screen">
<div class="spinner"></div>
<h2>Generating World...</h2>
</div>
<div id="menu-screen" class="screen">
<button class="btn">CLICK TO PLAY</button>
<p style="margin-top: 20px; color: #ccc;">WASD = Move | Space = Jump | Left Click = Break | Right Click = Place</p>
</div>
<script>
// --- SEEDABLE RANDOM GENERATOR (For predictable terrain) ---
function MathRandomSeed(seed) {
let m = 0x80000000, a = 1103515245, c = 12345;
let state = seed ? seed : Math.floor(Math.random() * (m - 1));
return function() {
state = (a * state + c) % m;
return state / (m - 1);
}
}
const rand = MathRandomSeed(12345);
// --- SIMPLIFIED 2D NOISE FOR TERRAIN PROCDURAL GENERATION ---
const PerlinNoise = new function() {
this.noise = function(x, y) {
let X = Math.floor(x) & 255, Y = Math.floor(y) & 255;
x -= Math.floor(x); y -= Math.floor(y);
let u = fade(x), v = fade(y);
let A = p[X]+Y, B = p[X+1]+Y;
return lerp(v, lerp(u, grad(p[A], x, y), grad(p[B], x-1, y)),
lerp(u, grad(p[A+1], x, y-1), grad(p[B+1], x-1, y-1)));
};
function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(t, a, b) { return a + t * (b - a); }
function grad(hash, x, y) {
let h = hash & 7;
let u = h < 4 ? x : y, v = h < 4 ? y : x;
return ((h & 1) ? -u : u) + ((h & 2) ? -2.0 * v : 2.0 * v);
}
const p = new Array(512);
for (let i=0; i<256 ; i++) p[i] = Math.floor(rand() * 256);
for (let i=0; i<256 ; i++) p[256+i] = p[i];
};
// --- GAME CONFIGURATION ---
const CONFIG = {
chunkSize: 16,
chunkHeight: 32,
renderDistance: 3,
blockSize: 1,
gravity: -32.0,
jumpForce: 11.0,
playerSpeed: 7.0,
playerRadius: 0.35,
playerHeight: 1.8,
eyeHeight: 1.6
};
// --- BLOCK TYPE DEFINITIONS ---
const BLOCK_TYPES = {
AIR: 0,
GRASS: 1,
DIRT: 2,
STONE: 3
};
const BLOCK_COLORS = {
[BLOCK_TYPES.GRASS]: 0x557a2b,
[BLOCK_TYPES.DIRT]: 0x866043,
[BLOCK_TYPES.STONE]: 0x737373
};
// --- GLOBAL SYSTEM VARIABLES ---
let scene, camera, renderer, clock;
let chunks = {};
let instancedMeshes = {};
let materials = {};
let boxGeometry;
// Player physics state
let player = {
pos: new THREE.Vector3(0, 20, 0),
velocity: new THREE.Vector3(),
onGround: false,
controls: { forward: false, backward: false, left: false, right: false, jump: false }
};
// Camera rotational look properties
let pitch = 0, yaw = 0;
const targetPitchRange = Math.PI / 2 - 0.05;
// UI references
const ui = {
loading: document.getElementById('loading-screen'),
menu: document.getElementById('menu-screen'),
posX: document.getElementById('pos-x'),
posY: document.getElementById('pos-y'),
posZ: document.getElementById('pos-z'),
chunk: document.getElementById('chunk-coord'),
blocks: document.getElementById('block-count')
};
// --- INITIALIZATION WORKFLOW ---
function init() {
// Setup Three.js Core Engine
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.FogExp2(0x87CEEB, 0.025);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
clock = new THREE.Clock();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = false;
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Lighting setup
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffffff, 0.5);
sunLight.position.set(20, 40, 20);
scene.add(sunLight);
// Reuse geometry and solid color materials across all chunks
boxGeometry = new THREE.BoxGeometry(CONFIG.blockSize, CONFIG.blockSize, CONFIG.blockSize);
Object.keys(BLOCK_COLORS).forEach(type => {
materials[type] = new THREE.MeshLambertMaterial({ color: BLOCK_COLORS[type] });
});
// Set Initial safe spawn location over origin terrain
const spawnHeight = getTerrainHeight(0, 0) + CONFIG.eyeHeight + 1;
player.pos.set(0, spawnHeight, 0);
// Event Binding
window.addEventListener('resize', onWindowResize);
setupInputHandlers();
// Trigger Initial procedural loop pass
updateChunks(true);
}
// --- PROCEDURAL GENERATION ENGINE ---
function getTerrainHeight(x, z) {
// Multi-octave Perlin Noise for smooth landscape curves
let n1 = PerlinNoise.noise(x * 0.02, z * 0.02) * 12;
let n2 = PerlinNoise.noise(x * 0.08, z * 0.08) * 4;
return Math.floor(n1 + n2 + 10);
}
function generateChunkData(cx, cz) {
const key = `${cx},${cz}`;
if (chunks[key]) return chunks[key];
// Setup local data array buffer representing block matrix
const data = new Uint8Array(CONFIG.chunkSize * CONFIG.chunkHeight * CONFIG.chunkSize);
for (let x = 0; x < CONFIG.chunkSize; x++) {
for (let z = 0; z < CONFIG.chunkSize; z++) {
const worldX = cx * CONFIG.chunkSize + x;
const worldZ = cz * CONFIG.chunkSize + z;
const surfaceY = getTerrainHeight(worldX, worldZ);
for (let y = 0; y < CONFIG.chunkHeight; y++) {
let idx = getBlockIndex(x, y, z);
if (y > surfaceY) {
data[idx] = BLOCK_TYPES.AIR;
} else if (y === surfaceY) {
data[idx] = BLOCK_TYPES.GRASS;
} else if (y > surfaceY - 3) {
data[idx] = BLOCK_TYPES.DIRT;
} else {
data[idx] = BLOCK_TYPES.STONE;
}
}
}
}
chunks[key] = { data: data, cx: cx, cz: cz, meshGroup: null, dirty: true };
return chunks[key];
}
function getBlockIndex(x, y, z) {
return x + (y * CONFIG.chunkSize) + (z * CONFIG.chunkSize * CONFIG.chunkHeight);
}
function getBlockAtWorld(wx, wy, wz) {
const cx = Math.floor(wx / CONFIG.chunkSize);
const cz = Math.floor(wz / CONFIG.chunkSize);
const chunk = chunks[`${cx},${cz}`];
if (!chunk) return BLOCK_TYPES.AIR;
const lx = Math.floor(wx) - cx * CONFIG.chunkSize;
const ly = Math.floor(wy);
const lz = Math.floor(wz) - cz * CONFIG.chunkSize;
if (ly < 0 || ly >= CONFIG.chunkHeight) return BLOCK_TYPES.AIR;
return chunk.data[getBlockIndex(lx, ly, lz)];
}
function setBlockAtWorld(wx, wy, wz, type) {
const cx = Math.floor(wx / CONFIG.chunkSize);
const cz = Math.floor(wz / CONFIG.chunkSize);
const chunk = chunks[`${cx},${cz}`];
if (!chunk) return;
const lx = Math.floor(wx) - cx * CONFIG.chunkSize;
const ly = Math.floor(wy);
const lz = Math.floor(wz) - cz * CONFIG.chunkSize;
if (ly < 0 || ly >= CONFIG.chunkHeight) return;
chunk.data[getBlockIndex(lx, ly, lz)] = type;
chunk.dirty = true;
// Rebuild chunk visually
buildChunkMesh(chunk);
}
// --- RENDERING OPTIMIZATION (Instanced Mesh Builder) ---
function buildChunkMesh(chunk) {
// Remove previous instances out of memory map
if (chunk.meshGroup) {
scene.remove(chunk.meshGroup);
chunk.meshGroup.traverse(child => {
if (child.isInstancedMesh) child.dispose();
});
}
chunk.meshGroup = new THREE.Group();
const counts = { [BLOCK_TYPES.GRASS]: 0, [BLOCK_TYPES.DIRT]: 0, [BLOCK_TYPES.STONE]: 0 };
// Step 1: Compute number of visible faces/blocks to instantiate
for (let x = 0; x < CONFIG.chunkSize; x++) {
for (let y = 0; y < CONFIG.chunkHeight; y++) {
for (let z = 0; z < CONFIG.chunkSize; z++) {
const type = chunk.data[getBlockIndex(x, y, z)];
if (type !== BLOCK_TYPES.AIR) {
// Simple Culling Optimization check: verify if surrounded completely by solid walls
if (isTransparent(x+1, y, z, chunk) || isTransparent(x-1, y, z, chunk) ||
isTransparent(x, y+1, z, chunk) || isTransparent(x, y-1, z, chunk) ||
isTransparent(x, y, z+1, chunk) || isTransparent(x, y, z-1, chunk)) {
counts[type]++;
}
}
}
}
}
// Step 2: Build InstancedMesh objects for tracked categories
const instances = {};
Object.keys(counts).forEach(type => {
if (counts[type] > 0) {
instances[type] = new THREE.InstancedMesh(boxGeometry, materials[type], counts[type]);
chunk.meshGroup.add(instances[type]);
}
});
const trackers = { [BLOCK_TYPES.GRASS]: 0, [BLOCK_TYPES.DIRT]: 0, [BLOCK_TYPES.STONE]: 0 };
const dummy = new THREE.Object3D();
// Step 3: Map spatial matrix transformation entries
for (let x = 0; x < CONFIG.chunkSize; x++) {
for (let y = 0; y < CONFIG.chunkHeight; y++) {
for (let z = 0; z < CONFIG.chunkSize; z++) {
const type = chunk.data[getBlockIndex(x, y, z)];
if (type !== BLOCK_TYPES.AIR) {
if (isTransparent(x+1, y, z, chunk) || isTransparent(x-1, y, z, chunk) ||
isTransparent(x, y+1, z, chunk) || isTransparent(x, y-1, z, chunk) ||
isTransparent(x, y, z+1, chunk) || isTransparent(x, y, z-1, chunk)) {
const wx = chunk.cx * CONFIG.chunkSize + x + 0.5;
const wy = y + 0.5;
const wz = chunk.cz * CONFIG.chunkSize + z + 0.5;
dummy.position.set(wx, wy, wz);
dummy.updateMatrix();
const idx = trackers[type]++;
instances[type].setMatrixAt(idx, dummy.matrix);
}
}
}
}
}
scene.add(chunk.meshGroup);
chunk.dirty = false;
}
function isTransparent(x, y, z, currentChunk) {
// Handle cross-boundary neighbor verification checks
if (x < 0 || x >= CONFIG.chunkSize || z < 0 || z >= CONFIG.chunkSize || y < 0 || y >= CONFIG.chunkHeight) {
const wx = currentChunk.cx * CONFIG.chunkSize + x;
const wy = y;
const wz = currentChunk.cz * CONFIG.chunkSize + z;
return getBlockAtWorld(wx, wy, wz) === BLOCK_TYPES.AIR;
}
return currentChunk.data[getBlockIndex(x, y, z)] === BLOCK_TYPES.AIR;
}
function updateChunks(initialRun = false) {
const pChunkX = Math.floor(player.pos.x / CONFIG.chunkSize);
const pChunkZ = Math.floor(player.pos.z / CONFIG.chunkSize);
// Generate or fetch arrays inside tracking perimeter radius
let activeKeys = new Set();
let totalBlocksRendered = 0;
for (let x = -CONFIG.renderDistance; x <= CONFIG.renderDistance; x++) {
for (let z = -CONFIG.renderDistance; z <= CONFIG.renderDistance; z++) {
const cx = pChunkX + x;
const cz = pChunkZ + z;
const key = `${cx},${cz}`;
activeKeys.add(key);
const chunk = generateChunkData(cx, cz);
if (chunk.dirty || !chunk.meshGroup) {
buildChunkMesh(chunk);
}
}
}
// Cleanup routine targeting out-of-bounds chunks to preserve memory metrics
Object.keys(chunks).forEach(key => {
if (!activeKeys.has(key) && chunks[key].meshGroup) {
scene.remove(chunks[key].meshGroup);
chunks[key].meshGroup.traverse(child => {
if (child.isInstancedMesh) child.dispose();
});
chunks[key].meshGroup = null;
chunks[key].dirty = true;
}
});
if (initialRun) {
ui.loading.style.opacity = '0';
setTimeout(() => {
ui.loading.style.display = 'none';
ui.menu.style.display = 'flex';
}, 300);
}
}
// --- FIRST PERSON PHYSICS & COLLISION SYSTEM ---
function checkCollisions(position, velocity, dt) {
let nextPos = position.clone().add(velocity.clone().multiplyScalar(dt));
// Simple robust AABB Bounding Box Checks over local 3x3 context block layout
let minX = Math.floor(nextPos.x - CONFIG.playerRadius);
let maxX = Math.floor(nextPos.x + CONFIG.playerRadius);
let minY = Math.floor(nextPos.y - CONFIG.playerHeight);
let maxY = Math.floor(nextPos.y);
let minZ = Math.floor(nextPos.z - CONFIG.playerRadius);
let maxZ = Math.floor(nextPos.z + CONFIG.playerRadius);
player.onGround = false;
// Y Axis Resolution Pass (Floor / Ceilings)
for (let x = minX; x <= maxX; x++) {
for (let z = minZ; z <= maxZ; z++) {
// Check below player feet floor boundaries
if (getBlockAtWorld(x, Math.floor(nextPos.y - CONFIG.playerHeight), z) !== BLOCK_TYPES.AIR) {
let floorY = Math.floor(nextPos.y - CONFIG.playerHeight) + 1;
if (position.y - CONFIG.playerHeight >= floorY - 0.001) {
velocity.y = 0;
nextPos.y = floorY + CONFIG.playerHeight;
player.onGround = true;
}
}
// Check above head ceiling boundaries
if (getBlockAtWorld(x, maxY, z) !== BLOCK_TYPES.AIR) {
let ceilY = Math.floor(nextPos.y);
if (position.y <= ceilY + 0.001) {
velocity.y = 0;
nextPos.y = ceilY - 0.05;
}
}
}
}
// X & Z Axis Wall Sliding Resolution Pass
minY = Math.floor(nextPos.y - CONFIG.playerHeight + 0.1);
maxY = Math.floor(nextPos.y - 0.1);
// Resolve X displacement
for (let y = minY; y <= maxY; y++) {
for (let z = minZ; z <= maxZ; z++) {
if (velocity.x > 0 && getBlockAtWorld(maxX, y, z) !== BLOCK_TYPES.AIR) {
nextPos.x = maxX - CONFIG.playerRadius - 0.001;
velocity.x = 0;
} else if (velocity.x < 0 && getBlockAtWorld(minX, y, z) !== BLOCK_TYPES.AIR) {
nextPos.x = minX + 1 + CONFIG.playerRadius + 0.001;
velocity.x = 0;
}
}
}
// Resolve Z displacement
minX = Math.floor(nextPos.x - CONFIG.playerRadius);
maxX = Math.floor(nextPos.x + CONFIG.playerRadius);
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
if (velocity.z > 0 && getBlockAtWorld(x, y, maxZ) !== BLOCK_TYPES.AIR) {
nextPos.z = maxZ - CONFIG.playerRadius - 0.001;
velocity.z = 0;
} else if (velocity.z < 0 && getBlockAtWorld(x, y, minZ) !== BLOCK_TYPES.AIR) {
nextPos.z = minZ + 1 + CONFIG.playerRadius + 0.001;
velocity.z = 0;
}
}
}
position.copy(nextPos);
}
function updatePlayerPhysics(dt) {
if (document.pointerLockElement !== renderer.domElement) return;
// Apply Constant Gravity pull acceleration force downwards
if (!player.onGround) {
player.velocity.y += CONFIG.gravity * dt;
}
// Calculate directional input velocity vectors relative to view yaw angle orientation
let moveVector = new THREE.Vector3();
if (player.controls.forward) moveVector.z -= 1;
if (player.controls.backward) moveVector.z += 1;
if (player.controls.left) moveVector.x -= 1;
if (player.controls.right) moveVector.x += 1;
moveVector.normalize();
let targetMoveVelocity = new THREE.Vector3();
targetMoveVelocity.x = (moveVector.x * Math.cos(yaw) + moveVector.z * Math.sin(yaw)) * CONFIG.playerSpeed;
targetMoveVelocity.z = (moveVector.z * Math.cos(yaw) - moveVector.x * Math.sin(yaw)) * CONFIG.playerSpeed;
player.velocity.x = targetMoveVelocity.x;
player.velocity.z = targetMoveVelocity.z;
// Jump command processing rule
if (player.controls.jump && player.onGround) {
player.velocity.y = CONFIG.jumpForce;
player.onGround = false;
}
// Execute local physics verification processing
checkCollisions(player.pos, player.velocity, dt);
// Apply transformed camera matrix offsets
camera.position.copy(player.pos);
let lookTarget = new THREE.Vector3();
lookTarget.x = Math.sin(yaw) * Math.cos(pitch);
lookTarget.y = Math.sin(pitch);
lookTarget.z = Math.cos(yaw) * Math.cos(pitch);
camera.lookAt(player.pos.clone().add(lookTarget));
}
// --- INTERACTION RAYCASTING ENGINE (Break/Place) ---
function performRaycast(isDestroyMode) {
if (document.pointerLockElement !== renderer.domElement) return;
// 3D DDA style Raycast vector tracing step iteration calculations across voxel boundaries
let rayOrigin = new THREE.Vector3(player.pos.x, player.pos.y, player.pos.z);
let rayDirection = new THREE.Vector3();
camera.getWorldDirection(rayDirection);
let maxDistance = 5.0;
let currentDistance = 0.0;
let stepSize = 0.05;
let lastEmptyBlock = null;
while (currentDistance < maxDistance) {
rayOrigin.addScaledVector(rayDirection, stepSize);
currentDistance += stepSize;
let bx = Math.floor(rayOrigin.x);
let by = Math.floor(rayOrigin.y);
let bz = Math.floor(rayOrigin.z);
let targetBlock = getBlockAtWorld(bx, by, bz);
if (targetBlock !== BLOCK_TYPES.AIR) {
if (isDestroyMode) {
setBlockAtWorld(bx, by, bz, BLOCK_TYPES.AIR);
updateChunks();
} else if (lastEmptyBlock) {
// Prevent placing a block directly inside the player's bounding cylinder space
let pMinX = Math.floor(player.pos.x - CONFIG.playerRadius);
let pMaxX = Math.floor(player.pos.x + CONFIG.playerRadius);
let pMinY = Math.floor(player.pos.y - CONFIG.playerHeight);
let pMaxY = Math.floor(player.pos.y);
let pMinZ = Math.floor(player.pos.z - CONFIG.playerRadius);
let pMaxZ = Math.floor(player.pos.z + CONFIG.playerRadius);
if (lastEmptyBlock.x >= pMinX && lastEmptyBlock.x <= pMaxX &&
lastEmptyBlock.y >= pMinY && lastEmptyBlock.y <= pMaxY &&
lastEmptyBlock.z >= pMinZ && lastEmptyBlock.z <= pMaxZ) {
return; // Collides with player, abort placement
}
// Determine block type based on target depth layer selection
let placementType = BLOCK_TYPES.STONE;
if (by >= getTerrainHeight(bx, bz) - 1) placementType = BLOCK_TYPES.DIRT;
setBlockAtWorld(lastEmptyBlock.x, lastEmptyBlock.y, lastEmptyBlock.z, placementType);
updateChunks();
}
break;
}
lastEmptyBlock = { x: bx, y: by, z: bz };
}
}
// --- INPUT & WINDOW INTERFACE HANDLERS ---
function setupInputHandlers() {
// Pointer lock toggle mechanics
ui.menu.addEventListener('click', () => {
renderer.domElement.requestPointerLock();
});
document.addEventListener('pointerlockchange', () => {
if (document.pointerLockElement === renderer.domElement) {
ui.menu.style.opacity = '0';
setTimeout(() => ui.menu.style.display = 'none', 300);
} else {
ui.menu.style.display = 'flex';
setTimeout(() => ui.menu.style.opacity = '1', 10);
}
});
// Mouse Look Traversal Calculation Handling
document.addEventListener('mousemove', (e) => {
if (document.pointerLockElement !== renderer.domElement) return;
yaw -= e.movementX * 0.0025;
pitch -= e.movementY * 0.0025;
// Lock vertical view bounds prevents pitch flipping mechanics
pitch = Math.max(-targetPitchRange, Math.min(targetPitchRange, pitch));
});
// Action Mouse Click Bindings
document.addEventListener('mousedown', (e) => {
if (e.button === 0) performRaycast(true); // Left Click -> Break
if (e.button === 2) performRaycast(false); // Right Click -> Place
});
// Disable standard contextual popup on right click
window.addEventListener('contextmenu', e => e.preventDefault());
// Keyboard tracking mappings
const onKeyDown = (e) => {
switch(e.code) {
case 'KeyW': player.controls.forward = true; break;
case 'KeyS': player.controls.backward = true; break;
case 'KeyA': player.controls.left = true; break;
case 'KeyD': player.controls.right = true; break;
case 'Space': player.controls.jump = true; break;
}
};
const onKeyUp = (e) => {
switch(e.code) {
case 'KeyW': player.controls.forward = false; break;
case 'KeyS': player.controls.backward = false; break;
case 'KeyA': player.controls.left = false; break;
case 'KeyD': player.controls.right = false; break;
case 'Space': player.controls.jump = false; break;
}
};
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// --- HUD HUD MONITOR DATA PIPELINE ---
function updateHUD() {
ui.posX.textContent = player.pos.x.toFixed(1);
ui.posY.textContent = (player.pos.y - CONFIG.eyeHeight).toFixed(1); // Readout at feet level
ui.posZ.textContent = player.pos.z.toFixed(1);
const cx = Math.floor(player.pos.x / CONFIG.chunkSize);
const cz = Math.floor(player.pos.z / CONFIG.chunkSize);
ui.chunk.textContent = `${cx}, ${cz}`;
let activeInstances = 0;
Object.keys(chunks).forEach(k => {
if(chunks[k].meshGroup) {
chunks[k].meshGroup.children.forEach(c => { if(c.count) activeInstances += c.count; });
}
});
ui.blocks.textContent = activeInstances;
}
// --- MAIN ENGINE CORE LOOP PASS ---
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.1); // Clamp step to safeguard collisions on lag spikes
updatePlayerPhysics(dt);
updateChunks();
updateHUD();
renderer.render(scene, camera);
}
// Entry-point runtime kickstart execution
window.onload = () => {
init();
animate();
};
</script>
</body>
</html>