diff --git a/src/game/maze.ts b/src/game/maze.ts index f30b410..94ba571 100644 --- a/src/game/maze.ts +++ b/src/game/maze.ts @@ -1591,29 +1591,9 @@ function themeRoll(level: MazeLevel, pieces: readonly MazePiece[], stats: MazeSt } /** - * Compute BFS topological distance of every room from the level's entrance room - * (the room holding a 'prev' or 'entrance' piece, or `level.first`). - */ -function bfsDistances(level: MazeLevel): Map { - const start = level.rooms.find(r => r.piece?.kind === 'prev' || r.piece?.kind === 'entrance') ?? level.first - const dist = new Map([[start, 0]]) - const queue: Room[] = [start] - while (queue.length > 0) { - const current = queue.shift()! - const d = dist.get(current)! - for (const orth of current.orths) { - if (!dist.has(orth.room)) { - dist.set(orth.room, d + 1) - queue.push(orth.room) - } - } - } - return dist -} - -/** - * Place a special room group using topological dead-end / BFS distance selection - * and `LvlPrest.txt` full-room preset substitution. + * Place a special room group following standard D2 scanning rules: + * scans room candidates against valid wall connections, LvlPrest file definitions, + * and room flags, rather than imposing an artificial global maximum BFS distance. * * @param level - the level. * @param pieces - the level's pieces. @@ -1630,11 +1610,9 @@ function placeSpecialGroup( index: PieceIndex, stats: MazeStats, ): void { - const isExit = group.kind === 'next' || group.kind === 'down' || group.kind === 'quest' - const distMap = bfsDistances(level) - - // Phase 1: Try to replace an existing unfinished room in place, preferring - // maximum BFS distance from entrance for exits/objectives and minimum for entrances. + // Phase 1: Try to replace an existing unfinished room in place, scanning + // room candidates against valid wall connections, LvlPrest definitions, + // and room flags in standard D2 roster order. for (let offset = 0; offset < group.entries.length; offset += 1) { const entry = group.entries[(startCursor + offset) % group.entries.length]! const wanted = sideMaskFromToken(entry.plainSides) @@ -1642,20 +1620,14 @@ function placeSpecialGroup( const special = findSpecial(pieces, group.kind, entry.sides) if (special === undefined) continue - const candidates = level.rooms.filter(r => { + const chosen = level.rooms.find(r => { if (roomSideMask(r) !== wanted) return false if (!r.hasDs1) return true if (group.inPlaceOnly === true && (r.piece === special || r.piece?.kind === 'theme')) return true return false }) - if (candidates.length > 0) { - candidates.sort((a, b) => { - const da = distMap.get(a) ?? 0 - const db = distMap.get(b) ?? 0 - return isExit ? db - da : da - db - }) - const chosen = candidates[0]! + if (chosen !== undefined) { chosen.piece = special chosen.variant = 0 chosen.hasDs1 = true @@ -1670,16 +1642,12 @@ function placeSpecialGroup( return } - // Phase 2: Grow a new dead-end room off the best available parent room. - const parentCandidates = level.rooms.filter( - r => !r.hasDs1 || r.piece?.kind === 'theme', - ) - parentCandidates.sort((a, b) => { - if (a.hasDs1 !== b.hasDs1) return a.hasDs1 ? 1 : -1 - const da = distMap.get(a) ?? 0 - const db = distMap.get(b) ?? 0 - return isExit ? db - da : da - db - }) + // Phase 2: Grow a new dead-end room off an available parent room in D2 + // roster order (plain rooms without DS1 scanned first, followed by theme rooms). + const parentCandidates = [ + ...level.rooms.filter(r => !r.hasDs1), + ...level.rooms.filter(r => r.hasDs1 && r.piece?.kind === 'theme'), + ] for (const parent of parentCandidates) { for (let offset = 0; offset < group.entries.length; offset += 1) {