From 176ad34214d425865a724f9bc299d41168201ed8 Mon Sep 17 00:00:00 2001 From: pobadoba Date: Wed, 6 May 2026 01:47:27 +0900 Subject: [PATCH] fix(babylon_setup.js): fix exit not generating properly Co-authored-by: Copilot --- src/babylon_setup.js | 164 +++++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/src/babylon_setup.js b/src/babylon_setup.js index a7e43e0..d04bf1d 100644 --- a/src/babylon_setup.js +++ b/src/babylon_setup.js @@ -126,6 +126,7 @@ let chestMap = new Map(); // key: "x,y" -> {mesh, opened} let keyChestKey = null; let exitBox = null; let exitGridPos = null; // track exit grid position for collision checking +let spawnGridPos = null; // track spawn grid position for validation let spawnMarker = null; const cellSize = 2; @@ -139,6 +140,7 @@ function clearLevelMeshes() { if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; } if (spawnMarker) { try { spawnMarker.dispose(); } catch(e){}; spawnMarker = null; } exitGridPos = null; + spawnGridPos = null; } // Simple seeded RNG @@ -209,6 +211,26 @@ function findDeadEnds(grid) { return dead; } +function gridCellToWorld(grid, x, y) { + const halfW = (grid[0].length * cellSize) / 2; + const halfH = (grid.length * cellSize) / 2; + return new BABYLON.Vector3( + x * cellSize - halfW + cellSize / 2, + 0, + y * cellSize - halfH + cellSize / 2, + ); +} + +function isWalkableCell(grid, x, y) { + return y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 0; +} + +function isReservedCell(x, y) { + if (chestMap.has(`${x},${y}`)) return true; + if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) return true; + return false; +} + function buildLevelFromGrid(grid) { clearLevelMeshes(); const h = grid.length; @@ -295,71 +317,96 @@ function placeExit(grid, seed) { exitGridPos = { x, y }; } - const [x,y] = exitGridPos; - const halfW = (grid[0].length * cellSize) / 2; - const halfH = (grid.length * cellSize) / 2; - const ex = x*cellSize - halfW + cellSize/2; - const ez = y*cellSize - halfH + cellSize/2; + const [x,y] = [exitGridPos.x, exitGridPos.y]; + if (!isWalkableCell(grid, x, y)) { + console.warn("Exit selected on non-walkable cell", { x, y }); + return; + } + const exitWorld = gridCellToWorld(grid, x, y); + const ex = exitWorld.x; + const ez = exitWorld.z; + const worldSpan = Math.max(grid[0].length, grid.length) * cellSize; - // Ground plane indicator - const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*0.9, height: cellSize*0.9 }, scene); - plane.material = new BABYLON.StandardMaterial('exitMat', scene); - plane.material.emissiveColor = new BABYLON.Color3(0.9, 0.8, 0.2); - plane.position = new BABYLON.Vector3(ex, 0.01, ez); + // Ground plane indicator (larger + slightly raised to avoid z-fighting) + const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene); + const exitMat = new BABYLON.StandardMaterial('exitMat', scene); + exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03); + exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15); + exitMat.disableLighting = true; + plane.material = exitMat; + plane.position = new BABYLON.Vector3(ex, 0.08, ez); exitBox = plane; levelMeshes.push(plane); - // Tall pillar for visibility - const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.3, height: cellSize*3.0 }, scene); + // Very tall beacon for visibility over maze walls + const beaconHeight = Math.max(18, worldSpan * 0.8); + const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.55, height: beaconHeight }, scene); const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene); pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1); - pillarMat.emissiveColor = new BABYLON.Color3(0.3, 0.25, 0.05); + pillarMat.emissiveColor = new BABYLON.Color3(1.0, 0.7, 0.1); + pillarMat.disableLighting = true; pillar.material = pillarMat; - pillar.position = new BABYLON.Vector3(ex, cellSize*0.75, ez); + pillar.position = new BABYLON.Vector3(ex, beaconHeight * 0.5, ez); levelMeshes.push(pillar); + + // Bright orb at the top of beacon + const orb = BABYLON.MeshBuilder.CreateSphere('exitOrb', { diameter: cellSize * 1.0 }, scene); + const orbMat = new BABYLON.StandardMaterial('exitOrbMat', scene); + orbMat.diffuseColor = new BABYLON.Color3(1.0, 0.9, 0.3); + orbMat.emissiveColor = new BABYLON.Color3(1.0, 0.9, 0.35); + orbMat.disableLighting = true; + orb.material = orbMat; + orb.position = new BABYLON.Vector3(ex, beaconHeight + cellSize * 0.7, ez); + levelMeshes.push(orb); } function spawnCameraAt(grid) { - const h = grid.length, w = grid[0].length; - for (let y = 1; y < h-1; y++) { - for (let x = 1; x < w-1; x++) { - // Skip walls - if (grid[y][x] !== 0) continue; - - // Skip chests - if (chestMap.has(`${x},${y}`)) continue; - - // Skip exit - if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) continue; - - // Found valid spawn point - const halfW = (w * cellSize) / 2; - const halfH = (h * cellSize) / 2; - const px = x*cellSize - halfW + cellSize/2; - const pz = y*cellSize - halfH + cellSize/2; - - try { - if (camera && camera.position) { - camera.position = new BABYLON.Vector3(px, 1.6, pz); - } - } catch (e) {} - - // Add spawn marker sphere - if (spawnMarker) { - try { spawnMarker.dispose(); } catch(e) {} + const h = grid.length; + const w = grid[0].length; + let bestCell = null; + let bestDist = -1; + + // Choose a valid spawn that is far from exit when possible. + for (let y = 1; y < h - 1; y++) { + for (let x = 1; x < w - 1; x++) { + if (!isWalkableCell(grid, x, y)) continue; + if (isReservedCell(x, y)) continue; + + const d = exitGridPos ? Math.hypot(x - exitGridPos.x, y - exitGridPos.y) : 0; + if (d > bestDist) { + bestDist = d; + bestCell = { x, y }; } - const marker = BABYLON.MeshBuilder.CreateSphere('spawnMarker', { diameter: cellSize*0.4 }, scene); - const markerMat = new BABYLON.StandardMaterial('spawnMarkerMat', scene); - markerMat.diffuseColor = new BABYLON.Color3(0.2, 0.6, 0.95); - markerMat.emissiveColor = new BABYLON.Color3(0.1, 0.3, 0.5); - marker.material = markerMat; - marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz); - spawnMarker = marker; - levelMeshes.push(marker); - - return; } } + + if (!bestCell) { + console.warn("No valid spawn cell found."); + return; + } + + spawnGridPos = bestCell; + const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y); + const px = spawnWorld.x; + const pz = spawnWorld.z; + + try { + if (camera && camera.position) { + camera.position = new BABYLON.Vector3(px, 1.6, pz); + } + } catch (e) {} + + if (spawnMarker) { + try { spawnMarker.dispose(); } catch(e) {} + } + const marker = BABYLON.MeshBuilder.CreateSphere('spawnMarker', { diameter: cellSize*0.4 }, scene); + const markerMat = new BABYLON.StandardMaterial('spawnMarkerMat', scene); + markerMat.diffuseColor = new BABYLON.Color3(0.2, 0.6, 0.95); + markerMat.emissiveColor = new BABYLON.Color3(0.1, 0.3, 0.5); + marker.material = markerMat; + marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz); + spawnMarker = marker; + levelMeshes.push(marker); } function generateLevel() { @@ -374,7 +421,20 @@ function generateLevel() { placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level); placeExit(grid, seed + cfg.level); spawnCameraAt(grid); - sharedState.runtime.message = `Level ${cfg.level} generated.`; + + const placementValid = + !!exitGridPos && + !!spawnGridPos && + isWalkableCell(grid, exitGridPos.x, exitGridPos.y) && + isWalkableCell(grid, spawnGridPos.x, spawnGridPos.y) && + !(exitGridPos.x === spawnGridPos.x && exitGridPos.y === spawnGridPos.y); + + if (!placementValid) { + sharedState.runtime.message = `Placement warning: spawn/exit invalid on level ${cfg.level}.`; + console.warn("Invalid spawn/exit placement", { exitGridPos, spawnGridPos }); + } else { + sharedState.runtime.message = `Level ${cfg.level} generated (spawn ${spawnGridPos.x},${spawnGridPos.y} / exit ${exitGridPos.x},${exitGridPos.y}).`; + } window.requestAnimationFrame(()=>{ /* let scene update */ }); }