import { describe, expect, test } from 'vitest' import type { Ds1, Ds1Cell } from '../src/formats/ds1' import { generateMaze, type MazePiece, type MazePieceKind, type MazeRequest } from '../src/game/maze' function makeMockDs1(width = 10, height = 10): Ds1 { const cells: Ds1Cell[][] = [] for (let y = 0; y < height; y += 1) { const row: Ds1Cell[] = [] for (let x = 0; x < width; x += 1) { row.push({ walls: [{ prop1: 0, sequence: 0, style: 0, type: 0, unknown1: 0, unknown2: 0, hidden: false }], floors: [{ prop1: 0, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false }], shadows: [], substitutions: [], }) } cells.push(row) } return { version: 18, width, height, act: 1, substitutionType: 0, wallLayers: 1, floorLayers: 1, cells, objects: [], npcPathOffset: null, } } function makeMockPiece(name: string, kind: MazePieceKind, sides: string): MazePiece { return { name, kind, sides, levels: [makeMockDs1(10, 10)], } } function makeCavePieceSet(): MazePiece[] { const ds1 = makeMockDs1(10, 10) const tokens = [ 'NSEW', 'NSE', 'NSW', 'NEW', 'SEW', 'NS', 'EW', 'NE', 'NW', 'SE', 'SW', 'N', 'S', 'E', 'W', ] const pieces: MazePiece[] = tokens.map(token => ({ name: `Act 1 - Cave ${token}`, kind: 'room', sides: token, levels: [ds1], })) pieces.push({ name: 'Act 1 - Cave Prev NS', kind: 'prev', sides: 'NS', levels: [ds1], }) pieces.push({ name: 'Act 1 - Cave Prev EW', kind: 'prev', sides: 'EW', levels: [ds1], }) pieces.push({ name: 'Act 1 - Cave Down NS', kind: 'down', sides: 'NS', levels: [ds1], }) pieces.push({ name: 'Act 1 - Cave Down EW', kind: 'down', sides: 'EW', levels: [ds1], }) return pieces } describe('Maze Global Bounding Box Constraints (Issue #48)', () => { test('generateMaze respects maxSectionsX and maxSectionsY limits', () => { const pieces = makeCavePieceSet() const request: MazeRequest = { levelId: 2, levelName: 'Act 1 - Cave 1', levelTypeName: 'Act 1 - Cave', sectionSize: 10, sectionHeight: 10, minRooms: 9, merge: 0, seed: 12345, pieces, maxSectionsX: 3, maxSectionsY: 3, } const result = generateMaze(request) expect(result.level.width).toBeLessThanOrEqual(30) expect(result.level.height).toBeLessThanOrEqual(30) expect(result.stats.maxCellsX).toBe(30) expect(result.stats.maxCellsY).toBe(30) expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(9) }) test('generateMaze respects maxCellsX and maxCellsY directly', () => { const pieces = makeCavePieceSet() const request: MazeRequest = { levelId: 2, levelName: 'Act 1 - Cave 1', levelTypeName: 'Act 1 - Cave', sectionSize: 10, sectionHeight: 10, minRooms: 8, merge: 500, seed: 99999, pieces, maxCellsX: 30, maxCellsY: 30, } const result = generateMaze(request) expect(result.level.width).toBeLessThanOrEqual(30) expect(result.level.height).toBeLessThanOrEqual(30) expect(result.stats.maxCellsX).toBe(30) expect(result.stats.maxCellsY).toBe(30) }) test('forces maze to fold vertically when maxSectionsX is narrow (e.g. 2 sections wide)', () => { const pieces = makeCavePieceSet() const request: MazeRequest = { levelId: 2, levelName: 'Act 1 - Cave 1', levelTypeName: 'Act 1 - Cave', sectionSize: 10, sectionHeight: 10, minRooms: 8, merge: 0, seed: 42, pieces, maxSectionsX: 2, maxSectionsY: 6, } const result = generateMaze(request) expect(result.level.width).toBeLessThanOrEqual(20) expect(result.level.height).toBeLessThanOrEqual(60) expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(8) expect(Number(result.stats.boundsRejected)).toBeGreaterThanOrEqual(1) }) test('records boundsRejected when room growth attempts cross the configured boundary', () => { const pieces = makeCavePieceSet() const request: MazeRequest = { levelId: 2, levelName: 'Act 1 - Cave 1', levelTypeName: 'Act 1 - Cave', sectionSize: 10, sectionHeight: 10, minRooms: 9, merge: 800, seed: 54321, pieces, maxSectionsX: 3, maxSectionsY: 3, } const result = generateMaze(request) expect(result.stats.boundsRejected).toBeDefined() expect(typeof result.stats.boundsRejected).toBe('number') expect(result.level.width).toBeLessThanOrEqual(30) expect(result.level.height).toBeLessThanOrEqual(30) }) test('unconstrained maze allows wide sprawl when no bounds are specified', () => { const pieces = makeCavePieceSet() const request: MazeRequest = { levelId: 2, levelName: 'Act 1 - Cave 1', levelTypeName: 'Act 1 - Cave', sectionSize: 10, sectionHeight: 10, minRooms: 9, merge: 0, seed: 12345, pieces, } const result = generateMaze(request) expect(result.stats.maxCellsX).toBeUndefined() expect(result.stats.maxCellsY).toBeUndefined() expect(result.stats.boundsRejected).toBe(0) expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(9) }) })