import { describe, test, expect } from 'vitest' import { generateMaze, unimplementedPassesFor, UNIMPLEMENTED_PASSES, MAZE_LEVEL_TYPE_PROFILES, type MazePiece, type MazePieceKind, type MazeWarp, type MazeWaypoint, } from '../src/game/maze.ts' import { LVLPREST_ACT2_CORRUPT_HAREM_NE, LVLPREST_ACT2_CORRUPT_HAREM_SE, LVLPREST_ACT2_CORRUPT_HAREM_SW, LVLPREST_ACT2_CORRUPT_HAREM_NW, LVLPREST_ACT2_BASEMENT_NE, LVLPREST_ACT2_BASEMENT_SE, LVLPREST_ACT2_BASEMENT_SW, LVLPREST_ACT2_BASEMENT_NW, LVLPREST_ACT3_SPIDER_SW, LVLPREST_ACT3_SPIDER_SE, LVLPREST_ACT3_SPIDER_NW, LVLPREST_ACT3_SPIDER_NE, LVLPREST_ACT3_SPIDER_CHEST_NW, LVLPREST_ACT3_SPIDER_CHEST_NE, LVLPREST_ACT5_LAVA_N, LVLPREST_ACT5_LAVA_S, LVLPREST_ACT5_LAVA_E, LVLPREST_ACT5_LAVA_W, LVLPREST_ACT5_LAVA_EW, LVLPREST_ACT5_LAVA_NS, LVLPREST_MAZE_DEF_REGISTRY, } from '../src/game/maze-special-passes.ts' import type { Ds1, Ds1Cell, Ds1Object } from '../src/formats/ds1.ts' 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, } } function mockPiece( name: string, kind: MazePieceKind, sides = '', objects: Ds1Object[] = [], width = 10, height = 10, ): MazePiece { return { name, kind, sides, levels: [mockDs1(width, height, objects)], } } const SIDES = ['W', 'E', 'EW', 'S', 'SW', 'SE', 'SEW', 'N', 'NW', 'NE', 'NEW', 'NS', 'NSW', 'NSE', 'NSEW'] function createPieceSetForType( levelTypeName: string, extraKinds: { kind: MazePieceKind; nameToken: string; objects?: Ds1Object[] }[] = [], ): MazePiece[] { 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} Waypoint ${s}`, 'waypoint', s, [{ type: 2, id: 156, x: 25, y: 25, flags: 0 }]), ), ...['N', 'S', 'E', 'W'].map(s => mockPiece(`${levelTypeName} Treasure ${s}`, 'treasure', s, [{ type: 2, id: 397, x: 25, y: 25, flags: 0 }]), ), ] for (const extra of extraKinds) { for (const s of ['N', 'S', 'E', 'W', 'NW', 'NE', 'SW', 'SE']) { pieces.push(mockPiece(`${levelTypeName} ${extra.nameToken} ${s}`, extra.kind, s, extra.objects ?? [])) } } return pieces } describe('Maze Specials Profiles (Issue #84)', () => { describe('Profile completeness and unimplementedPasses check', () => { test('all 4 target level types report 0 unimplemented passes', () => { 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('UNIMPLEMENTED_PASSES global array is completely empty', () => { expect(UNIMPLEMENTED_PASSES).toEqual([]) }) test('profile table contains correctly configured special tables for all four types', () => { const harem = MAZE_LEVEL_TYPE_PROFILES.find(p => p.levelTypeName === 'Act 2 - Harem')! expect(harem).toBeDefined() expect(harem.ring).toBe(2) expect(harem.grow).toBe(false) expect(harem.specials.map(s => ({ kind: s.kind, levelIds: s.levelIds }))).toEqual([ { kind: 'prev', levelIds: [51] }, { kind: 'next', levelIds: [51] }, ]) const basement = MAZE_LEVEL_TYPE_PROFILES.find(p => p.levelTypeName === 'Act 2 - Basement')! expect(basement).toBeDefined() expect(basement.ring).toBe(2) expect(basement.grow).toBe(false) expect(basement.specials.map(s => ({ kind: s.kind, levelIds: s.levelIds }))).toEqual([ { kind: 'prev', levelIds: [52, 53, 54] }, { kind: 'waypoint', levelIds: [52] }, { kind: 'next', levelIds: [52, 53] }, ]) const spider = MAZE_LEVEL_TYPE_PROFILES.find(p => p.levelTypeName === 'Act 3 - Spider')! expect(spider).toBeDefined() expect(spider.ring).toBe(2) expect(spider.grow).toBe(false) expect(spider.specials.map(s => ({ kind: s.kind, levelIds: s.levelIds }))).toEqual([ { kind: 'prev', levelIds: [84, 85] }, { kind: 'treasure', levelIds: [84, 85] }, ]) const lava = MAZE_LEVEL_TYPE_PROFILES.find(p => p.levelTypeName === 'Act 5 - Lava')! expect(lava).toBeDefined() expect(lava.grow).toBe(false) expect(lava.lavaPairs).toBe(true) expect(lava.specials.map(s => ({ kind: s.kind, levelIds: s.levelIds }))).toEqual([ { kind: 'prev', levelIds: [125, 126, 127] }, { kind: 'treasure', levelIds: [125, 126, 127] }, ]) }) }) describe('LvlPrest constants & def registry', () => { test('defines canonical LvlPrest Def IDs', () => { expect(LVLPREST_ACT2_CORRUPT_HAREM_NE).toBe(354) expect(LVLPREST_ACT2_CORRUPT_HAREM_SE).toBe(355) expect(LVLPREST_ACT2_CORRUPT_HAREM_SW).toBe(356) expect(LVLPREST_ACT2_CORRUPT_HAREM_NW).toBe(357) expect(LVLPREST_ACT2_BASEMENT_NE).toBe(358) expect(LVLPREST_ACT2_BASEMENT_SE).toBe(359) expect(LVLPREST_ACT2_BASEMENT_SW).toBe(360) expect(LVLPREST_ACT2_BASEMENT_NW).toBe(361) expect(LVLPREST_ACT3_SPIDER_SW).toBe(659) expect(LVLPREST_ACT3_SPIDER_SE).toBe(660) expect(LVLPREST_ACT3_SPIDER_NW).toBe(661) expect(LVLPREST_ACT3_SPIDER_NE).toBe(662) expect(LVLPREST_ACT3_SPIDER_CHEST_NW).toBe(663) expect(LVLPREST_ACT3_SPIDER_CHEST_NE).toBe(664) expect(LVLPREST_ACT5_LAVA_N).toBe(1053) expect(LVLPREST_ACT5_LAVA_S).toBe(1054) expect(LVLPREST_ACT5_LAVA_E).toBe(1055) expect(LVLPREST_ACT5_LAVA_W).toBe(1056) expect(LVLPREST_ACT5_LAVA_EW).toBe(1057) expect(LVLPREST_ACT5_LAVA_NS).toBe(1058) }) test('registers Spider chests in LVLPREST_MAZE_DEF_REGISTRY', () => { expect(LVLPREST_MAZE_DEF_REGISTRY[LVLPREST_ACT3_SPIDER_CHEST_NW]).toEqual({ name: 'Act 3 - Spider Chest NW', kind: 'treasure', sides: 'NW', }) expect(LVLPREST_MAZE_DEF_REGISTRY[LVLPREST_ACT3_SPIDER_CHEST_NE]).toEqual({ name: 'Act 3 - Spider Chest NE', kind: 'treasure', sides: 'NE', }) }) }) describe('generateMaze execution with specials', () => { test('Act 2 - Harem (Level 51) places prev and next specials', () => { const pieces = createPieceSetForType('Act 2 - Harem') const result = generateMaze({ levelId: 51, levelName: 'Harem Level 2', levelTypeName: 'Act 2 - Harem', sectionSize: 10, minRooms: 4, merge: 0, seed: 51001, pieces, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).toContain('next') const warps = result.stats.warps as MazeWarp[] expect(warps.length).toBeGreaterThanOrEqual(2) expect(warps.some(w => w.direction === 'up')).toBe(true) expect(warps.some(w => w.direction === 'down')).toBe(true) }) test('Act 2 - Basement (Level 52) places prev, waypoint, and next specials', () => { const pieces = createPieceSetForType('Act 2 - Basement') const result = generateMaze({ levelId: 52, levelName: 'Palace Cellar Level 1', levelTypeName: 'Act 2 - Basement', sectionSize: 10, minRooms: 4, merge: 0, seed: 52001, pieces, hasWaypoint: true, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).toContain('waypoint') expect(kinds).toContain('next') const waypoints = result.stats.waypoints as MazeWaypoint[] expect(waypoints.length).toBe(1) const warps = result.stats.warps as MazeWarp[] expect(warps.length).toBeGreaterThanOrEqual(2) }) test('Act 2 - Basement (Level 53) places prev and next without waypoint', () => { const pieces = createPieceSetForType('Act 2 - Basement') const result = generateMaze({ levelId: 53, levelName: 'Palace Cellar Level 2', levelTypeName: 'Act 2 - Basement', sectionSize: 10, minRooms: 4, merge: 0, seed: 53001, pieces, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).toContain('next') expect(kinds).not.toContain('waypoint') const waypoints = result.stats.waypoints as MazeWaypoint[] expect(waypoints.length).toBe(0) }) test('Act 2 - Basement (Level 54) places prev only', () => { const pieces = createPieceSetForType('Act 2 - Basement') const result = generateMaze({ levelId: 54, levelName: 'Palace Cellar Level 3', levelTypeName: 'Act 2 - Basement', sectionSize: 10, minRooms: 4, merge: 0, seed: 54001, pieces, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).not.toContain('next') expect(kinds).not.toContain('waypoint') }) test('Act 3 - Spider (Level 84 & 85) places prev and treasure', () => { for (const levelId of [84, 85]) { const pieces = createPieceSetForType('Act 3 - Spider') const result = generateMaze({ levelId, levelName: levelId === 84 ? 'Spider Cave' : 'Spider Cavern', levelTypeName: 'Act 3 - Spider', sectionSize: 10, minRooms: 4, merge: 0, seed: levelId * 1000 + 7, pieces, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).toContain('treasure') const warps = result.stats.warps as MazeWarp[] expect(warps.some(w => w.direction === 'up')).toBe(true) } }) test('Act 5 - Lava (Levels 125, 126, 127) places prev and treasure', () => { for (const levelId of [125, 126, 127]) { const pieces = createPieceSetForType('Act 5 - Lava') const result = generateMaze({ levelId, levelName: `Lava Pit ${levelId}`, levelTypeName: 'Act 5 - Lava', sectionSize: 10, minRooms: 4, merge: 0, seed: levelId * 1000 + 9, pieces, }) expect(result.stats.unimplementedPasses).toEqual([]) const specials = result.stats.specialsApplied as { room: number; kind: string; sides: string }[] const kinds = specials.map(s => s.kind) expect(kinds).toContain('prev') expect(kinds).toContain('treasure') const warps = result.stats.warps as MazeWarp[] expect(warps.some(w => w.direction === 'up')).toBe(true) } }) }) })