Last active
November 19, 2025 08:28
-
-
Save nonetrix/daa8b6233176b740aead594f99e427d2 to your computer and use it in GitHub Desktop.
vibe coded game so bad that crashes Mesa somehow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | |
| <title>Touch Voxel Clone</title> | |
| <style> | |
| body { margin: 0; overflow: hidden; background-color: #87CEEB; font-family: sans-serif; touch-action: none; user-select: none; -webkit-user-select: none; } | |
| canvas { display: block; } | |
| /* UI Overlay */ | |
| #ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } | |
| /* Crosshair */ | |
| #crosshair { | |
| position: absolute; top: 50%; left: 50%; | |
| width: 20px; height: 20px; | |
| background: transparent; | |
| border: 2px solid rgba(255, 255, 255, 0.8); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| #crosshair::after { | |
| content: ''; position: absolute; top: 50%; left: 50%; | |
| width: 4px; height: 4px; background: white; | |
| transform: translate(-50%, -50%); | |
| } | |
| /* Touch Zones */ | |
| .control-area { pointer-events: auto; position: absolute; bottom: 20px; } | |
| #stick-container { left: 20px; width: 120px; height: 120px; background: rgba(255,255,255,0.1); border-radius: 50%; border: 2px solid rgba(255,255,255,0.2); } | |
| #stick-nub { position: relative; top: 50%; left: 50%; width: 50px; height: 50px; background: rgba(255,255,255,0.5); border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; } | |
| /* Action Buttons */ | |
| #actions { right: 20px; display: flex; flex-direction: column; gap: 15px; align-items: flex-end; } | |
| .btn { | |
| width: 70px; height: 70px; | |
| background: rgba(0, 0, 0, 0.3); | |
| border: 2px solid rgba(255, 255, 255, 0.4); | |
| border-radius: 15px; | |
| color: white; font-weight: bold; | |
| display: flex; justify-content: center; align-items: center; | |
| font-size: 14px; text-transform: uppercase; | |
| pointer-events: auto; | |
| backdrop-filter: blur(4px); | |
| } | |
| .btn:active { background: rgba(255, 255, 255, 0.3); } | |
| .active-mode { background: rgba(0, 255, 0, 0.4); border-color: #0f0; } | |
| #debug { position: absolute; top: 10px; left: 10px; color: white; text-shadow: 1px 1px 0 #000; font-size: 12px; } | |
| </style> | |
| <!-- Import Map for Three.js --> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <div id="ui-layer"> | |
| <div id="debug">Loading World...</div> | |
| <div id="crosshair"></div> | |
| <!-- Left Joystick --> | |
| <div id="stick-container" class="control-area"> | |
| <div id="stick-nub"></div> | |
| </div> | |
| <!-- Right Buttons --> | |
| <div id="actions" class="control-area"> | |
| <div id="btn-mode" class="btn">Mode: Break</div> | |
| <div id="btn-jump" class="btn">Jump</div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| // --- Configuration --- | |
| const WORLD_SIZE = 32; | |
| const WORLD_HEIGHT = 16; | |
| const BLOCK_SIZE = 1; | |
| // --- Globals --- | |
| let camera, scene, renderer; | |
| let raycaster; | |
| // FIX: Define lastTime here so it exists before animate() is called | |
| let lastTime = performance.now(); | |
| // Physics | |
| const player = { | |
| position: new THREE.Vector3(WORLD_SIZE/2, WORLD_HEIGHT + 5, WORLD_SIZE/2), | |
| velocity: new THREE.Vector3(), | |
| speed: 6.0, | |
| jumpForce: 10.0, | |
| gravity: 25.0, | |
| height: 1.6, | |
| radius: 0.3, | |
| grounded: false | |
| }; | |
| // Voxel Data | |
| const voxels = {}; | |
| let instancedMesh; | |
| const maxInstances = WORLD_SIZE * WORLD_SIZE * WORLD_HEIGHT; | |
| let instanceCount = 0; | |
| const dummyMatrix = new THREE.Object3D(); | |
| // Inputs | |
| const input = { | |
| moveX: 0, | |
| moveY: 0, | |
| jump: false, | |
| placeMode: false | |
| }; | |
| // Touch State | |
| const touches = { | |
| moveId: null, | |
| lookId: null, | |
| startX: 0, startY: 0, | |
| lastLookX: 0, lastLookY: 0, | |
| tapStartTime: 0, | |
| isTap: false | |
| }; | |
| // Start Game | |
| init(); | |
| animate(); | |
| function init() { | |
| // 1. Scene Setup | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x87CEEB); | |
| scene.fog = new THREE.Fog(0x87CEEB, 10, 50); | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| // 2. Lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
| scene.add(ambientLight); | |
| const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| dirLight.position.set(50, 100, 50); | |
| scene.add(dirLight); | |
| // 3. Texture Generation | |
| const texture = createBlockTexture(); | |
| const material = new THREE.MeshLambertMaterial({ map: texture }); | |
| const geometry = new THREE.BoxGeometry(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); | |
| // 4. Instanced Mesh | |
| instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances); | |
| instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); | |
| scene.add(instancedMesh); | |
| // 5. World Generation | |
| generateWorld(); | |
| rebuildMesh(); | |
| // 6. Renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: false }); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| // 7. Raycaster | |
| raycaster = new THREE.Raycaster(); | |
| raycaster.far = 6; | |
| // 8. Input Listeners | |
| setupTouchControls(); | |
| window.addEventListener('resize', onWindowResize); | |
| } | |
| function createBlockTexture() { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = 64; canvas.height = 64; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.fillStyle = '#795548'; | |
| ctx.fillRect(0,0,64,64); | |
| ctx.fillStyle = '#4CAF50'; | |
| ctx.fillRect(0,0,64,16); | |
| for(let i=0; i<200; i++) { | |
| ctx.fillStyle = Math.random() > 0.5 ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'; | |
| ctx.fillRect(Math.random()*64, Math.random()*64, 2, 2); | |
| } | |
| ctx.strokeStyle = 'rgba(0,0,0,0.2)'; | |
| ctx.strokeRect(0,0,64,64); | |
| const tex = new THREE.CanvasTexture(canvas); | |
| tex.magFilter = THREE.NearestFilter; | |
| return tex; | |
| } | |
| function generateWorld() { | |
| for(let x=0; x<WORLD_SIZE; x++) { | |
| for(let z=0; z<WORLD_SIZE; z++) { | |
| const h = Math.floor( | |
| (Math.sin(x/4) + Math.cos(z/5)) * 2 + | |
| (Math.sin(x/10) + Math.cos(z/10)) * 2 + | |
| WORLD_HEIGHT/2 | |
| ); | |
| for(let y=0; y<=h; y++) { | |
| setVoxel(x, y, z, 1); | |
| } | |
| } | |
| } | |
| } | |
| function setVoxel(x, y, z, type) { | |
| const key = `${x},${y},${z}`; | |
| if (type === 0) { | |
| delete voxels[key]; | |
| } else { | |
| voxels[key] = type; | |
| } | |
| } | |
| function getVoxel(x, y, z) { | |
| return voxels[`${x},${y},${z}`] ? 1 : 0; | |
| } | |
| function rebuildMesh() { | |
| let i = 0; | |
| for (let key in voxels) { | |
| const [x, y, z] = key.split(',').map(Number); | |
| dummyMatrix.position.set(x, y, z); | |
| dummyMatrix.updateMatrix(); | |
| instancedMesh.setMatrixAt(i, dummyMatrix.matrix); | |
| i++; | |
| } | |
| for(let j=i; j<maxInstances; j++) { | |
| dummyMatrix.position.set(0, -1000, 0); | |
| dummyMatrix.updateMatrix(); | |
| instancedMesh.setMatrixAt(j, dummyMatrix.matrix); | |
| } | |
| instancedMesh.count = maxInstances; | |
| instancedMesh.instanceMatrix.needsUpdate = true; | |
| instanceCount = i; | |
| document.getElementById('debug').innerText = `Blocks: ${instanceCount} | Pos: ${Math.floor(player.position.x)},${Math.floor(player.position.y)},${Math.floor(player.position.z)}`; | |
| } | |
| function setupTouchControls() { | |
| const stickContainer = document.getElementById('stick-container'); | |
| const stickNub = document.getElementById('stick-nub'); | |
| const btnJump = document.getElementById('btn-jump'); | |
| const btnMode = document.getElementById('btn-mode'); | |
| stickContainer.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| const touch = e.changedTouches[0]; | |
| touches.moveId = touch.identifier; | |
| touches.startX = touch.clientX; | |
| touches.startY = touch.clientY; | |
| updateJoystick(touch.clientX, touch.clientY); | |
| }, {passive: false}); | |
| stickContainer.addEventListener('touchmove', (e) => { | |
| e.preventDefault(); | |
| for(let i=0; i<e.changedTouches.length; i++) { | |
| if(e.changedTouches[i].identifier === touches.moveId) { | |
| const t = e.changedTouches[i]; | |
| updateJoystick(t.clientX, t.clientY); | |
| } | |
| } | |
| }, {passive: false}); | |
| const endJoystick = (e) => { | |
| for(let i=0; i<e.changedTouches.length; i++) { | |
| if(e.changedTouches[i].identifier === touches.moveId) { | |
| touches.moveId = null; | |
| input.moveX = 0; | |
| input.moveY = 0; | |
| stickNub.style.transform = `translate(-50%, -50%)`; | |
| } | |
| } | |
| }; | |
| stickContainer.addEventListener('touchend', endJoystick); | |
| stickContainer.addEventListener('touchcancel', endJoystick); | |
| function updateJoystick(cx, cy) { | |
| const rect = stickContainer.getBoundingClientRect(); | |
| const centerX = rect.left + rect.width/2; | |
| const centerY = rect.top + rect.height/2; | |
| let dx = cx - centerX; | |
| let dy = cy - centerY; | |
| const maxDist = rect.width/2; | |
| const dist = Math.sqrt(dx*dx + dy*dy); | |
| if(dist > maxDist) { | |
| dx = (dx/dist) * maxDist; | |
| dy = (dy/dist) * maxDist; | |
| } | |
| stickNub.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`; | |
| input.moveX = dx / maxDist; | |
| input.moveY = dy / maxDist; | |
| } | |
| document.addEventListener('touchstart', (e) => { | |
| if(e.target.closest('.control-area')) return; | |
| for(let i=0; i<e.changedTouches.length; i++) { | |
| const t = e.changedTouches[i]; | |
| if(t.identifier !== touches.moveId && touches.lookId === null) { | |
| touches.lookId = t.identifier; | |
| touches.lastLookX = t.clientX; | |
| touches.lastLookY = t.clientY; | |
| touches.tapStartTime = Date.now(); | |
| touches.isTap = true; | |
| } | |
| } | |
| }, {passive: false}); | |
| document.addEventListener('touchmove', (e) => { | |
| for(let i=0; i<e.changedTouches.length; i++) { | |
| const t = e.changedTouches[i]; | |
| if(t.identifier === touches.lookId) { | |
| const dx = t.clientX - touches.lastLookX; | |
| const dy = t.clientY - touches.lastLookY; | |
| const sens = 0.005; | |
| camera.rotation.y -= dx * sens; | |
| camera.rotation.x -= dy * sens; | |
| camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, camera.rotation.x)); | |
| touches.lastLookX = t.clientX; | |
| touches.lastLookY = t.clientY; | |
| if(Math.abs(dx) > 2 || Math.abs(dy) > 2) touches.isTap = false; | |
| } | |
| } | |
| }, {passive: false}); | |
| document.addEventListener('touchend', (e) => { | |
| for(let i=0; i<e.changedTouches.length; i++) { | |
| const t = e.changedTouches[i]; | |
| if(t.identifier === touches.lookId) { | |
| if(touches.isTap && (Date.now() - touches.tapStartTime) < 300) { | |
| performAction(); | |
| } | |
| touches.lookId = null; | |
| } | |
| } | |
| }); | |
| btnJump.addEventListener('touchstart', (e) => { e.preventDefault(); input.jump = true; }); | |
| btnJump.addEventListener('touchend', (e) => { e.preventDefault(); input.jump = false; }); | |
| btnMode.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| input.placeMode = !input.placeMode; | |
| if(input.placeMode) { | |
| btnMode.innerText = "Mode: Place"; | |
| btnMode.classList.add('active-mode'); | |
| } else { | |
| btnMode.innerText = "Mode: Break"; | |
| btnMode.classList.remove('active-mode'); | |
| } | |
| }); | |
| } | |
| function performAction() { | |
| raycaster.setFromCamera(new THREE.Vector2(0,0), camera); | |
| const intersects = raycaster.intersectObject(instancedMesh); | |
| if (intersects.length > 0) { | |
| const hit = intersects[0]; | |
| if (!input.placeMode) { | |
| const p = hit.point.clone().addScaledVector(raycaster.ray.direction, 0.1); | |
| const x = Math.floor(p.x); | |
| const y = Math.floor(p.y); | |
| const z = Math.floor(p.z); | |
| if (y > 0) { | |
| setVoxel(x, y, z, 0); | |
| rebuildMesh(); | |
| } | |
| } else { | |
| const p = hit.point.clone().add(hit.face.normal.multiplyScalar(0.1)); | |
| const x = Math.floor(p.x); | |
| const y = Math.floor(p.y); | |
| const z = Math.floor(p.z); | |
| const playerBox = new THREE.Box3( | |
| new THREE.Vector3(player.position.x - 0.3, player.position.y - 1.6, player.position.z - 0.3), | |
| new THREE.Vector3(player.position.x + 0.3, player.position.y, player.position.z + 0.3) | |
| ); | |
| const blockBox = new THREE.Box3( | |
| new THREE.Vector3(x, y, z), | |
| new THREE.Vector3(x+1, y+1, z+1) | |
| ); | |
| if (!playerBox.intersectsBox(blockBox)) { | |
| setVoxel(x, y, z, 1); | |
| rebuildMesh(); | |
| } | |
| } | |
| } | |
| } | |
| function updatePhysics(dt) { | |
| if(dt > 0.1) dt = 0.1; | |
| const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion); | |
| forward.y = 0; forward.normalize(); | |
| const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion); | |
| right.y = 0; right.normalize(); | |
| const moveVec = new THREE.Vector3(); | |
| moveVec.addScaledVector(forward, -input.moveY); | |
| moveVec.addScaledVector(right, input.moveX); | |
| if(moveVec.length() > 0) moveVec.normalize(); | |
| player.velocity.x = moveVec.x * player.speed; | |
| player.velocity.z = moveVec.z * player.speed; | |
| if (input.jump && player.grounded) { | |
| player.velocity.y = player.jumpForce; | |
| player.grounded = false; | |
| } | |
| player.velocity.y -= player.gravity * dt; | |
| player.position.x += player.velocity.x * dt; | |
| if(checkCollision(player.position)) { | |
| player.position.x -= player.velocity.x * dt; | |
| player.velocity.x = 0; | |
| } | |
| player.position.z += player.velocity.z * dt; | |
| if(checkCollision(player.position)) { | |
| player.position.z -= player.velocity.z * dt; | |
| player.velocity.z = 0; | |
| } | |
| player.position.y += player.velocity.y * dt; | |
| if(checkCollision(player.position)) { | |
| player.position.y -= player.velocity.y * dt; | |
| if(player.velocity.y < 0) { | |
| player.grounded = true; | |
| player.velocity.y = 0; | |
| player.position.y = Math.round(player.position.y * 100) / 100; | |
| } else { | |
| player.velocity.y = 0; | |
| } | |
| } else { | |
| player.grounded = false; | |
| } | |
| if(player.position.y < -10) { | |
| player.position.set(WORLD_SIZE/2, WORLD_HEIGHT + 5, WORLD_SIZE/2); | |
| player.velocity.set(0,0,0); | |
| } | |
| camera.position.copy(player.position); | |
| } | |
| function checkCollision(pos) { | |
| const r = player.radius; | |
| const h = player.height; | |
| const x1 = Math.floor(pos.x - r); | |
| const x2 = Math.floor(pos.x + r); | |
| const z1 = Math.floor(pos.z - r); | |
| const z2 = Math.floor(pos.z + r); | |
| const y1 = Math.floor(pos.y - h); | |
| const y2 = Math.floor(pos.y - 0.1); | |
| for(let x = x1; x <= x2; x++) { | |
| for(let z = z1; z <= z2; z++) { | |
| for(let y = y1; y <= y2; y++) { | |
| if(getVoxel(x, y, z)) return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const time = performance.now(); | |
| const dt = (time - lastTime) / 1000; | |
| lastTime = time; | |
| updatePhysics(dt); | |
| renderer.render(scene, camera); | |
| } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment