Commit graph

53 commits

Author SHA1 Message Date
277c158378 feat(deepfall): board-game design + balance-by-bot simulator
DEEPFALL: a tabletop game mashing up 9 loved games (SmashUp, Dragon
Rampage, Blood Rage, Catan, EverQuest, Daggerfall, Eye of the Beholder,
Quarriors, SmallWorld). Core fusion = SmallWorld decline + Blood Rage
glorious death -> three exits (Cash Out / Press On / Worthy End) under a
rising Maw that collapses a reikhelm dungeon floor-by-floor.

- deepfall-core: pure deterministic engine (vendored ChaCha8, 11 tests)
- deepfall-sim: balance-by-bot sweeps -> CSV/JSON
- analysis/analyze.py: stdlib ASCII heatmap (no deps)
- DESIGN.md (rules + post-critique revisions), FINDINGS.md (9-iteration
  balance journey), README.md
- reikhelm-core/examples/sample_dungeon.rs: dumps a real generated board

Method: adversarial design critics -> build -> simulate -> tune. 9
iterations compressed combo spread 77.8 -> 32.5 pts (all 36 combos
viable). Fixed deep-content lockout (value rises with the Maw), the
multi-collapse feel-bad (1 collapse/round cap; feel-bad now 0.00/game),
and dead combos. Open finding: Cash-Out and Worthy-End are substitutes
for a glory-maximizing bot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:49:33 -06:00
a86b52b4c5 docs(pipeline): iteration-4 QA sweep + strength-0.70 finding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:17:55 -06:00
b661989d4b fix(pipeline): bake strength 0.85→0.70 (kills ceiling blowout)
GPU A/B on the worst-blown frame (r9_N crypt): at strength 0.85 the
ControlNet forced the bright near-ceiling depth into a blown-white band
(27.7% of pixels >245); at 0.70 the blowout vanishes (0.0%) and the
ceiling renders as natural dark vaulted stone, with geometry still
honored. Verified a closed wall (r0_N threshold) stays solid at 0.70 —
no phantom doors return — so one global strength suffices. Matches the
documented Z-Image 0.6-0.7 sweet spot now that rev-3 maps are
full-contrast (high strength was only needed to force weak old maps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:17:17 -06:00
3773ac2ad7 docs(pipeline): iteration-3 end-to-end validation + campaign re-bake notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:24:33 -06:00
5506c38a51 fix(render): kill blown highlights + phantom doors (GPU-verified)
First GPU pass on the rev-3 depth pipeline surfaced two issues; both fixed
and re-rendered clean:

1. Blown near-field highlights (regression from 018909b). Mapping the
   nearest pixel to pure white (nearPlane=nearRaw) made the depth
   ControlNet render overexposed near floors/ceilings at strength 0.85.
   fpv.js: nearPlane=0 (anchor white at the camera; nearest real surface
   ≈gray 214, headroom intact) + minSpan 5→8. Re-bake confirms the blown
   ceiling is gone while recesses still reach black and geometry holds.

2. Phantom doors on closed walls — a PROMPT problem, not the depth map
   (present in old maps too). Opening NOUNS in the positive prompt summon
   a passage onto solid stone: threshold's "gatehouse, portcullis,
   entrance" painted a gate on a blank wall, and at cfg 1.0 the negative
   can't cancel it. bake_atlas.py: THEME_BODY_CLOSED (full theme flavor,
   zero opening nouns) for closed facings + opening-nouns negated on
   closed facings only. r0_N (threshold) now renders clean ashlar wall.
   ("Bricked-up archway" backfires — "archway" alone re-summons the arch.)

Also: sweep_strength.py re-exec-under-venv guard (the venv python is a
symlink to the base interpreter, so the realpath check wrongly skipped
the hop and the montage died after every render); now env-sentinel
guarded. Findings in .dev/2026-06-15-diffusion-pipeline-reliability.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:57:49 -06:00
e0cbd3d66b feat(pipeline): A/B mode for strength sweep (old-vs-new depth maps)
--alt-work renders each test frame from a second depth dir alongside the
new full-range maps, so the GPU run proves the rev-3 normalization fix
(018909b) beats the washed-out rev-2 maps rather than just looking
better. Persisted the rev-2 maps under tools/out/compare/rev2_depths so
the A/B is reproducible. Doc updated with the one-shot command.

Workstation note: host 192.168.1.26 is up but ComfyUI (:8188) wasn't
running this iteration — needs a manual start before the sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:18:13 -06:00
53cd1023e0 feat(pipeline): strength-sweep harness + reliability findings doc
sweep_strength.py renders one open + one closed FPV frame across a
ControlNet strength matrix at fixed seed/prompt and montages them beside
the depth map — the GPU experiment to find the right strength now that
rev-3 depth maps carry a full-range signal (018909b). Findings/plan in
.dev/2026-06-15-diffusion-pipeline-reliability.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:25:45 -06:00
018909b2cd fix(render): full-range per-frame depth normalization for FPV ControlNet
Diagnosis: every baked FPV depth map used only the upper-mid grays
(typ. 160–214, 0/68 frames reaching true black, only 35/68 reaching
near-white). A fixed 0..maxView=12 scale meant the nearest floor (~1
cell) never hit white and a wall 3 cells ahead never went past mid-gray,
so a small room collapsed into a low-contrast band. A depth ControlNet
trained on full-range MiDaS/depth-anything maps gets almost no structural
signal from that — the root cause of "controlnet barely followed / hit
and miss" renders.

Fix: two-pass raycast. Pass 1 records each column's wall distance and the
frame's true near/far. Pass 2 maps depth through a per-frame window so
near surfaces read white and the deepest visible recess reads black every
frame. The far plane tracks the deepest real surface but is clamped to a
minSpan floor so a shallow dead-end nook is NOT stretched into a fake
tunnel — its faced wall stays an honest mid-gray slab (no black recess
for the model to paint a phantom door into). gamma default 2.0→1.5 since
normalization now owns the range. nearPlane/minSpan exposed as opts.

Verified offline (headless bake, seed 7, 68 frames): median dynamic range
193→255; frames reaching true-white near surfaces 35/68→68/68; worst-frame
range 54→106. Visual before/after confirms open frames now frame a black
passage and closed frames a clean mid-gray wall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:24:29 -06:00
df02911a1b feat(world): procedural overworld generator — continents, climate, rivers
A new `world` generation domain in reikhelm-core, sibling to the dungeon
passes. Builds continuous fields (elevation, temperature, moisture, flow)
and classifies them into biomes — a general-purpose world generator any
game can consume, the foundation for the "generate a world for any game"
app idea.

Stages (each forks its own RNG sub-stream, like the dungeon pipeline):
- noise:     own Perlin + fBm + domain warp (no `noise` crate, same reason
             we own range/shuffle — version drift would change every seed)
- elevation: domain-warped fBm shaped by edge falloff + redistribution
- hydrology: ocean/lake flood, priority-flood depression fill, D8 flow
             accumulation → dendritic rivers that always reach the sea
- climate:   latitude temperature w/ altitude lapse; prevailing-wind
             moisture sweep with orographic rain + emergent rain shadows
- biome:     Whittaker classifier over temperature × moisture

Determinism: same (config, seed) → byte-identical World on every machine.
The whole generator is transcendental-free (only + - * / and comparisons),
so even the f32 fields reproduce exactly cross-platform (spec §7 ethos).

Plus `examples/world_png.rs`: a zero-dependency PNG exporter (hand-rolled
stored-DEFLATE zlib) with biome palette, ocean-depth shading, and hillshade
— a viewer for eyeballing output, kept out of the pure core.

24 new tests (determinism, rain-shadow, depression-filling, river density,
Whittaker corners); full suite 169 passing, clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:11:04 -06:00
a12bcbbcad feat(art): re-bake the DESCENT campaign through the rev-2 pipeline
All 208 frames of seeds 7/69/59, rendered with per-direction cameras
(directedVantage: gap-centered when open, close + off-axis when walled,
per-class wall seed) and dithered through the speckle-free rev-2 recipe
(sRGB-spaced LUT, gamut-clamped diffusion, --overshoot 6).

Walls no longer grow phantom doors; the passage you walk is the passage
you see; shadows are warm stone instead of green confetti.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:37:03 -06:00
acc8137f4a fix(pipeline): kill dither speckle + atlas geometry lies (post-playtest)
Parley's review: dither full of green spots, baked rooms don't match the
nav graph (doors painted on closed walls, real openings off-center).
Both root-caused:

- pixelate.py: the nearest-color LUT was spaced in LINEAR RGB, cramming
  the whole shadow range into ~4 cells — dark picks were effectively
  arbitrary (green/teal confetti in warm near-blacks). LUT is now
  sRGB-spaced, diffusion error is gamut-clamped (no more blue blobs at
  torch highlights), and a new --overshoot penalty forbids choosing a
  color more saturated than the source. Green-dominant dark pixels on
  the test frame: 5.2% -> 0.02%, while palette usage went UP (107 -> 135
  colors). Recipe rev 2: append --overshoot 6.

- fpv.js: new directedVantage(env, region, dir) — per-(room,facing)
  camera instead of one room-wide vantage. Open ahead: stand in a cell
  whose straight-ahead ray escapes through the actual gap (the passage
  you walk is centered, 2-7 cells deep). Closed ahead: stand ~2 cells
  from the wall, deliberately OFF the room's axis — a symmetric stage
  begs the model to paint a centered focal door; an off-axis dead-end
  corner doesn't. Walls also render under their own fixed seed and an
  emphatic sealed-masonry prompt clause (cfg 1.0 negatives are inert).

Validated on seed 7 room 1: the closed-N phantom door is gone; the open-S
passage is dead-center. Full campaign re-bake follows as its own commit.

Also: rest-overlay no longer sticks without a keyup, in-combat drink
message, feathered sprite mask, no monster packs on floor 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:06:46 -06:00
5813ee3b5a feat(game): REIKHELM: DESCENT — playable first-person roguelike
The pieces finally meet: procgen dungeons + the baked EoB atlas pipeline +
the Vigor combat engine become one game. Three fixed pre-baked floors
(seeds 7 → 69 → 59, 208 frames), monsters/loot/encounters rerolled per run
and composited over the empty scenes; the Ember Sovereign waits at the
bottom.

- game-wasm: run state machine in Rust (manifest nav graph as dungeon
  truth, seeded spawning by theme/depth, embedded combat-core Encounter,
  vigor/fatigue persistence between fights, rest/potions/relics/flee,
  permadeath + victory). 8 native tests incl. a whole-run bot and a
  ScriptedPlayer boss-winnability probe used for tuning.
- game-web: no-framework front-end — title/death/victory screens, baked
  frames with screen-blend monster portraits, combat overlay (dual vigor
  bars, cast telegraphs, cooldown sweeps, floaters), minimap with fog,
  all SFX synthesized in WebAudio.
- balance: floor-scaled levels with a hero's edge over trash, no packs on
  floor 1, boss tuned by sim sweep (~35%/55% win at skill 50/80).
- campaign atlases now versioned (tools/.gitignore exception); boss
  portrait added to the Z-Image roster.

Verified end-to-end in-browser: an autopilot beat the full game through
the real UI (10 fights, 2 flees, Sovereign down), and died plenty before
that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:30:50 -06:00
57dba5e149 feat(combat): order-of-magnitude slowdown — fights now ~20-30s
Fights were ~3-5s; the user wanted 20-30s. Tuned the table (and added one
mechanic) so they land there, re-validated against the sim throughout.

- Engine: a `global_cooldown` (the genre swing timer, §10 "attack delay") — an
  actor can't start a new action until it elapses, so no button-masher can
  out-DPS the intended tempo. Config-driven (`global_cooldown` ticks; 0 = off).
  Actor gains `next_action_at`; engine gates decisions on it.
- Controller: ScriptedPlayer now pressures with its heavy proactively when
  healthy (not only on the exact overrun frame), so the sim measures what a real
  player experiences instead of an unrealistically patient bound.
- Config rebalance: damage cut + flattened, fatigue/regen/recovery co-tuned, and
  the kit made roughly **DPS-neutral** with auto-attack (each ability's cooldown
  matched to auto's damage-per-second). Under the global cooldown that's the key
  insight: abilities win through stagger leverage + timing, not raw throughput —
  otherwise strong abilities trivialize the pace and flat ones aren't worth their
  fill (the guardrail kept inverting until this landed). Auto-attacks (difficulty
  0) also no longer fizzle.
- Front-end: cache-bust the config fetch so edits are always picked up; widened
  the sim's archetype sweep to all six monsters.

Result (sim, skilled play ≈ real play): monster fights 16-38s (centered ~24s);
guardrail steep and green (even-con auto 0% → skill20 2% → skill50 12% →
skill80 44% → skill100 78%); all six monsters winnable. Verified live in-browser:
realistic play lands ~15-30s and a clean mid-fight reads perfectly on the dual bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:34:10 -06:00
902a233229 feat(combat): playable browser front-end + AI monster portraits
A real-time consumer of combat-core (the front-end the spec designs for, §14):
pick a foe, manage your single Vigor pool, try not to spend your survival.

- combat-core: a HumanController (input-driven, spec §5) behind a shared intent
  cell, plus Encounter::tick_once / drain_events so a wall-clock front-end can
  drive the engine a tick at a time (§10). Trivial abilities (difficulty 0, e.g.
  auto-attack) no longer fizzle. Engine unchanged otherwise; 48 tests still green.
- combat-wasm: a wasm-bindgen Game bridge — tick-stepping, JSON world state, the
  player's ability list, and a flavorful MonsterAI that uses each monster's
  signature kit (the wraith curses your ceiling) on a coin-flip so big hits space
  into a readable duel. Own workspace member, kept out of the default host build.
- combat-web: an atmospheric battler. The signature dual Vigor bar shows fill,
  the regen-headroom ceiling, the ceiling marker, and the dark cracked fatigue
  zone in one bar — the squeeze made legible. Floating damage, cast telegraphs,
  combat log, result overlay. Verified end-to-end in a real browser.
- tools/portraits.py: stdlib text-to-image client reusing the proven LAN Z-Image
  stack (minus the depth ControlNet) to render the bestiary. 7 portraits committed.
- Added skeleton / troll / wraith monster stat blocks; bumped the duelist's
  durability so fights are readable (even-con ~30s) and active play beats every
  monster while passive auto-attacking loses the hard ones. Re-validated the sim:
  the skill gradient and anti-turtle guardrail still hold (auto ~1% → skill100 34%
  at even con). README findings updated to match.

Verified live: roster screen, win/lose, the dual bars, telegraphed casts, and a
3s active-play victory over the Skeleton Knight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:46:47 -06:00
5055feb4c3 feat(combat): deterministic combined-pool combat engine + sim + analysis
Build the full combat-system experiment from its spec, no slices deferred.

combat-core — pure, headless, deterministic engine (the single source of truth):
- One combined Vigor pool (HP+mana+stamina merged) with the two-axis
  (fill, ceiling) cost model; offense spends survival.
- Stagger cascade (absorbed/dazed/unconscious/dead), capacity = current fill,
  ceiling collapse via fatigue as the real loss condition; revive-on-recovery.
- Unified ability model (skills == spells) with the delivery+effects+potency+
  targets cost composer and a full effect library (fill/ceiling damage, DoT/HoT,
  regen-sabotage, fatigue-amp, mitigation buff/debuff, recovery).
- Ordered mitigation pipeline, config-driven con curve, competency/fizzle/resist,
  loadout/memorization, active-defense windows.
- Symmetric actors; pluggable controllers (SwingOnCooldown, ScriptedPlayer{skill},
  HoldAndPunish). Fixed-tick loop with the canonical within-tick resolution order;
  decisions are simultaneous within a tick so mirror matches are fair.
- Determinism is load-bearing: vendored order-independent RNG fork (+ integer
  chance_bp so no f64 touches the hot path) and integer fixed-point magnitudes
  with banker's rounding. 48 tests incl. bit-reproducible event-log integration.

combat-sim — batch harness: loads the config "table", runs con-tier x skill and
archetype sweeps (tens of thousands of fights), exports per-fight CSV, an
aggregated summary, and one fight's full event log.

analysis — pandas/matplotlib layer: difficulty curve, duration tent, anti-turtle
guardrail, stagger frequency, and one fight's fill/ceiling time series.

The sim did its job: the first default numbers produced a one-tick cliff and 82%
mutual-KO draws and surfaced the spec's deepest risk live (a poke skill strictly
worse than free auto-attack, so auto-only out-won the full kit). Tuning the table
moved it to the intended shape — duration tent peaking ~22s at even con, a
monotone skill->win-rate gradient (auto ~3% -> skill100 44% at even con), and a
green anti-turtle guardrail. That sim->measure->tune loop is the deliverable.

Own Cargo workspace, fully decoupled from the root reikhelm workspace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:13:04 -06:00
e112ef5ee9 docs(combat): harden spec after two critic passes
Adversarial review found no blockers but several serious issues; fixed:
- determinism: require vendoring reikhelm-core's order-independent, version-
  stable Rng::fork (not DefaultHasher) + integer fixed-point internal state
  with documented per-stage rounding; carve out chance()'s IEEE-deterministic
  f64 compare as the sole hot-path exception
- pin a canonical within-tick resolution order (incl. step-0 controller
  decisions in actor-id order) as a determinism invariant
- close internal-consistency dead-ends: fatigue clamp [0,max_vigor], cap=0 as
  the explicit attrition-collapse trigger, regen floor + no-soft-lock rule,
  fix inverted ceiling-damage verb
- enrich instrumentation (pre-mitigation damage, per-stage mitigation, blocked-
  action + fail-reason, fill-flow accounting) + anti-turtle guardrail metric

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:51:12 -06:00
35c544e931 docs(combat): design spec for combined-pool combat system experiment
New sandbox experiment: a deterministic, fully-parameterized real-time
combat engine (EQ/Daggerfall lineage) built on a single combined Vigor pool
(health+endurance+mana) with ceiling/fatigue attrition, capacity-as-fill
stagger cascade, symmetric Actors + pluggable controllers, one composable
Ability model (skills==spells), and classless three-layer emergent identity.
Sim-first: headless deterministic engine + batch sweep harness + CSV/JSON
export for data-driven balancing. Rust engine (combat-core/combat-sim) +
Python analysis layer, in its own decoupled combat/ workspace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:40:50 -06:00
ae67ad2ffa docs: add root README — sandbox model + folder-per-experiment convention
reikhelm is now framed as an open-ended sandbox: each game-system experiment
lives in its own top-level folder, loosely coupled, glue code as needed. README
documents the convention and maps the current contents (dungeon procgen / web
playground + explorer / AI-render pixel-art pipeline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:16:49 -06:00
ed00e31009 feat(render): real-geometry depth export + first-person explorable atlas
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>
2026-06-02 00:02:14 -06:00
e3a30f6962 feat(tools): AI-render → pixel-art pipeline (depth render + Primordyn dither)
- comfy-spike/comfy.py: depth → ComfyUI/Z-Image render bridge over LAN
- pixelart/pixelate.py: locked Primordyn recipe (Floyd–Steinberg, OKLab,
  linear-light) limited-256 dither; montage.py helper; primordyn_v2 palette
- README + .gitignore; curated before/after + contact-sheet samples

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:36:33 -06:00
0aec5e927d feat(core): room theming — RoomThemer classifier + themed renderer (10th pass)
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>
2026-05-31 17:03:53 -06:00
e3501a02f3 chore(core): drop unused RegionId imports in pass tests (clippy --all-targets clean)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:12:14 -06:00
00502153f3 feat(core): items & creatures — entity layer + EntityPlacer (9th pass)
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>
2026-05-31 01:11:13 -06:00
381a52c310 feat(core): cellular-automata caverns (CaveShaper) + robust corridor anchoring
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>
2026-05-31 00:46:33 -06:00
e7e8bb9187 feat(core): water & lava pools (PoolDecorator) + lava lighting
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>
2026-05-31 00:41:48 -06:00
128dce6310 tune(core): make pillars an occasional special-room feature
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>
2026-05-31 00:29:14 -06:00
b1449248f8 feat(core): pillars — PillarPlacer decorator + Tile::Pillar
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>
2026-05-31 00:25:37 -06:00
d6dc354a1f polish(web): calmer stone texture + warm torchlight pooling
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>
2026-05-31 00:18:15 -06:00
a1d2a63937 feat(core): polygonal room shapes (rect/octagon/ellipse/plus)
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>
2026-05-31 00:16:45 -06:00
f849d82d31 feat(wasm+web): live browser dungeon playground (WASM core + atmospheric renderer)
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>
2026-05-31 00:07:24 -06:00
9d8d8b28f0 docs(spec): address critic findings on viz playground spec
- C1.5: derive serde on DungeonConfig + 5 sub-configs (bridge needs it)
- C2: make "shape contains bounds.center()" a hard carver invariant (not a
  size heuristic the room-size sliders could defeat for Plus rooms)
- specify workspace placement (member + default-members excludes wasm from
  host cargo test/clippy)
- determinism crosscheck compares decoded tiles, excludes gen_ms

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:48:27 -06:00
e1c0612efc docs(spec): browser WASM viz playground + organic dungeons
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>
2026-05-30 23:41:23 -06:00
c13fc18d8f feat(viz): enter a specific seed (S to type, Enter to apply)
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>
2026-05-28 22:53:03 -06:00
ab195306a5 feat(core): richer default dungeon — denser rooms + loop edges
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>
2026-05-28 22:53:03 -06:00
5f195fdb0b chore: record Task 13 + final v1 verification (all 13 tasks complete)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:34:21 -06:00
0372ce06d8 feat(viz): macroquad dungeon renderer with seed reroll
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:32:21 -06:00
3232cf3d09 chore: record Task 12 + integration stress results (core complete)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:29:14 -06:00
4e54d3b7ba feat(core): dungeon recipe + end-to-end integration tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:24:53 -06:00
93c32a652f fix(core): pierce-aware DoorPlacer with configurable door_chance
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>
2026-05-28 22:19:59 -06:00
1d003ec5fc chore: record Phase 3 results + door-topology decision (Variant A, door_chance 0.5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:14:47 -06:00
61aedfa0c7 feat(core): DoorPlacer pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:39:02 -06:00
42fda84eaa feat(core): CorridorCarver pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:36:04 -06:00
d3e389d7f8 feat(core): MstConnect pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:33:16 -06:00
d2d5fa1a8b feat(core): RoomCarver pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:30:28 -06:00
f95a562927 feat(core): BspPartition pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:27:44 -06:00
068df27c7d chore: orchestration bookkeeping through Task 6 (progress, ledger, conventions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:22:42 -06:00
e0a20f7fd1 feat(core): generation engine — GenContext, Pass, Pipeline, Blackboard
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:20:29 -06:00
8c4ef2ed4d feat(core): derive PartialEq/Eq on Grid<T> and Map
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>
2026-05-28 21:14:12 -06:00
659ef00017 feat(core): data model + Map output contract + ASCII renderer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:10:47 -06:00
6ffe0cc68c feat(core): deterministic ChaCha8 RNG with order-independent fork
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:07:43 -06:00