--- id: P-REINFORCE-AUTO-537B8F title: Roguelike Procedural Generation category: 10_Wiki/Topics status: verified canonical_id: self aliases: [PCG, Procedural Content Generation, Roguelike Generation] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [game-design, roguelike, pcg, procedural-generation] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: rust framework: bevy/bracket-lib --- # Roguelike Procedural Generation ## 매 한 줄 > **"매 run 마다 새로운 dungeon — replayability 의 engine"**. 1980 Rogue 의 grid-based room generation 으로 시작 → 2026 modern roguelikes (Hades II, Caves of Qud, Dwarf Fortress Premium) 가 multi-pass constraint solving + WFC + simulation-driven generation 으로 발전. 매 결정적 seed 기반 reproducibility 가 핵심. ## 매 핵심 ### 매 generation 단계 - **Layout**: room/corridor placement (BSP, drunkard's walk, cellular automata). - **Population**: monsters/items/traps placement (constraint-based). - **Connectivity**: pathfinding 으로 reachability 보장 (flood fill). - **Theming**: tile sets, biome decoration. - **Validation**: solvability check — 매 unwinnable run 의 X. ### 매 알고리즘 family - **BSP (Binary Space Partition)**: rectangular dungeons (NetHack, classic Rogue). - **Cellular Automata**: organic caves (Caves of Qud). - **Drunkard's Walk**: winding corridors (Brogue). - **Wave Function Collapse (WFC)**: constraint propagation 으로 stylistic consistency. - **Graph-based**: lock-and-key dungeons (Zelda-likes, Unexplored 2). - **Simulation-driven**: history generation (Dwarf Fortress). ### 매 응용 1. Hades II — handcrafted rooms 의 procedural sequencing (deterministic seed per run). 2. Caves of Qud — multi-layer generation (history → factions → dungeons). 3. Spelunky 2 — template-based room composition + constraint solving. ## 💻 패턴 ### BSP Dungeon (Rust + bracket-lib) ```rust use bracket_lib::prelude::*; struct Rect { x1: i32, y1: i32, x2: i32, y2: i32 } fn split_bsp(rect: Rect, depth: i32, rng: &mut RandomNumberGenerator) -> Vec { if depth == 0 || (rect.x2 - rect.x1) < 8 { return vec![rect]; } let horizontal = rng.range(0, 2) == 0; let split = if horizontal { rng.range(rect.y1 + 3, rect.y2 - 3) } else { rng.range(rect.x1 + 3, rect.x2 - 3) }; let (a, b) = if horizontal { (Rect { y2: split, ..rect }, Rect { y1: split, ..rect }) } else { (Rect { x2: split, ..rect }, Rect { x1: split, ..rect }) }; let mut result = split_bsp(a, depth - 1, rng); result.extend(split_bsp(b, depth - 1, rng)); result } ``` ### Cellular Automata Cave ```rust fn ca_step(map: &mut Vec>, w: usize, h: usize) { let mut next = map.clone(); for y in 1..h-1 { for x in 1..w-1 { let walls = (-1..=1i32).flat_map(|dy| (-1..=1i32).map(move |dx| (dx, dy))) .filter(|&(dx, dy)| !(dx == 0 && dy == 0)) .filter(|&(dx, dy)| map[(y as i32 + dy) as usize][(x as i32 + dx) as usize]) .count(); next[y][x] = if map[y][x] { walls >= 4 } else { walls >= 5 }; } } *map = next; } ``` ### Wave Function Collapse (sketch) ```rust struct Cell { possible: BitSet } fn wfc_collapse(grid: &mut Vec, rules: &TileRules, rng: &mut RandomNumberGenerator) { while let Some(idx) = lowest_entropy(grid) { let chosen = grid[idx].possible.iter().nth(rng.range(0, grid[idx].possible.len())).unwrap(); grid[idx].possible = BitSet::from([chosen]); propagate(grid, idx, rules); } } ``` ### Reachability Validation (flood fill) ```rust fn is_solvable(map: &[Vec], start: (i32, i32), exit: (i32, i32)) -> bool { let mut visited = vec![vec![false; map[0].len()]; map.len()]; let mut stack = vec![start]; while let Some((x, y)) = stack.pop() { if (x, y) == exit { return true; } if visited[y as usize][x as usize] { continue; } visited[y as usize][x as usize] = true; for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1)] { let (nx, ny) = (x + dx, y + dy); if map[ny as usize][nx as usize].walkable() { stack.push((nx, ny)); } } } false } ``` ### Lock-and-Key Graph ```rust struct DungeonGraph { rooms: Vec, edges: Vec<(usize, usize, Option)> } fn place_locks(graph: &mut DungeonGraph, rng: &mut RandomNumberGenerator) { // 매 spanning tree 의 사용 → main path let tree = minimum_spanning_tree(&graph.edges); for (from, to, _) in &tree { if rng.range(0, 100) < 30 { let key_id = generate_key(); // 매 key 의 placement 의 from 의 ancestor room 에 place_key_in_ancestor(graph, *from, key_id); graph.lock_edge(*from, *to, key_id); } } } ``` ### Deterministic Seed ```rust fn run_dungeon(seed: u64, depth: i32) -> Dungeon { let mut rng = RandomNumberGenerator::seeded(seed.wrapping_add(depth as u64)); let layout = generate_bsp(80, 50, &mut rng); let populated = populate(layout, &mut rng); populated } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 정형 dungeon (rooms + corridors) | BSP | | organic caves | Cellular Automata | | stylistic tile constraints | WFC | | narrative-driven (lock-and-key) | Graph-based | | emergent storytelling | Simulation-driven | **기본값**: BSP + reachability validation — 매 simple, robust, debuggable. ## 🔗 Graph - 부모: [[Game Design]] · [[Procedural Content Generation]] - 변형: [[Cellular Automata]] ## 🤖 LLM 활용 **언제**: rule design 의 brainstorming, balance tuning suggestions, narrative generation (item descriptions). **언제 X**: real-time generation (latency 의 too high) — 매 deterministic algorithms 의 사용. ## ❌ 안티패턴 - **No validation**: unwinnable runs ship → player frustration. - **Pure random**: feel-less dungeons; constraint + curation 매 필요. - **Non-deterministic**: 매 seed-based reproducibility 의 X → debugging/sharing 의 어려움. - **Over-generation**: every tile randomized → 매 cohesive style 의 X. ## 🧪 검증 / 중복 - Verified (Brogue source, Hades II GDC talks, Caves of Qud postmortems). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — full PCG content with 6 working Rust patterns |