Compare commits

...

2 commits

Author SHA1 Message Date
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
212 changed files with 128 additions and 18 deletions

View file

@ -53,6 +53,59 @@ export function roomVantage(env, region, opts = {}) {
return mostOpenCell(distanceToWall(tiles, env.width, env.height), env.width, region.cells);
}
// Per-(room, facing) camera cell, chosen so the faced geometry reads
// UNAMBIGUOUSLY in the depth map. The room-wide most-open vantage stands far
// from everything: the faced wall lands near max-view (≈ black), and the
// diffusion model decorates that ambiguity with hallucinated doors — while a
// real opening renders off-center because the camera isn't aligned with the
// gap. Instead:
// open ahead → stand in a cell whose straight-ahead ray ESCAPES the room
// through the gap, a comfortable 27 cells back: the passage
// you'd walk through is centered and clearly deep.
// wall ahead → stand ~3 cells from the wall: it renders mid-bright and
// solid, leaving the model nothing to invent.
export function directedVantage(env, region, dirName, opts = {}) {
const { tiles } = getStage(env, opts.stage);
const w = env.width, h = env.height;
const dist = distanceToWall(tiles, w, h);
const [dx, dy] = DIRS[dirName];
const cellSet = new Set(region.cells.map(([x, y]) => x + ',' + y));
const [cx, cy] = centroidOf(region.cells);
let best = null, bestScore = Infinity, anyEscape = false;
const cand = [];
for (const [x, y] of region.cells) {
let px = x, py = y, run = 0, escaped = false;
for (let s = 0; s < 24; s++) {
px += dx; py += dy;
if (px < 0 || py < 0 || px >= w || py >= h) break;
if (blocks(tiles[py][px])) break;
run++;
if (!cellSet.has(px + ',' + py)) escaped = true; // ray left the room through a gap
}
if (escaped) anyEscape = true;
cand.push({ x, y, run, escaped });
}
for (const c of cand) {
if (anyEscape && !c.escaped) continue; // when a gap is sightable, only gap-aligned cells qualify
const clearance = dist[c.y * w + c.x];
// Sweet bands: gaps read best 27 cells out; a blank wall at ~2.
const band = anyEscape
? (c.run < 2 ? (2 - c.run) * 6 : c.run > 7 ? (c.run - 7) * 2 : 0)
: Math.abs(c.run - 2) * 3; // closed: stand close — a bright dominant slab leaves no room for phantom doors
const hug = clearance <= 1 ? 5 : 0; // don't press against a side wall
// Open: center on the gap. Closed: deliberately stand OFF the room's
// perpendicular axis — a symmetric stage begs the model to paint a
// centered focal door; an off-axis dead-end corner doesn't.
const perp = dirName === 'N' || dirName === 'S' ? c.x - cx : c.y - cy;
const cen = ((c.x - cx) ** 2 + (c.y - cy) ** 2) * 0.05;
const offAxis = anyEscape ? 0 : (Math.abs(perp) < 1.5 ? 4 : 0);
const score = band + hug + (anyEscape ? cen : offAxis + cen * 0.4);
if (score < bestScore) { bestScore = score; best = [c.x, c.y]; }
}
return best ?? mostOpenCell(dist, w, region.cells);
}
// Default demo vantage: the most-open cell of the largest room (or, failing that,
// the most open floor cell anywhere).
export function vantage(env, opts = {}) {

View file

@ -5,7 +5,7 @@
import init, { generate, default_config_json } from './pkg/reikhelm_wasm.js';
import { renderMap, getStage, THEMES } from './render.js';
import { downloadDepth, exportDepthDataURL, exportDepthToServer } from './depth.js';
import { exportFPVDataURL, exportFPVToServer, vantage } from './fpv.js';
import { exportFPVDataURL, exportFPVToServer, vantage, directedVantage } from './fpv.js';
import { dungeonManifest } from './explore.js';
const canvas = document.getElementById('view');
@ -241,7 +241,10 @@ window.reikhelm = {
const sd = state.env.seed;
for (const r of m.rooms) {
for (const dir of ['N', 'E', 'S', 'W']) {
await exportFPVToServer(state.env, `atlas/${sd}/_work/r${r.id}_${dir}_depth.png`, { at: r.vantage, dir });
// Per-direction camera: gap-aligned when the way is open, near-wall
// when it's closed — so the depth map can't be misread by the model.
const at = directedVantage(state.env, state.env.regions[r.id], dir);
await exportFPVToServer(state.env, `atlas/${sd}/_work/r${r.id}_${dir}_depth.png`, { at, dir });
}
}
await this.saveJSON(`atlas/${sd}/manifest.json`, m);

View file

@ -56,7 +56,11 @@ DEFAULT_BODY = THEME_BODY["stone"]
# These reinforce the depth (a deep recess vs a near wall straight ahead) so the
# model paints a passage only where one actually exists — killing phantom doors.
AHEAD_OPEN = "a dark arched passage leads onward into shadow ahead"
AHEAD_WALL = "a solid carved stone wall closes the way ahead"
# cfg 1.0 makes negatives inert, so the closed case is carried by emphatic
# positive description — themes like the vault say "locked", which otherwise
# summons phantom doors on blank walls.
AHEAD_WALL = ("the way ahead ends at a blank unbroken wall of carved stone blocks, "
"sealed solid masonry with no door and no opening, a dead end")
NEGATIVE = ("person, people, human, figure, character, hands, fingers, hand, arm, arms, "
"holding weapon, sword, feet, legs, body, silhouette, UI, HUD, text, watermark, modern")
@ -124,13 +128,18 @@ def main():
theme = room.get("theme")
is_open = bool(room.get("open", {}).get(d, False))
prompt = prompt_for(theme, is_open)
strength = a.strength
# Walls render under their own FIXED seed: the open-frame seed's
# favorite composition is a centered doorway — exactly what a closed
# wall must not grow. One class, one seed → style stays coherent.
render_seed = a.render_seed if is_open else a.render_seed + 13
prefix = f"r{rid}_{d}_{RUN}" # per-run unique; avoids ComfyUI prefix-counter collisions on re-bake
frame = os.path.join(atlas, f"r{rid}_{d}.png")
tag = f"[{i}/{len(depths)}] r{rid} {d} {theme or '?'}{'·open' if is_open else '·wall'}"
if not run(["python3", COMFY, "zrender", a.url, "--depth", depth,
"--width", str(a.width), "--height", str(a.height),
"--strength", str(a.strength), "--seed", str(a.render_seed),
"--strength", str(strength), "--seed", str(render_seed),
"--prompt", prompt, "--negative", NEGATIVE,
"--prefix", prefix, "--wait", "300"]):
print(f" {tag} RENDER FAILED"); continue
@ -141,7 +150,8 @@ def main():
render = renders[0]
if not run([VENV_PY, PIXELATE, render, "--palette", PALETTE,
"--size", a.size, "--preview-scale", "0", "--out", frame]):
"--size", a.size, "--preview-scale", "0", "--overshoot", "6",
"--out", frame]):
print(f" {tag} DITHER FAILED"); continue
os.remove(render) # keep the depth, drop the big intermediate render

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Some files were not shown because too many files have changed in this diff Show more