diablo2-web/tests/maze-special-passes.test.ts

848 lines
34 KiB
TypeScript

/**
* Unit tests for the 11 Diablo II DRLG Maze Special Passes.
*
* Covers:
* 1. DRLGMAZE_PlaceAct2TombStuff (Tal Rasha True/False Tombs, Kaa, Cube, Leatherarm, Waypoint)
* 2. DRLGMAZE_PlaceAct2LairStuff (Maggot Lair Coldworm Tight Spot & Staff of Kings Treasure)
* 3. DRLGMAZE_PlaceAct3DungeonStuff (Flayer Dungeon Brain chest, Kurast Temples Lam Esen's Tome)
* 4. DRLGMAZE_PlaceAct3SewerStuff (Kurast Sewers Drain lever descent & Chest)
* 5. DRLGMAZE_PlaceAct3MephistoStuff (Durance of Hate 3 Mephisto Complex, Durance 2 Waypoint)
* 6. DRLGMAZE_PlaceAct5TempleStuff (Halls of Vaught Nihlathak Final Room, Halls of Pain Waypoint)
* 7. DRLGMAZE_PlaceAct5BaalStuff (Throne of Destruction Baal Entrance/ThroneRoom, Worldstone Waypoint)
* 8. DRLGMAZE_PlaceAct5IceStuff (Frozen River Anya platform, Drifter Cavern / Icy Cellar Pools, Waypoints)
* 9. DRLGMAZE_PlaceAct1Barracks (Monastery Barracks Smith Forge & Court Connect)
* 10. DRLGMAZE_PlaceAct4Lava (River of Flame Hellforge, Bridges 1 & 2, Warp, Lava X fillers)
* 11. DRLGMAZE_ScanReplaceSpecialAct2SewersPresets (Lut Gholein Sewers Radament's Lair, Dual Entrances, Waypoint)
* 12. Full generateMaze integration tests verifying specialPassesApplied and the
* per-level-type `unimplementedPasses` gap list.
*/
import { describe, expect, test } from 'vitest'
import type { Ds1, Ds1Cell, Ds1Object } from '../src/formats/ds1.ts'
import {
generateMaze,
classifyMazePieceName,
MAZE_LEVEL_TYPE_PROFILES,
UNIMPLEMENTED_PASSES,
unimplementedPassesFor,
type MazePiece,
type MazePieceKind,
type MazeRequest,
} from '../src/game/maze.ts'
import {
runMazeSpecialPasses,
type SpecialPassName,
} from '../src/game/maze-special-passes.ts'
/**
* Construct a synthetic minimal Ds1 map.
*/
function mockDs1(width = 10, height = 10, objects: Ds1Object[] = []): Ds1 {
const cells: Ds1Cell[][] = []
for (let y = 0; y < height; y++) {
const row: Ds1Cell[] = []
for (let x = 0; x < width; x++) {
row.push({
walls: [{ prop1: 1, sequence: 0, style: 0, type: 0, unknown1: 0, unknown2: 0, hidden: false }],
floors: [{ prop1: 1, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false }],
shadows: [{ prop1: 0, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false }],
substitutions: [],
})
}
cells.push(row)
}
return {
version: 18,
width,
height,
act: 1,
substitutionType: 0,
wallLayers: 1,
floorLayers: 1,
cells,
objects,
npcPathOffset: null,
}
}
/**
* Construct a synthetic MazePiece.
*/
function mockPiece(
name: string,
kind: MazePieceKind,
sides = '',
objects: Ds1Object[] = [],
width = 10,
height = 10,
): MazePiece {
return {
name,
kind,
sides,
levels: [mockDs1(width, height, objects)],
}
}
/**
* Create a full standard piece set for a level type so maze generation has
* all 15 basic room shapes plus entrance and common directions.
*/
function createStandardPieceSet(levelTypeName: string, extraPieces: MazePiece[] = []): MazePiece[] {
const SIDES = [
'N', 'S', 'E', 'W',
'NE', 'NW', 'SE', 'SW',
'NS', 'EW',
'NSE', 'NSW', 'NEW', 'SEW',
'NSEW',
]
const pieces: MazePiece[] = [
mockPiece(`${levelTypeName} Entrance`, 'entrance', ''),
...SIDES.map(s => mockPiece(`${levelTypeName} ${s}`, 'room', s)),
...['N', 'S', 'E', 'W'].map(s => mockPiece(`${levelTypeName} Prev ${s}`, 'prev', s, [{ type: 2, id: 1, x: 25, y: 25, flags: 0 }])),
...['N', 'S', 'E', 'W'].map(s => mockPiece(`${levelTypeName} Next ${s}`, 'next', s, [{ type: 2, id: 2, x: 25, y: 25, flags: 0 }])),
...['N', 'S', 'E', 'W'].map(s => mockPiece(`${levelTypeName} Down ${s}`, 'down', s, [{ type: 2, id: 3, x: 25, y: 25, flags: 0 }])),
...['N', 'S', 'E', 'W'].map(s => mockPiece(`${levelTypeName} Theme ${s}`, 'theme', s)),
...extraPieces,
]
return pieces
}
describe('Diablo II DRLG Maze Special Passes', () => {
test('UNIMPLEMENTED_PASSES is empty when all maze profiles define specials', () => {
const emptySpecials = MAZE_LEVEL_TYPE_PROFILES
.filter(profile => profile.specials.length === 0 && profile.singleRoom === undefined && profile.specialPass === undefined)
.map(profile => profile.levelTypeName)
expect(emptySpecials).toEqual([])
expect(UNIMPLEMENTED_PASSES).toEqual([])
})
test('unimplementedPassesFor is empty for a fully transcribed level type', () => {
expect(unimplementedPassesFor('Act 1 - Cave')).toEqual([])
expect(unimplementedPassesFor('Act 2 - Tomb')).toEqual([])
expect(unimplementedPassesFor('Act 2 - Harem')).toEqual([])
expect(unimplementedPassesFor('Act 2 - Basement')).toEqual([])
expect(unimplementedPassesFor('Act 3 - Spider')).toEqual([])
expect(unimplementedPassesFor('Act 5 - Lava')).toEqual([])
})
test('unimplementedPassesFor reports a level type that has no profile at all', () => {
expect(unimplementedPassesFor('Act 9 - Nowhere'))
.toEqual(['DRLGMAZE_GenerateLevel profile (Act 9 - Nowhere)'])
})
test('generateMaze reports empty unimplementedPasses for transcribed levels', () => {
const spider = generateMaze({
levelId: 84,
levelName: 'Spider Cavern',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 8484,
pieces: createStandardPieceSet('Act 3 - Spider'),
})
expect(spider.stats.unimplementedPasses).toEqual([])
// The other two diagnostic channels must travel with it.
expect(Array.isArray(spider.stats.unresolvedRoles)).toBe(true)
expect(Array.isArray(spider.stats.notes)).toBe(true)
const cave = generateMaze({
levelId: 8,
levelName: 'Den of Evil',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 8484,
pieces: createStandardPieceSet('Act 1 - Cave'),
})
expect(cave.stats.unimplementedPasses).toEqual([])
})
/* ----------------------------------------------------------------------- *
* 1. Act 2 - Tomb
* ----------------------------------------------------------------------- */
describe('Pass 1: DRLGMAZE_PlaceAct2TombStuff', () => {
const tombExtraPieces = [
mockPiece('Act 2 - Tomb Talrasha N', 'quest', 'N', [{ type: 2, id: 152, x: 25, y: 25, flags: 0 }]), // Horadric Staff Orifice
mockPiece('Act 2 - Tomb Chest N', 'treasure', 'N', [{ type: 2, id: 397, x: 25, y: 25, flags: 0 }]), // Golden chest
mockPiece('Act 2 - Tomb Kaa N', 'quest', 'N', [{ type: 1, id: 250, x: 25, y: 25, flags: 0 }]), // Ancient Kaa
mockPiece('Act 2 - Tomb Cube N', 'treasure', 'N', [{ type: 2, id: 354, x: 25, y: 25, flags: 0 }]), // Horadric Cube
mockPiece('Act 2 - Tomb Leatherarm N', 'quest', 'N'),
mockPiece('Act 2 - Tomb Treasure N', 'treasure', 'N'),
mockPiece('Act 2 - Tomb Waypoint N', 'waypoint', 'N', [{ type: 2, id: 156, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 2 - Tomb Prev NSW', 'prev', 'NSW'),
mockPiece('Act 2 - Tomb Prev NEW', 'prev', 'NEW'),
mockPiece('Act 2 - Tomb Prev NSE', 'prev', 'NSE'),
mockPiece('Act 2 - Tomb Prev SEW', 'prev', 'SEW'),
]
test('True Tomb (staffTombLevelId): places Talrasha Horadric Staff orifice', () => {
const pieces = createStandardPieceSet('Act 2 - Tomb', tombExtraPieces)
const res = generateMaze({
levelId: 66,
levelName: 'Tal Rashas Tomb 1',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 12345,
pieces,
staffTombLevelId: 66,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2TombStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
const hasTalrasha = specials.some(s => s.kind === 'quest')
expect(hasTalrasha).toBe(true)
// Level size is tripled for staff tomb
expect(res.stats.targetRooms).toBe(15)
})
test('False Tombs (67..72): places golden chest room', () => {
const pieces = createStandardPieceSet('Act 2 - Tomb', tombExtraPieces)
const res = generateMaze({
levelId: 67,
levelName: 'Tal Rashas Tomb 2',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 54321,
pieces,
staffTombLevelId: 66,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2TombStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'treasure')).toBe(true)
})
test('Halls of the Dead 2 (level 59): places Waypoint', () => {
const pieces = createStandardPieceSet('Act 2 - Tomb', tombExtraPieces)
const res = generateMaze({
levelId: 59,
levelName: 'Halls of the Dead 2',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 99999,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2TombStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
test('Halls of the Dead 3 (level 60): places Horadric Cube chest', () => {
const pieces = createStandardPieceSet('Act 2 - Tomb', tombExtraPieces)
const res = generateMaze({
levelId: 60,
levelName: 'Halls of the Dead 3',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 88888,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2TombStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest' || s.kind === 'treasure')).toBe(true)
})
test('Boss Tomb: places Ancient Kaa the Soulless', () => {
const pieces = createStandardPieceSet('Act 2 - Tomb', tombExtraPieces)
const res = generateMaze({
levelId: 68,
levelName: 'Tal Rashas Tomb 3',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 77777,
pieces,
staffTombLevelId: 66,
bossTombLevelId: 68,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2TombStuff')
expect(res.stats.targetRooms).toBe(10) // Doubled for boss tomb
})
})
/* ----------------------------------------------------------------------- *
* 2. Act 2 - Lair
* ----------------------------------------------------------------------- */
describe('Pass 2: DRLGMAZE_PlaceAct2LairStuff', () => {
const lairExtraPieces = [
mockPiece('Act 2 - Lair Tight Spot S', 'quest', 'S', [{ type: 1, id: 284, x: 25, y: 25, flags: 0 }]), // Coldworm
mockPiece('Act 2 - Lair Treasure W', 'treasure', 'W', [{ type: 2, id: 356, x: 25, y: 25, flags: 0 }]), // Staff of Kings chest
]
test('Maggot Lair 3 (level 64): places Coldworm Tight Spot and Staff of Kings chest', () => {
const pieces = createStandardPieceSet('Act 2 - Lair', lairExtraPieces)
const res = generateMaze({
levelId: 64,
levelName: 'Maggot Lair 3',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 42,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2LairStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(specials.some(s => s.kind === 'treasure')).toBe(true)
expect(res.level.objects.some(o => o.type === 1 && o.id === 284)).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 356)).toBe(true)
})
test('Maggot Lair 1 (level 62): places regular next and prev', () => {
const pieces = createStandardPieceSet('Act 2 - Lair', lairExtraPieces)
const res = generateMaze({
levelId: 62,
levelName: 'Maggot Lair 1',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 101,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct2LairStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'next')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 3. Act 3 - Dungeon
* ----------------------------------------------------------------------- */
describe('Pass 3: DRLGMAZE_PlaceAct3DungeonStuff', () => {
const dungeonExtraPieces = [
mockPiece('Act 3 - Dungeon Treasure 2', 'treasure', '', [{ type: 2, id: 406, x: 25, y: 25, flags: 0 }]), // Khalim's Brain chest
mockPiece('Act 3 - Dungeon Treasure 1', 'treasure', ''),
mockPiece('Act 3 - Temple 1', 'quest', '', [{ type: 2, id: 193, x: 25, y: 25, flags: 0 }]), // Lam Esen's Tome altar
]
test("Flayer Dungeon 3 (level 91): places Khalim's Brain chest (Treasure 2)", () => {
const pieces = createStandardPieceSet('Act 3 - Dungeon', dungeonExtraPieces)
const res = generateMaze({
levelId: 91,
levelName: 'Flayer Dungeon 3',
levelTypeName: 'Act 3 - Dungeon',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 31415,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct3DungeonStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'treasure')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 406)).toBe(true)
})
test("Ruined Temple (level 94): places Lam Esen's Tome altar (Temple 1)", () => {
const pieces = createStandardPieceSet('Act 3 - Dungeon', dungeonExtraPieces)
const res = generateMaze({
levelId: 94,
levelName: 'Ruined Temple',
levelTypeName: 'Act 3 - Dungeon',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 27182,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct3DungeonStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 193)).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 4. Act 3 - Sewer
* ----------------------------------------------------------------------- */
describe('Pass 4: DRLGMAZE_PlaceAct3SewerStuff', () => {
const sewerExtraPieces = [
mockPiece('Act 3 - Sewer Drain N', 'next', 'N', [{ type: 2, id: 366, x: 25, y: 25, flags: 0 }]), // Drain lever to Level 2
mockPiece('Act 3 - Sewer Chest S', 'treasure', 'S'),
mockPiece('Act 3 - Sewer Treasure X', 'treasure', ''),
mockPiece('Act 3 - Sewer NW', 'room', 'NW'),
mockPiece('Act 3 - Sewer NE', 'room', 'NE'),
mockPiece('Act 3 - Sewer SW', 'room', 'SW'),
mockPiece('Act 3 - Sewer SE', 'room', 'SE'),
mockPiece('Act 3 - Sewer Prev NW', 'prev', 'NW'),
mockPiece('Act 3 - Sewer Prev NE', 'prev', 'NE'),
mockPiece('Act 3 - Sewer Prev SW', 'prev', 'SW'),
mockPiece('Act 3 - Sewer Prev SE', 'prev', 'SE'),
]
test('Kurast Sewers 1 (level 92): places Drain lever and Chest', () => {
const pieces = createStandardPieceSet('Act 3 - Sewer', sewerExtraPieces)
const res = generateMaze({
levelId: 92,
levelName: 'Kurast Sewers 1',
sectionSize: 10,
minRooms: 18,
merge: 500,
seed: 777,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct3SewerStuff')
expect(res.level.objects.some(o => o.type === 2 && o.id === 366)).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 5. Act 3 - Mephisto
* ----------------------------------------------------------------------- */
describe('Pass 5: DRLGMAZE_PlaceAct3MephistoStuff', () => {
const mephistoExtraPieces = [
mockPiece('Act 3 - Mephisto Complex', 'quest', '', [{ type: 2, id: 251, x: 50, y: 50, flags: 0 }]), // Red portal to Act 4
mockPiece('Act 3 - Mephisto Waypoint N', 'waypoint', 'N', [{ type: 2, id: 237, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 3 - Mephisto Prev N', 'prev', 'N'),
mockPiece('Act 3 - Mephisto Next N', 'next', 'N'),
]
test('Durance of Hate 3 (level 102): places Mephisto Complex', () => {
const pieces = createStandardPieceSet('Act 3 - Kurast', mephistoExtraPieces)
const res = generateMaze({
levelId: 102,
levelName: 'Durance of Hate 3',
levelTypeName: 'Act 3 - Kurast',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 666,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct3MephistoStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 251)).toBe(true)
})
test('Durance of Hate 2 (level 101): places Waypoint', () => {
const pieces = createStandardPieceSet('Act 3 - Kurast', mephistoExtraPieces)
const res = generateMaze({
levelId: 101,
levelName: 'Durance of Hate 2',
levelTypeName: 'Act 3 - Kurast',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 10101,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct3MephistoStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 6. Act 5 - Temple
* ----------------------------------------------------------------------- */
describe('Pass 6: DRLGMAZE_PlaceAct5TempleStuff', () => {
const templeExtraPieces = [
mockPiece('Act 5 - Temple Final Room', 'quest', '', [{ type: 1, id: 526, x: 25, y: 25, flags: 0 }]), // Nihlathak
mockPiece('Act 5 - Temple NE Waypoint', 'waypoint', 'NE'),
mockPiece('Act 5 - Temple NW Waypoint', 'waypoint', 'NW'),
mockPiece('Act 5 - Temple SW Waypoint', 'waypoint', 'SW'),
mockPiece('Act 5 - Temple SE Waypoint', 'waypoint', 'SE'),
mockPiece('Act 5 - Temple NE Down', 'down', 'NE'),
mockPiece('Act 5 - Temple NW Down', 'down', 'NW'),
mockPiece('Act 5 - Temple SW Down', 'down', 'SW'),
mockPiece('Act 5 - Temple SE up', 'prev', 'SE'),
mockPiece('Act 5 - Temple SE', 'room', 'SE'),
mockPiece('Act 5 - Temple NE', 'room', 'NE'),
mockPiece('Act 5 - Temple NW', 'room', 'NW'),
mockPiece('Act 5 - Temple SW', 'room', 'SW'),
]
test('Halls of Vaught (level 124): places Nihlathak Final Room altar', () => {
const pieces = createStandardPieceSet('Act 5 - Temple', templeExtraPieces)
const res = generateMaze({
levelId: 124,
levelName: 'Halls of Vaught',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 124124,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct5TempleStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 1 && o.id === 526)).toBe(true)
})
test('Halls of Pain (level 123): places Waypoint and stairs', () => {
const pieces = createStandardPieceSet('Act 5 - Temple', templeExtraPieces)
const res = generateMaze({
levelId: 123,
levelName: 'Halls of Pain',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 123123,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct5TempleStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
test('Halls of Pain (level 123): generates all 4 waypoint directions including NE across seeds', () => {
const pieces = createStandardPieceSet('Act 5 - Temple', templeExtraPieces)
const observedDirs = new Set<string>()
for (let seed = 1; seed <= 50; seed++) {
const res = generateMaze({
levelId: 123,
levelName: 'Halls of Pain',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed,
pieces,
})
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
const wp = specials.find(s => s.kind === 'waypoint')
if (wp !== undefined) {
observedDirs.add(wp.sides)
}
}
expect(observedDirs).toContain('NE')
expect(observedDirs).toContain('NW')
expect(observedDirs).toContain('SW')
expect(observedDirs).toContain('SE')
})
})
/* ----------------------------------------------------------------------- *
* 7. Act 5 - Baal
* ----------------------------------------------------------------------- */
describe('Pass 7: DRLGMAZE_PlaceAct5BaalStuff', () => {
const baalExtraPieces = [
mockPiece('Act 5 - ThroneRoom', 'quest', '', [{ type: 2, id: 563, x: 25, y: 25, flags: 0 }]), // Baal Throne
mockPiece('Act 5 - Baal Waypoint N', 'waypoint', 'N'),
mockPiece('Act 5 - Baal Prev NSW', 'prev', 'NSW'),
mockPiece('Act 5 - Baal Prev NEW', 'prev', 'NEW'),
mockPiece('Act 5 - Baal Prev NSE', 'prev', 'NSE'),
mockPiece('Act 5 - Baal Prev SEW', 'prev', 'SEW'),
]
test('Throne of Destruction (level 131): places ThroneRoom', () => {
const pieces = createStandardPieceSet('Act 5 - Baal', baalExtraPieces)
const res = generateMaze({
levelId: 131,
levelName: 'Throne of Destruction',
levelTypeName: 'Act 5 - Baal',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 131131,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct5BaalStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 563)).toBe(true)
})
test('Worldstone Keep 2 (level 129): places Waypoint', () => {
const pieces = createStandardPieceSet('Act 5 - Baal', baalExtraPieces)
const res = generateMaze({
levelId: 129,
levelName: 'Worldstone Keep 2',
levelTypeName: 'Act 5 - Baal',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 129129,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct5BaalStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 8. Act 5 - Ice
* ----------------------------------------------------------------------- */
describe('Pass 8: DRLGMAZE_PlaceAct5IceStuff', () => {
const iceExtraPieces = [
mockPiece('Act 5 - Ice River A', 'quest', '', [{ type: 2, id: 460, x: 25, y: 25, flags: 0 }]), // Frozen Anya
mockPiece('Act 5 - Ice River B', 'quest', '', [{ type: 2, id: 460, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 5 - Ice Pool A', 'quest', ''),
mockPiece('Act 5 - Ice Pool B', 'quest', ''),
mockPiece('Act 5 - Ice waypoint N', 'waypoint', 'N'),
]
test('Frozen River (level 114): places Anya platform', () => {
const pieces = createStandardPieceSet('Act 5 - Ice Caves', iceExtraPieces)
const res = generateMaze({
levelId: 114,
levelName: 'Frozen River',
levelTypeName: 'Act 5 - Ice Caves',
sectionSize: 10,
minRooms: 1, // Single-room preset in profile
merge: 500,
seed: 114114,
pieces,
})
// Level 114 resolves single room preset Act 5 - Ice River A
expect(res.level.objects.some(o => o.type === 2 && o.id === 460)).toBe(true)
})
test('Glacial Trail (level 115): places Waypoint', () => {
const pieces = createStandardPieceSet('Act 5 - Ice Caves', iceExtraPieces)
const res = generateMaze({
levelId: 115,
levelName: 'Glacial Trail',
levelTypeName: 'Act 5 - Ice Caves',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 115115,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct5IceStuff')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 9. Act 1 - Barracks
* ----------------------------------------------------------------------- */
describe('Pass 9: DRLGMAZE_PlaceAct1Barracks', () => {
const barracksExtraPieces = [
mockPiece('Act 1 - Barracks Forge N', 'quest', 'N', [{ type: 2, id: 108, x: 25, y: 25, flags: 0 }]), // Charsi's Malus forge
mockPiece('Act 1 - Barracks Forge S', 'quest', 'S', [{ type: 2, id: 108, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 1 - Barracks Forge E', 'quest', 'E', [{ type: 2, id: 108, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 1 - Barracks Forge W', 'quest', 'W', [{ type: 2, id: 108, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 1 - Barracks Court Connect', 'prev', '', [{ type: 2, id: 1, x: 10, y: 10, flags: 0 }]), // Entrance from Cloister
]
test('Monastery Barracks (level 28): places Court Connect and Smith Forge', () => {
const pieces = createStandardPieceSet('Act 1 - Barracks', barracksExtraPieces)
const res = generateMaze({
levelId: 28,
levelName: 'Barracks',
sectionSize: 10,
sectionHeight: 14,
minRooms: 6,
merge: 500,
seed: 2828,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct1Barracks')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(specials.some(s => s.kind === 'prev')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 108)).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 10. Act 4 - Lava
* ----------------------------------------------------------------------- */
describe('Pass 10: DRLGMAZE_PlaceAct4Lava', () => {
const lavaExtraPieces = [
mockPiece('Act 4 - Lava Warp N', 'prev', 'N', [{ type: 2, id: 255, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 4 - Bridge 1', 'next', '', [{ type: 2, id: 10, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 4 - Bridge 2', 'next', '', [{ type: 2, id: 11, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 4 - Lava Forge W', 'quest', 'W', [{ type: 2, id: 376, x: 25, y: 25, flags: 0 }]), // Hellforge
mockPiece('Act 4 - Lava Forge E', 'quest', 'E', [{ type: 2, id: 376, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 4 - Lava X', 'room', ''), // Filler
]
test('River of Flame (level 107): places Hellforge, Bridges, Warp and Lava X filler', () => {
const pieces = createStandardPieceSet('Act 4 - Lava', lavaExtraPieces)
const res = generateMaze({
levelId: 107,
levelName: 'River of Flame',
levelTypeName: 'Act 4 - Lava',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 107107,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_PlaceAct4Lava')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 2 && o.id === 376)).toBe(true)
// Exactly 1 Warp N (never duplicate)
expect(specials.filter(s => s.kind === 'prev').length).toBe(1)
// 3 Bridge rooms: 1 of Bridge 1, 2 of Bridge 2
expect(res.stats.notes).toContain('piece "Act 4 - Bridge 1" used 1 time(s)')
expect(res.stats.notes).toContain('piece "Act 4 - Bridge 2" used 2 time(s)')
// Confirm Lava X filler spaces were placed
expect(specials.some(s => s.sides === 'filler')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* 11. Act 2 - Sewers
* ----------------------------------------------------------------------- */
describe('Pass 11: DRLGMAZE_ScanReplaceSpecialAct2SewersPresets', () => {
const sewer2ExtraPieces = [
mockPiece("Act 2 - Sewer Radament's Lair N", 'quest', 'N', [{ type: 1, id: 229, x: 25, y: 25, flags: 0 }]), // Radament
mockPiece("Act 2 - Sewer Radament's Lair S", 'quest', 'S', [{ type: 1, id: 229, x: 25, y: 25, flags: 0 }]),
mockPiece("Act 2 - Sewer Radament's Lair E", 'quest', 'E', [{ type: 1, id: 229, x: 25, y: 25, flags: 0 }]),
mockPiece("Act 2 - Sewer Radament's Lair W", 'quest', 'W', [{ type: 1, id: 229, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 2 - Sewer Prev E', 'prev', 'E'), // City entrance
mockPiece('Act 2 - Sewer Prev NS', 'prev', 'NS'), // Dock entrance
mockPiece('Act 2 - Sewer Waypoint N', 'waypoint', 'N', [{ type: 2, id: 156, x: 25, y: 25, flags: 0 }]),
mockPiece('Act 2 - Sewer Chest N', 'treasure', 'N', [{ type: 2, id: 355, x: 25, y: 25, flags: 0 }]),
]
test("Sewers Level 3 (level 49): places Radament's Lair", () => {
const pieces = createStandardPieceSet('Act 2 - Sewer', sewer2ExtraPieces)
const res = generateMaze({
levelId: 49,
levelName: 'Sewers Level 3',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 4949,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_ScanReplaceSpecialAct2SewersPresets')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'quest')).toBe(true)
expect(res.level.objects.some(o => o.type === 1 && o.id === 229)).toBe(true)
})
test('Sewers Level 1 (level 47): places dock and city entrances', () => {
const pieces = createStandardPieceSet('Act 2 - Sewer', sewer2ExtraPieces)
const res = generateMaze({
levelId: 47,
levelName: 'Sewers Level 1',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 4747,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_ScanReplaceSpecialAct2SewersPresets')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
const prevs = specials.filter(s => s.kind === 'prev')
expect(prevs.length).toBeGreaterThanOrEqual(1)
})
test('Sewers Level 2 (level 48): places Waypoint', () => {
const pieces = createStandardPieceSet('Act 2 - Sewer', sewer2ExtraPieces)
const res = generateMaze({
levelId: 48,
levelName: 'Sewers Level 2',
sectionSize: 10,
minRooms: 6,
merge: 500,
seed: 4848,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_ScanReplaceSpecialAct2SewersPresets')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'waypoint')).toBe(true)
})
test('Ancient Tunnels (level 50): places golden chest', () => {
const pieces = createStandardPieceSet('Act 2 - Sewer', sewer2ExtraPieces)
const res = generateMaze({
levelId: 50,
levelName: 'Ancient Tunnels',
sectionSize: 10,
minRooms: 5,
merge: 500,
seed: 5050,
pieces,
})
expect(res.stats.specialPassesApplied).toContain('DRLGMAZE_ScanReplaceSpecialAct2SewersPresets')
const specials = res.stats.specialsApplied as { room: number; kind: string; sides: string }[]
expect(specials.some(s => s.kind === 'treasure')).toBe(true)
})
})
/* ----------------------------------------------------------------------- *
* Classification tests
* ----------------------------------------------------------------------- */
describe('Piece Classification for Special Presets', () => {
test('classifies waypoint pieces', () => {
expect(classifyMazePieceName('Act 2 - Tomb Waypoint N', 'Act 2 - Tomb')).toEqual({
kind: 'waypoint',
sides: 'N',
})
expect(classifyMazePieceName('Act 5 - Temple NE Waypoint', 'Act 5 - Temple')).toEqual({
kind: 'waypoint',
sides: 'NE',
})
expect(classifyMazePieceName('Act 5 - Ice waypoint N', 'Act 5 - Ice Caves')).toEqual({
kind: 'waypoint',
sides: 'N',
})
})
test('classifies quest and special pieces', () => {
expect(classifyMazePieceName('Act 2 - Tomb Talrasha N', 'Act 2 - Tomb')).toEqual({
kind: 'quest',
sides: 'N',
})
expect(classifyMazePieceName("Act 2 - Sewer Radament's Lair S", 'Act 2 - Sewer')).toEqual({
kind: 'quest',
sides: 'S',
})
expect(classifyMazePieceName('Act 2 - Lair Tight Spot S', 'Act 2 - Lair')).toEqual({
kind: 'quest',
sides: 'S',
})
expect(classifyMazePieceName('Act 1 - Barracks Forge W', 'Act 1 - Barracks')).toEqual({
kind: 'quest',
sides: 'W',
})
expect(classifyMazePieceName('Act 4 - Bridge 1', 'Act 4 - Lava')).toEqual({
kind: 'next',
sides: '',
})
expect(classifyMazePieceName('Act 3 - Mephisto Complex', 'Act 3 - Kurast')).toEqual({
kind: 'quest',
sides: '',
})
expect(classifyMazePieceName('Act 5 - Temple Final Room', 'Act 5 - Temple')).toEqual({
kind: 'quest',
sides: '',
})
expect(classifyMazePieceName('Act 5 - ThroneRoom Entrance', 'Act 5 - Baal')).toEqual({
kind: 'quest',
sides: '',
})
})
})
})