Dungeon themes: per-song visual identity (crypt/overgrown/ember/flooded/windswept)
One album must not render the same dungeon twice. dungeonTheme() scores the measured summary per theme — pace terms gate on tempoConfidence so doubled detectors never read fast, crypt is penalised for blazing masters — plus small seeded jitter so ambiguous songs vary across seeds while decided races stay decided. The plan carries the theme name; dressing mix, planting/timber/puddle densities follow it, and the facade builds all materials (stone/floor multipliers, torch + light color, wood, fog tint) from the same record. Calibrated bank spread is 4/4/4/4/1 (windswept holds runner); corners stable across seeds; plan reshaping, facade wiring, and murk-vs-clear gates covered by tests.
This commit is contained in:
@@ -60,7 +60,7 @@ look.venue.module = dungeon;
|
||||
look.venue.name = dungeon.name;
|
||||
look.venue.archetype = dungeon.archetype;
|
||||
const mapSeed = mapSeedParam !== null ? Number(mapseedParam) : (look.venue.seed ?? 7);
|
||||
const map = genDungeon({ seed: mapSeed });
|
||||
const map = genDungeon({ seed: mapSeed, summary: track.summary });
|
||||
look.venue.dungeon = { map };
|
||||
|
||||
// ---------------------------------------------------------- 2D map
|
||||
@@ -69,6 +69,9 @@ const mc = document.getElementById('map');
|
||||
const mg = mc.getContext('2d');
|
||||
const S = mc.width / map.w;
|
||||
document.getElementById('maptitle').textContent = `${songName} · map seed ${mapSeed} · ${map.rooms.length} rooms · route ${map.route.totalLen.toFixed(0)}u`;
|
||||
const ch = map.character || {};
|
||||
const chLine = `<div class="kv"><span>character</span><b>seal ${(ch.indoorBias ?? 0).toFixed(2)} · murk ${(ch.murk ?? 0).toFixed(2)} · size ×${(ch.sizeMul ?? 1).toFixed(2)}</b></div>` +
|
||||
`<div class="kv"><span>theme</span><b>${map.theme ?? 'crypt'}</b></div>`;
|
||||
for (let y = 0; y < map.h; y++) {
|
||||
for (let x = 0; x < map.w; x++) {
|
||||
if (!map.grid[y * map.w + x]) { mg.fillStyle = '#07090d'; }
|
||||
@@ -109,6 +112,9 @@ for (const l of map.items.loot || []) {
|
||||
mg.strokeStyle = '#fde047'; mg.lineWidth = 1; mg.beginPath();
|
||||
mg.arc((l.x / 2 + map.w / 2) * S, (l.z / 2 + map.h / 2) * S, 5, 0, 7); mg.stroke();
|
||||
}
|
||||
for (const t of map.items.timbers || []) {
|
||||
dot(t.x, t.z, '#92400e', 2);
|
||||
}
|
||||
if (map.portal) {
|
||||
dot(map.portal.x, map.portal.z, '#f0abfc', 5);
|
||||
mg.strokeStyle = '#f0abfc'; mg.lineWidth = 1.5; mg.beginPath();
|
||||
@@ -123,7 +129,7 @@ mg.fillStyle = '#fff';
|
||||
map.rooms.forEach((rm) => {
|
||||
mg.fillText(String(rm.id), (rm.x + 0.3) * S, (rm.y + 1) * S);
|
||||
});
|
||||
document.getElementById('mapmeta').innerHTML =
|
||||
document.getElementById('mapmeta').innerHTML = chLine +
|
||||
map.rooms.map((rm) =>
|
||||
`<div class="kv"><span>room ${rm.id} · ${rm.zone}${rm.indoor ? '' : ' · open'}</span><b>${rm.w}×${rm.h}${map.watch[rm.id] ? ' · ⌂' : ''}${map.portal && map.portal.roomId === rm.id ? ' · ◉' : ''}</b></div>`).join('') +
|
||||
`<div class="legend">
|
||||
@@ -135,7 +141,7 @@ document.getElementById('mapmeta').innerHTML =
|
||||
<i style="background:#166534"></i>veg · <i style="background:#6b7280"></i>boulder ·
|
||||
<i style="background:#f87171"></i>lurker · <i style="background:#fb923c"></i>sentry ·
|
||||
<i style="background:#facc15"></i>critter · <i style="background:#a5f3fc"></i>wisp ·
|
||||
<i style="background:#fde047"></i>loot (halt)</div></div>`;
|
||||
<i style="background:#fde047"></i>loot (halt) · <i style="background:#92400e"></i>timber</div></div>`;
|
||||
|
||||
// ---------------------------------------------------------- 3D stills
|
||||
// Fake viz feed so windows/portal/puddles show their IMAGE (the compositor
|
||||
|
||||
Reference in New Issue
Block a user