Render actual generated dungeons through the AI pipeline — both top-down and
first-person (Eye-of-the-Beholder style) — instead of synthetic test depth.
All renderer-side: derived from the envelope's tiles+regions+theme, the core
stores none of it.
- depth.js: top-down height-field depth exporter (walls raised, pools recessed,
pillars as bumps, subtle per-theme relief), calibrated to the proven room_depth
levels so one tall room can't crush every floor dark.
- fpv.js: first-person depth via a Wolfenstein-style grid raycaster (16:9),
reports straight-ahead distance for opening detection.
- explore.js: room->room nav graph derived from REAL tile openings (scan
boundary gaps, bin by cardinal, flood the corridor to the destination room) —
nav + open share one source of truth with the rendered passages.
- bake_atlas.py: bakes per-room x4-facing pixel-art frames; per-theme + per-facing
(wall vs passage) prompts, fixed diffusion seed for cross-frame style coherence,
per-run unique prefixes so re-bakes don't silently skip.
- explore.html + explore-viewer.js: WASD first-person dungeon explorer
(room-to-room movement, turning, minimap), integer-pixel fullscreen.
- serve.py: stdlib dev server — static web root + /out tree + POST /save, no-store.
- main.js/render.js/style.css: export hooks, the depth button, distanceToWall export.
- tools/README.md: Stage 0/1/2/3 docs; .dev/2026-06-01-fpv-prompts.md: prompt
research (cfg-1 negatives are inert; trigger words summon hands; empty-ruin
reframe; fixed seed = consistency).
Proven end-to-end on seed 7 (17 rooms). Outputs organized under git-ignored
tools/out/. Control strength: 0.80-0.85 top-down, 0.85 first-person.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds RegionTheme + RoomThemer: a pure classifier (no tile changes) that
labels each room (terrain-driven Forge/Cistern/Hall, special Throne/
Threshold, weighted Crypt/Den/Library/Vault/Stone). Renderer tints themed
rooms; EntityPlacer biases contents by theme. Entrance/exit now chosen
RNG-free from geometry so themer & placer agree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The roadmap capstone: the dungeon is now populated.
- New entity layer: src/entity.rs (Entity { kind, at }, EntityKind {Entrance,
Exit, Treasure, Monster}); Map gains an `entities: Vec<Entity>` overlay field
(serde round-tripped). Entities aren't terrain, so no new Tile and the frozen
viz is untouched.
- EntityPlacer/EntityConfig: places one Entrance (random room), one Exit (the
room farthest from it — a traversal goal), per-room Treasure (treasure_chance)
and Monsters (monster_chance, 1..=max, never in the entrance room). All on
plain Floor only, never two per cell, fully deterministic.
- Handoff via the Blackboard (the designed side channel for non-tile/region/edge
data) under entity::BLACKBOARD_KEY, harvested by Pipeline into Map.entities —
so GenContext's 25 literals stay untouched.
- Renderer draws iconic markers on the final stage: green beacon (entrance),
cyan portal (exit), gold diamond (treasure), crimson eyed-blob (monster), each
with a soft glow. New entities toggle + treasure/monster sliders + readout.
Core: 135 tests green (6 new: one entrance/one exit on distinct floor, entrance
room monster-free, determinism, empty-map, recipe populated-check). clippy
clean. Pipeline is now 9 passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Eighth pass (after RoomCarver, before MstConnect): re-sculpts some rooms into
organic caves.
- CaveShaper/CaveConfig (cave_chance, min_room, fill, steps): fills the room
rect with noise, runs the classic 4-5 cellular automaton `steps` times, keeps
the LARGEST 4-connected floor component, and re-carves the room to that blob
(cells + bounds updated; the rest of the footprint reverts to Wall). Too-small
results leave the room clean. Caves stay Room regions — their organic outline
reads as a cavern, so the renderer needs no change.
- CorridorCarver now anchors to the room cell NEAREST bounds.center() instead of
the center point itself. Output-preserving for convex/centered shapes (the
center cell is already floor), but robust for irregular cave blobs whose AABB
center may fall in rock — so a cavernized room is always reachable.
- DungeonConfig gains `caves`; recipe inserts CaveShaper after RoomCarver.
Core: 129 tests green (4 new: cave is connected floor within room, small/zero
skip, determinism); connectivity holds with caves in the default recipe. clippy
clean. Pipeline is now 8 passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seventh pass (before pillars): floods an organic blob of Water or Lava into
some larger rooms via randomized BFS growth.
- New Tile::Water / Tile::Lava (impassable hazards). Exhaustive matches updated
(ascii '~'/'!', frozen viz colors, wasm codes 4/5).
- PoolDecorator/PoolConfig (water_chance, lava_chance, min_room): blob is
interior-only (>= 2 from edge, never the center), and a connectivity guard
verifies the room's remaining floor stays 4-connected with the center intact
before committing — so a pool never orphans part of a room.
- DungeonConfig gains `pools`; recipe inserts PoolDecorator after DoorPlacer.
- Renderer: water as cool reflective pools; lava as molten rock that ALSO
throws warm light onto nearby floor (BFS light field, wall-blocked) — lava
chambers visibly glow. New water/lava sliders.
Core: 125 tests green (4 new: interior placement, floor stays connected,
zero/small-room skip, determinism); connectivity holds with pools in the
default recipe. clippy clean (core + viz).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per feedback ("not every room needs pillars"): default PillarConfig drops to
room_chance 0.30 / min_room 7 (was 0.55/7). A typical dungeon now has a couple
of distinctive pillared halls rather than pillars in most rooms — measured ~14
of 24 default seeds carry any pillars at all, usually just one room's worth.
The pillars slider still goes to 1.0 for full colonnades.
Also expose pillarTiles in the web state() hook (iteration/measurement aid).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A finishing decorator that scatters free-standing pillar columns into larger
rooms on a spaced interior grid. Sixth pass in the dungeon recipe.
- New Tile::Pillar (impassable like Wall, drawn distinctly). Exhaustive matches
updated: ascii ('o'), frozen macroquad viz (still compiles), wasm code 3.
- PillarPlacer/PillarConfig (room_chance, min_room, spacing): places isolated
single-cell pillars only on interior room floor (>= 2 from the edge, never the
center), on a grid with spacing >= 2 — so the floor stays 4-connected by
construction and a room is never orphaned. Respects non-rect room shapes
(pillars land only on actual carved floor).
- DungeonConfig gains `pillars`; recipe appends PillarPlacer after DoorPlacer.
- Renderer draws pillars as lit stone columns with a contact shadow; new
"pillars" density slider.
Core: 121 tests green (5 new: interior-only placement, floor stays connected,
zero-chance/small-room skip, determinism). clippy clean (core + viz).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renderer-only: low-frequency stone mottling (per ~4x4 block) with faint grain
instead of high-frequency per-cell noise; room glow now warms toward amber so
lit chambers read as torch-lit while corridors fall to shadow. Stronger
room/corridor light contrast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RoomCarver now rolls a per-room shape within its chosen rectangle, weighted by
a new RoomConfig.shapes (ShapeWeights). The rect is still picked first and
unchanged, so room centers — and the corridors wired between them — are
identical to before; only the carved interior differs.
- Shapes built from per-row contiguous spans → guaranteed 4-connected and
convex, so flood-fill never strands a cell.
- Hard invariant: every shape contains rect.center() (the corridor target),
with a fallback to a full rect for tiny rooms or any non-covering shape —
keeps MstConnect/CorridorCarver correct.
- ShapeWeights::default() is rect-only (preserves old behavior + existing
tests); the dungeon recipe opts into ShapeWeights::varied().
- Configs lose Eq (now carry f64 weights); serde unaffected.
Core: 116 tests green (new forced-shape invariants test: in-bounds, center
carved, non-rectangular, 4-connected); connectivity + determinism + door tests
all pass with varied shapes active. Surfaced as 4 shape-weight sliders in the
web playground.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0+1 of the browser viz effort:
- core: drop unused `rand` dep (removes getrandom/libc → clean wasm32 build);
derive serde on DungeonConfig + 5 sub-configs so the bridge can (de)serialize
- reikhelm-wasm: thin wasm-bindgen cdylib exposing generate(seed, config_json)
-> JSON envelope (tiles as int codes, regions, edges, per-pass snapshots) and
default_config_json(); holds no generation logic
- workspace: add reikhelm-wasm member; default-members keeps host cargo
test/clippy on core+viz only
- reikhelm-web: vanilla-JS + Canvas 2D renderer running the real core live in
the browser. Atmospheric look — lit stone floor, raised stone walls with
rim-lit carved edges + top bevel, room light-pooling, amber door thresholds,
vignette. Controls: seed/reroll, 9 config sliders, stage scrubber, semantic
overlays (region outlines / graph / grid). window.reikhelm hooks for
Playwright-driven iteration.
Core stays 115 tests green. Driven + screenshotted via Playwright MCP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the design for a live browser-based dungeon generator: reikhelm-core
compiled to WASM (after dropping the unused rand dep), a thin reikhelm-wasm
bridge, and a vanilla-JS/Canvas atmospheric renderer driven + screenshotted
via Playwright. Plus C2: room shape variety (rect/octagon/ellipse/plus) to
escape rectangular rooms. Aesthetic target: atmospheric + polygonal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Press S to begin seed entry, type digits (HUD shows the buffer),
Enter applies and regenerates, Esc cancels, Backspace edits. Reroll
still increments. Digit-only input; buffer capped under u64's range.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Default was a pure MST (extra_edge_ratio 0.0), which reads as a linear
chain of rooms. Retune DungeonConfig::default to min_leaf 8 / max_depth 5,
rooms 5..=11, and extra_edge_ratio 0.30 — ~17 interconnected rooms with
loops. Validation inequality still holds (8 - 2 >= 5); boundary test now
uses an explicit edge-case config since the default no longer sits on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Corridors carve center-to-center and pierce room walls, so the old
wall-threshold rule placed almost no doors. Detect the pierced-wall
threshold (corridor floor outside a room, room on one side, corridor on
the opposite) and mark it Door with probability door_chance (default
0.5), else leave it an open archway. Connectivity is via corridor floor,
independent of doors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets downstream determinism tests compare maps with `assert_eq!` instead
of a serde-string workaround. Auto-gated on T (Grid<f32> gets only
PartialEq); Map has no float fields so Eq is sound.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>