
Dungeon Generator v2
Prompt
1) Goal & Scope Create a singleâfile HTML/JS canvas app that renders a procedurally generated dungeon and a player carrying a torch. The world is revealed only where the torch has lineâofâsight (LOS); previously seen tiles retain a dim, decaying memory glow. Closed doors block both movement and light; doors autoâopen when the player is adjacent or attempts to step into them. 2) World Model Grid of tiles WĂH. Each cell has one of: VOID, WALL, ROOM, CORRIDOR, DOOR. Doors store: Position (x,y), orientation {H|V}, state {open|closed}. Rooms: axisâaligned rectangles with center (cx,cy). Deterministic RNG via seed string (e.g., cyrb128 â mulberry32 or equivalent). 3) Generation Requirements Rooms Place targetRooms nonâoverlapping rooms with padding. Room sizes in [rmin, rmax]. Walls Any VOID orthogonally adjacent to a ROOM becomes WALL. Connectivity Graph Build nodes at room centers. Connect via MST + sparse extra edges (â10â15% of nonâMST edges) for cycles. Doors (one per room side) For each graph edge (room AâB): choose a door on the side of each room facing the other. At most one door per side per room; reuse an existing side door if needed. Place door in the wall ring; record orientation. Corridors (merged, 1âtile wide) Carve path between the outside cell of Aâs door and Bâs door using a weighted BFS/Lee or grid A* that: Prefers stepping onto existing CORRIDOR cells (merges parallel attempts). Allows carving through VOID and nonâroom WALL (but not roomâadjacent walls if that would breach rooms). Mark carved cells as CORRIDOR. Corridor Side Walls Any VOID orthogonally adjacent to a CORRIDOR becomes WALL. Connectivity Check Verify every room is reachable from any room through ROOM/CORRIDOR/DOOR cells (ignoring door states for generation). 4) Rendering Preârender a base color layer (in sRGB) onto an offscreen canvas: Ground, room floors, corridor floors, walls. Doors are drawn later (to reflect open/closed). Procedural tileset from seed: Palette (HSL â sRGB), mild noise/grunge for stone, distinct room/corridor hues. Sprites: doorH/doorV, doorOpenH/doorOpenV, player, torch. The main view draws a camera crop centered on the player, then scales by zoom. 5) Lighting (LOS torch â physically accurate falloff) Ambient = 0.0 (unexplored is black). Linearâspace pipeline: keep all lighting math in linear color; convert sRGBâlinear only at image I/O edges. Rayâmarched LOS on a subâcell grid with higher resolution: Subâcell scale S â {4..6} (default 4 or 5, higher costs more). Cast â1200â2000 rays uniformly over [0, 2Ï) (default 1600). March step â 0.2â0.3 tile (default 0.25); stop at world bounds or light radius. Walls stop light. Door rule: Closed door: blocks light like a wall. Open door: light passes (no special attenuation). Cornerâoccluders (no diagonal leaks): if a ray transitions into a diagonal neighbor (Îxâ 0 and Îyâ 0) and the two orthogonally adjacent tiles forming that corner are both blocking, terminate the ray (prevents light âthreading the needleâ between diagonally adjacent blockers). Falloff (purely physical): pointâlight inverseâsquare from the torch origin, no constant region: Intensity vs. distance in tiles d: L(d) = 1 / (d^2 + Δ^2) where Δ is a tiny constant (e.g., Δ = 0.1 tile) to avoid a singularity at d=0. (This does not introduce a perceptible flat zone.) Optionally allow a shaping exponent p (default 1.0) â L(d) = (1 / (d^2 + Δ^2))^p for âsoft/hardâ presets without violating physical behavior. Light radius & buffer: radius â„ halfViewport + halo; the light buffer must cover (camera + halo) to avoid edge sampling gaps. Composition per pixel (linear space): finalRGB = baseLinear * ( exposure * torchRGB_linear * L(d_sample) + memIntensity * memory[tile] ) Convert finalRGB â sRGB for display. 6) Fog of War (memory) memory is a Float32Array per tile in [0..1]. On each lighting solve, mark tiles seen by rays as 1.0. Exponential decay every frame: mem *= 0.5^(dt / halfLife). Defaults: memIntensity â 0.08, halfLife â 20s. Doors & sprites are drawn only if (tile is lit) OR (memory > epsilon). 7) Player, Doors, Movement Spawn on a safe passable tile (prefer corridors of degree â„2; fallback to room center; else first passable). Movement: W/A/S/D or arrow keys; 4âdir, tileâstep. Passability: ROOM, CORRIDOR â passable. DOOR â passable only if open. WALL/VOID â blocked. Autoâopen rules: If the player attempts to step into a closed door: open it, then move. Also open any adjacent closed doors after movement (QoL). On spawn and each move: recompute lighting; update memory; redraw. 8) Camera & UI Camera shows a configurable window of tiles, clamped to world; zoom 1â3Ă. Controls: Seed (randomize), Map WĂH, Rooms count, Room size range. View tiles vwĂvh, Zoom, Exposure. Lighting quality: presets that raise resolution (e.g., Low: S=3, raysâ900; Med: S=4, raysâ1300; High: S=5, raysâ1800). Falloff preset (optional): adjusts exponent p only; base model remains inverseâsquare from the torch. N: regenerate (keep or change seed), R: safe respawn. Export current view as PNG. 9) Determinism & Performance Deterministic per seed for generation, palette, tileset. Lighting recomputed only when state changes (movement/door opens); memory decay applies each frame. Use typed arrays and preallocated buffers. Avoid perâframe allocations in hot loops; reuse offscreen canvases and buffers. 10) Acceptance Criteria (functional) Every room is connected via corridors; corridors are 1âtile wide (no doubleâwide runs). †1 door per side of any room; corridor/room connections have exactly one door. Closed doors block both movement and light; open doors allow both. Unexplored area renders black; explored tiles show dim memory that decays over time. Torch lighting is purely physical: intensity follows inverseâsquare from the torch (no constantâintensity inner region). Apparent reach depends on exposure/quality settings. No diagonal light leaks: light cannot pass between two diagonally adjacent blocking tiles (cornerâoccluder rule). Higherâresolution light calculation is used by default: at least Sâ„4, raysâ„1200, and march step †0.3 tile. Player always spawns visible; camera centers on the player and clamps at edges. 11) Suggested Interfaces (pseudocode) generateDungeon({W,H,targetRooms,rmin,rmax,seed}) => { grid, rooms[], doors[], doorOpen[y][x], doorOrient[y][x], W, H, connected:boolean } buildTileset(seed, tile) => { images... } renderBaseToCanvas(dungeon, tiles, tile) => baseCanvas, baseLinearRGB // Physically accurate torch; high-res subcell grid + corner occluders solveTorch(dungeon, playerXY, { tile, // tile size in pixels S, // subcell scale (recommend 4..6) rays, // 1200..2000 step, // â0.25 tile radiusTiles, // >= halfViewport + halo p=1.0, // optional shaping exponent (1.0 = pure inverse-square) eps=0.1 // small epsilon to avoid singularity at d=0 }) => { samplePix(px,py) /* returns L in [0..1] at subcell */ }, seenMask(W*H) compose(viewCanvas, baseCanvas, baseLinear, sampler, exposure, region{px,py,w,h}, memory, dungeon, tile, memIntensity, tiles, zoom) openDoorAt(x,y); openAdjacentDoors(playerXY) Lighting kernel details (normative): For a ray sample at world position (x,y), with torch origin (ox,oy) and distance d = hypot(x-ox, y-oy): L(d) = (1 / (d*d + eps*eps))^p When stepping from (tx_prev, ty_prev) to (tx, ty) where both differ (diagonal), if isBlocking(tx, ty_prev) && isBlocking(tx_prev, ty) then terminate the ray before writing to the buffer.
A system prompt was added to support web rendering