import { describe, expect, test } from "vitest" import { resolveUnsetSize, UnresolvedLevelSizeError, generateWilderness, type WildernessPiece, } from "../src/game/wilderness.ts" import type { Ds1, Ds1Cell } from "../src/formats/ds1.ts" function makeMockDs1(width: number, height: number, floorStyle: number = 1): 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: [], floors: [{ prop1: 2, sequence: 0, style: floorStyle, unknown1: 0, unknown2: 0, hidden: false }], shadows: [], substitutions: [], }) } cells.push(row) } return { version: 18, width, height, act: 5, substitutionType: 0, wallLayers: 1, floorLayers: 1, cells, objects: [], npcPathOffset: null, } } describe("Adversarial Stress Testing: resolveUnsetSize and Barricade Extent", () => { test("resolveUnsetSize throws UnresolvedLevelSizeError when pieces array is empty for Level 111 and 112", () => { for (const levelId of [111, 112]) { expect(() => { resolveUnsetSize({ levelId, levelName: `Act 5 - Barricade ${levelId - 110}`, levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: 12345, pieces: [], substitutions: [], }) }).toThrow(UnresolvedLevelSizeError) } }) test("resolveUnsetSize throws UnresolvedLevelSizeError for non-barricade levels without pieces across Acts 1..4", () => { const testCases = [ { levelId: 2, name: "Blood Moor", type: "Act 1 - Wilderness" }, { levelId: 41, name: "Rocky Waste", type: "Act 2 - Desert" }, { levelId: 76, name: "Spider Forest", type: "Act 3 - Jungle" }, { levelId: 104, name: "Outer Steppes", type: "Act 4 - Mesa" }, ] for (const tc of testCases) { expect(() => { resolveUnsetSize({ levelId: tc.levelId, levelName: tc.name, levelTypeName: tc.type, sizeX: -1, sizeY: -1, subType: 0, subTheme: 0, seed: 12345, pieces: [], substitutions: [], }) }).toThrow(UnresolvedLevelSizeError) } }) test("resolveUnsetSize throws UnresolvedLevelSizeError for non-barricade levels with non-barricade pieces", () => { const mockBorderPiece: WildernessPiece = { name: "Act 1 - Wild Border 1", border: true, levels: [makeMockDs1(8, 8)], } expect(() => { resolveUnsetSize({ levelId: 2, levelName: "Blood Moor", levelTypeName: "Act 1 - Wilderness", sizeX: -1, sizeY: -1, subType: 6, subTheme: 0, seed: 12345, pieces: [mockBorderPiece], substitutions: [], }) }).toThrow(UnresolvedLevelSizeError) }) test("resolveUnsetSize handles combinations of entrance only, exit only, multiple entrances, and multiple exits", () => { const entranceDs1 = makeMockDs1(16, 32) const exitDs1 = makeMockDs1(16, 32) const wallDs1 = makeMockDs1(17, 17) // Entrance only const entranceOnly: WildernessPiece[] = [ { name: "Act 5 - Barricade Entrance 16x32", border: false, levels: [entranceDs1] }, { name: "Act 5 - Barricade 1", border: false, levels: [wallDs1] }, ] const resEntranceOnly = resolveUnsetSize({ levelId: 111, levelName: "Act 5 - Barricade 1", levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: 12345, pieces: entranceOnly, substitutions: [], }) expect(resEntranceOnly.sizeY).toBeGreaterThan(resEntranceOnly.sizeX) expect(resEntranceOnly.sizeX).toBeGreaterThanOrEqual(48) expect(resEntranceOnly.sizeX).toBeLessThanOrEqual(64) expect(resEntranceOnly.sizeY).toBeGreaterThanOrEqual(160) expect(resEntranceOnly.sizeY).toBeLessThanOrEqual(192) // Exit only const exitOnly: WildernessPiece[] = [ { name: "Act 5 - Barricade Exit 16x32", border: false, levels: [exitDs1] }, { name: "Act 5 - Barricade 1", border: false, levels: [wallDs1] }, ] const resExitOnly = resolveUnsetSize({ levelId: 111, levelName: "Act 5 - Barricade 1", levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: 12345, pieces: exitOnly, substitutions: [], }) expect(resExitOnly.sizeY).toBeGreaterThan(resExitOnly.sizeX) expect(resExitOnly.sizeX).toBeGreaterThanOrEqual(48) expect(resExitOnly.sizeX).toBeLessThanOrEqual(64) expect(resExitOnly.sizeY).toBeGreaterThanOrEqual(160) expect(resExitOnly.sizeY).toBeLessThanOrEqual(192) // Multiple entrances & exits const multiplePieces: WildernessPiece[] = [ { name: "Act 5 - Barricade Entrance A", border: false, levels: [entranceDs1] }, { name: "Act 5 - Barricade Entrance B", border: false, levels: [makeMockDs1(24, 40)] }, { name: "Act 5 - Barricade Exit A", border: false, levels: [exitDs1] }, { name: "Act 5 - Barricade Exit B", border: false, levels: [makeMockDs1(32, 48)] }, { name: "Act 5 - Barricade 1", border: false, levels: [wallDs1] }, ] const resMultiple = resolveUnsetSize({ levelId: 111, levelName: "Act 5 - Barricade 1", levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: 12345, pieces: multiplePieces, substitutions: [], }) expect(resMultiple.sizeY).toBeGreaterThan(resMultiple.sizeX) expect(resMultiple.sizeX).toBeGreaterThanOrEqual(48) expect(resMultiple.sizeX).toBeLessThanOrEqual(64) expect(resMultiple.sizeY).toBeGreaterThanOrEqual(160) expect(resMultiple.sizeY).toBeLessThanOrEqual(192) }) test("resolveUnsetSize guarantees sizeY > sizeX across 1,000 combinatorial fuzzing trials", () => { const mockWallPiece: WildernessPiece = { name: "Act 5 - Barricade Wall 1", border: false, levels: [makeMockDs1(17, 17)], } for (let trial = 0; trial < 1000; trial += 1) { const numEntrances = trial % 5 const numExits = (trial * 3) % 5 const pieces: WildernessPiece[] = [mockWallPiece] for (let i = 0; i < numEntrances; i += 1) { const w = 1 + ((trial * 7 + i * 11) % 128) const h = 1 + ((trial * 13 + i * 17) % 256) pieces.push({ name: `Act 5 - Barricade Entrance ${i}`, border: false, levels: [makeMockDs1(w, h)], }) } for (let i = 0; i < numExits; i += 1) { const w = 1 + ((trial * 19 + i * 23) % 128) const h = 1 + ((trial * 29 + i * 31) % 256) pieces.push({ name: `Act 5 - Barricade Exit ${i}`, border: false, levels: [makeMockDs1(w, h)], }) } const res = resolveUnsetSize({ levelId: 111, levelName: "Act 5 - Barricade 1", levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: trial, pieces, substitutions: [], }) expect(res.sizeX).toBeGreaterThanOrEqual(48) expect(res.sizeX).toBeLessThanOrEqual(64) expect(res.sizeX % 8).toBe(0) expect(res.sizeY).toBeGreaterThanOrEqual(160) expect(res.sizeY).toBeLessThanOrEqual(192) expect(res.sizeY % 8).toBe(0) expect(res.sizeY).toBeGreaterThan(res.sizeX) expect(res.sizeY / res.sizeX).toBeGreaterThanOrEqual(2.5) expect(res.source).toBe("LvlPrest Barricade corridor dynamic extent") } }) test("generateWilderness generates Level 111 and 112 conforming to vertical corridor bounds", () => { const entranceDs1 = makeMockDs1(17, 33) const exitDs1 = makeMockDs1(17, 33) const wallDs1 = makeMockDs1(17, 17) const cliffDs1 = makeMockDs1(17, 17) const pieces: WildernessPiece[] = [ { name: "Act 5 - Barricade Entrance 16x32", border: false, levels: [entranceDs1] }, { name: "Act 5 - Barricade Exit 16x32", border: false, levels: [exitDs1] }, { name: "Act 5 - Barricade 1", border: false, levels: [wallDs1] }, { name: "Act 5 - Barricade 2", border: false, levels: [wallDs1] }, { name: "Act 5 - Barricade Cliff Border 1", border: true, levels: [cliffDs1] }, ] for (const levelId of [111, 112]) { const generated = generateWilderness({ levelId, levelName: `Act 5 - Barricade ${levelId - 110}`, levelTypeName: "Act 5 - Barricade", sizeX: -1, sizeY: -1, subType: 10, subTheme: 0, seed: 0x301cd095 + levelId, pieces, substitutions: [], }) expect(Number(generated.stats.sizeX)).toBeGreaterThanOrEqual(48) expect(Number(generated.stats.sizeX)).toBeLessThanOrEqual(64) expect(Number(generated.stats.sizeY)).toBeGreaterThanOrEqual(160) expect(Number(generated.stats.sizeY)).toBeLessThanOrEqual(192) expect(Number(generated.stats.sizeY)).toBeGreaterThan(Number(generated.stats.sizeX)) expect(generated.level.width).toBe(Number(generated.stats.sizeX)) expect(generated.level.height).toBe(Number(generated.stats.sizeY)) } }) })