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