feat(wilderness): track procedural passes in telemetry stats

- Add proceduralRoadCells, proceduralDividerCells, proceduralWaterCells, proceduralWallCells to WildernessTelemetry and WildernessStats
- Measure actual painted cells in stampThemedRoads, stampThemedDividers, generateDirtPaths, and act outdoor level topography passes
- Add unit tests in tests/wilderness-telemetry.test.ts verifying counted telemetry against canvas state

TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
troytt 2026-09-17 15:23:31 +00:00
parent fa307bff46
commit 53d6cdd08c
3 changed files with 215 additions and 28 deletions

View File

@ -398,6 +398,9 @@ export function initAct2OutdoorLevel(
}
stats.act2Topography = { canyonWalls, sandTrailCells, oasisPoolCells, tombEntrances }
stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + canyonWalls
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + sandTrailCells
stats.proceduralWaterCells = (stats.proceduralWaterCells ?? 0) + oasisPoolCells
stats.notes.push(
`Act 2 Desert Topography: ${String(canyonWalls)} canyon walls, ${String(sandTrailCells)} sand trail cells, ${String(oasisPoolCells)} oasis cells, ${String(tombEntrances)} tomb entrances`,
)
@ -443,6 +446,7 @@ export function stampAct3KurastCauseway(
// Central paved walkway flanked by canal water and balustrades
let causewayCells = 0
let canalCells = 0
let balustradeCells = 0
const walkwayLeft = Math.max(2, Math.floor((canvas.width - 8) / 2))
const walkwayRight = walkwayLeft + 7
@ -465,6 +469,7 @@ export function stampAct3KurastCauseway(
} else if (x === walkwayLeft - 1 || x === walkwayRight + 1) {
// Stone balustrade wall: style 1, prop1 129
paintWall(canvas, x, y, makeWall(1, 0, 1, 129))
balustradeCells += 1
} else {
// Deep canal water: style 2, sequence 0, prop1 2
paintFloor(canvas, x, y, makeFloor(2, 0, 2), true)
@ -480,6 +485,9 @@ export function stampAct3KurastCauseway(
canalCells,
junglePathCells: 0,
}
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + causewayCells
stats.proceduralWaterCells = (stats.proceduralWaterCells ?? 0) + canalCells
stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + balustradeCells
stats.notes.push(
`Act 3 Causeway Topography: ${String(causewayCells)} causeway cells, ${String(canalCells)} canal water cells`,
)
@ -507,6 +515,7 @@ export function initAct3OutdoorLevel(
let causewayCells = 0
let riverBridges = 0
let canalCells = 0
let embankmentWalls = 0
const junglePathCells = 0
const hubX = inX * TILES_PER_BLOCK + 4
@ -558,10 +567,14 @@ export function initAct3OutdoorLevel(
// Stone canal embankments
paintWall(canvas, canalX - 1, y, makeWall(1, 0, 1, 129))
paintWall(canvas, canalX + 4, y, makeWall(1, 0, 1, 129))
embankmentWalls += 2
}
}
stats.act3Topography = { causewayCells, riverBridges, canalCells, junglePathCells }
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + causewayCells
stats.proceduralWaterCells = (stats.proceduralWaterCells ?? 0) + canalCells
stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + embankmentWalls
stats.notes.push(
`Act 3 Kurast Topography: ${String(causewayCells)} causeway cells, ${String(canalCells)} canal cells, ${String(riverBridges)} bridges`,
)
@ -690,6 +703,8 @@ export function initAct4OutdoorLevel(
}
stats.act4Topography = { lavaChasmCells, rockyBridges, plateauCells }
stats.proceduralWaterCells = (stats.proceduralWaterCells ?? 0) + lavaChasmCells
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + rockyBridges
stats.notes.push(
`Act 4 Mesa/Lava Topography: ${String(lavaChasmCells)} lava cells, ${String(rockyBridges)} rocky bridge cells, ${String(plateauCells)} plateau cells`,
)
@ -811,6 +826,9 @@ export function initAct5OutdoorLevel(
}
stats.act5Topography = { barricadeWalls, trenchCells, frozenLakeCells }
stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + barricadeWalls
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + trenchCells
stats.proceduralWaterCells = (stats.proceduralWaterCells ?? 0) + frozenLakeCells
stats.notes.push(
`Act 5 Siege/Barricade Topography: ${String(barricadeWalls)} barricade walls, ${String(trenchCells)} trench cells, ${String(frozenLakeCells)} frozen lake cells`,
)

View File

@ -1070,6 +1070,14 @@ export interface LandmarkEntity {
readonly objectId?: number
}
/** Procedural terrain pass telemetry counters (Issue #62). */
export interface WildernessTelemetry {
readonly proceduralRoadCells: number
readonly proceduralDividerCells: number
readonly proceduralWaterCells: number
readonly proceduralWallCells: number
}
/** The report collector. */
export interface WildernessStats {
readonly substitutions: SubstitutionReport[]
@ -1103,6 +1111,11 @@ export interface WildernessStats {
riverForks?: number
meanderCount?: number
siegeTopography?: SiegeTopographyStats
subTheme?: number
proceduralRoadCells?: number
proceduralDividerCells?: number
proceduralWaterCells?: number
proceduralWallCells?: number
}
/**
@ -3153,7 +3166,10 @@ export function stampThemedRoads(
rng: Rng,
theme: OutdoorRoadTheme,
openGates?: ReadonlyMap<string, number>,
stats?: WildernessStats,
): void {
const paintedRoadCells = new Set<string>()
const isInsideProtectedPreset = (x: number, y: number): boolean => {
for (const r of placedRects) {
if (theme.isProtectedPreset ? !theme.isProtectedPreset(r.name) : false) {
@ -3189,6 +3205,7 @@ export function stampThemedRoads(
}
}
;(cell.floors as Ds1Floor[])[0] = theme.roadFloor(rng)
paintedRoadCells.add(`${x},${y}`)
}
const hubX = inX * TILES_PER_BLOCK + 4
@ -3287,6 +3304,10 @@ export function stampThemedRoads(
paintSpurSegment(doorX, doorY, doorX, hubY)
}
}
if (stats) {
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + paintedRoadCells.size
}
}
/**
@ -3302,11 +3323,13 @@ export function stampThemedDividers(
placedRects: readonly PlacedPresetRect[],
rng: Rng,
theme: OutdoorDividerTheme,
stats?: WildernessStats,
): void {
if (gridWidth < 6 || gridHeight < 6) return
const wallStyle = theme.wallStyle
const wallProp1 = theme.wallProp1 ?? 129
const placedCells = new Set<string>()
const makeWall = (type: number, sequence: number): Ds1Wall => ({
prop1: wallProp1,
@ -3327,6 +3350,7 @@ export function stampThemedDividers(
} else {
targetWalls.push(wall)
}
placedCells.add(`${x},${y}`)
}
// Offset divider lines by 2 blocks (16 tiles) from the main road spine
@ -3422,6 +3446,11 @@ export function stampThemedDividers(
}
}
}
if (stats) {
stats.proceduralDividerCells = (stats.proceduralDividerCells ?? 0) + placedCells.size
stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + placedCells.size
}
}
/**
@ -3437,6 +3466,7 @@ export function stampAct1DirtRoads(
placedRects: readonly PlacedPresetRect[],
rng: Rng,
openGates?: ReadonlyMap<string, number>,
stats?: WildernessStats,
): void {
stampThemedRoads(
canvas,
@ -3459,6 +3489,7 @@ export function stampAct1DirtRoads(
isIgnoredSpurPreset: (name) => name === 'Act 1 - River' || /Stone Fill|Swamp Fill|Tree Fill|Fence Fill|Corral Fill/i.test(name),
},
openGates,
stats,
)
}
@ -3474,12 +3505,13 @@ export function stampAct1InteriorStoneWalls(
outY: number,
placedRects: readonly PlacedPresetRect[],
rng: Rng,
stats?: WildernessStats,
): void {
stampThemedDividers(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, {
wallStyle: 2,
wallProp1: 129,
roadStyles: [5],
})
}, stats)
}
/**
@ -3494,6 +3526,7 @@ export function stampAct2DesertRoads(
placedRects: readonly PlacedPresetRect[],
rng: Rng,
openGates?: ReadonlyMap<string, number>,
stats?: WildernessStats,
): void {
stampThemedRoads(
canvas,
@ -3516,6 +3549,7 @@ export function stampAct2DesertRoads(
isIgnoredSpurPreset: (name) => /Fill|Berms/i.test(name),
},
openGates,
stats,
)
}
@ -3530,12 +3564,13 @@ export function stampAct2SandstoneBerms(
outY: number,
placedRects: readonly PlacedPresetRect[],
rng: Rng,
stats?: WildernessStats,
): void {
stampThemedDividers(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, {
wallStyle: 1,
wallProp1: 129,
roadStyles: [1],
})
}, stats)
}
/**
@ -3550,6 +3585,7 @@ export function stampAct3KurastAvenues(
placedRects: readonly PlacedPresetRect[],
rng: Rng,
openGates?: ReadonlyMap<string, number>,
stats?: WildernessStats,
): void {
stampThemedRoads(
canvas,
@ -3572,6 +3608,7 @@ export function stampAct3KurastAvenues(
isIgnoredSpurPreset: (name) => /Fill/i.test(name),
},
openGates,
stats,
)
}
@ -3586,12 +3623,13 @@ export function stampAct3KurastBalustrades(
outY: number,
placedRects: readonly PlacedPresetRect[],
rng: Rng,
stats?: WildernessStats,
): void {
stampThemedDividers(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, {
wallStyle: 1,
wallProp1: 129,
roadStyles: [1],
})
}, stats)
}
/**
@ -3606,6 +3644,7 @@ export function stampAct5SnowRoads(
placedRects: readonly PlacedPresetRect[],
rng: Rng,
openGates?: ReadonlyMap<string, number>,
stats?: WildernessStats,
): void {
stampThemedRoads(
canvas,
@ -3628,6 +3667,7 @@ export function stampAct5SnowRoads(
isIgnoredSpurPreset: (name) => /Fill|Filler/i.test(name),
},
openGates,
stats,
)
}
@ -3642,12 +3682,13 @@ export function stampAct5FrozenTrailDivides(
outY: number,
placedRects: readonly PlacedPresetRect[],
rng: Rng,
stats?: WildernessStats,
): void {
stampThemedDividers(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, {
wallStyle: 2,
wallProp1: 129,
roadStyles: [1],
})
}, stats)
}
/**
@ -3666,47 +3707,48 @@ export function stampOutdoorRoads(
levelTypeName?: string,
openGates?: ReadonlyMap<string, number>,
levelsTable?: D2Table,
stats?: WildernessStats,
): void {
if (levelId === 39) return // Cow Level has no roads
// Level ID takes highest precedence
if (levelId >= 41 && levelId <= 46) {
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelId >= 79 && levelId <= 82) {
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelId === 111 || levelId === 112 || levelId === 117) {
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelId >= 2 && levelId <= 7) {
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
// Level type name takes second precedence
} else if (levelTypeName === 'Act 2 - Desert') {
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelTypeName === 'Act 3 - Kurast') {
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelTypeName === 'Act 5 - Barricade' || levelTypeName === 'Act 5 - Siege') {
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
} else if (levelTypeName === 'Act 1 - Wilderness') {
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
return
}
// Unified actOfLevel fallback
const effectiveAct = actOfLevel(levelId, levelTypeName, act, levelsTable)
if (effectiveAct === 2) {
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct2DesertRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
} else if (effectiveAct === 3) {
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct3KurastAvenues(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
} else if (effectiveAct === 5) {
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct5SnowRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
} else if (effectiveAct === 1) {
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates)
stampAct1DirtRoads(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, openGates, stats)
}
}
@ -3725,49 +3767,50 @@ export function stampOutdoorDividers(
rng: Rng,
levelTypeName?: string,
levelsTable?: D2Table,
stats?: WildernessStats,
): void {
if (levelId === 39) return // Cow Level has no dividers
// Level ID takes highest precedence
if (levelId >= 41 && levelId <= 46) {
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelId >= 79 && levelId <= 82) {
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelId === 111 || levelId === 112 || levelId === 117) {
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelId >= 2 && levelId <= 7) {
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
}
// Level type name takes second precedence
if (levelTypeName === 'Act 2 - Desert') {
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelTypeName === 'Act 3 - Kurast') {
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelTypeName === 'Act 5 - Barricade' || levelTypeName === 'Act 5 - Siege') {
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
} else if (levelTypeName === 'Act 1 - Wilderness') {
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
return
}
// Unified actOfLevel fallback
const effectiveAct = actOfLevel(levelId, levelTypeName, act, levelsTable)
if (effectiveAct === 2) {
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct2SandstoneBerms(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
} else if (effectiveAct === 3) {
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct3KurastBalustrades(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
} else if (effectiveAct === 5) {
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct5FrozenTrailDivides(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
} else if (effectiveAct === 1) {
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng)
stampAct1InteriorStoneWalls(canvas, gridWidth, gridHeight, inX, outY, placedRects, rng, stats)
}
}
@ -5073,6 +5116,10 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
anchors: 0,
specialPresets: [],
entrances: [],
proceduralRoadCells: 0,
proceduralDividerCells: 0,
proceduralWaterCells: 0,
proceduralWallCells: 0,
substitutions: [],
substitutedClusters: 0,
objects: canvas.objects.length,
@ -5090,6 +5137,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
substitutions: [], borderPieces: {}, unresolved: [], notes: [],
borderStamped: 0, groundCells: 0, groundTile: null, groundTiles: undefined, sizeSource,
roadCells: 0, roadSegments: 0, anchors: 0, specialPresets: [], entrances: [],
proceduralRoadCells: 0, proceduralDividerCells: 0, proceduralWaterCells: 0, proceduralWallCells: 0,
}
let wallLayers = 1
@ -5214,6 +5262,10 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
anchors: stats.anchors,
entrances: stats.entrances,
specialPresets: stats.specialPresets,
proceduralRoadCells: stats.proceduralRoadCells ?? 0,
proceduralDividerCells: stats.proceduralDividerCells ?? 0,
proceduralWaterCells: stats.proceduralWaterCells ?? 0,
proceduralWallCells: stats.proceduralWallCells ?? 0,
substitutions: stats.substitutions,
substitutedClusters: placedSpecial.size,
objects: canvas.objects.length,
@ -5281,6 +5333,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
dirtPathGrid = roadRes.dirtPathGrid
stats.roadCells = roadRes.roadCells
stats.roadSegments = roadRes.roadSegments
stats.proceduralRoadCells = (stats.proceduralRoadCells ?? 0) + roadRes.roadCells
}
}
@ -5437,6 +5490,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
request.levelTypeName,
act1Plan?.openGates,
request.levelsTable,
stats,
)
stampOutdoorDividers(
canvas,
@ -5450,6 +5504,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
rng,
request.levelTypeName,
request.levelsTable,
stats,
)
// Act 2 Desert Topography: Canyons, oasis pools, sand caravan trails, tomb entrances (DRLGOUTDESR_InitAct2OutdoorLevel)
@ -5572,6 +5627,10 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
act3Topography: stats.act3Topography,
act4Topography: stats.act4Topography,
act5Topography: stats.act5Topography,
proceduralRoadCells: stats.proceduralRoadCells ?? 0,
proceduralDividerCells: stats.proceduralDividerCells ?? 0,
proceduralWaterCells: stats.proceduralWaterCells ?? 0,
proceduralWallCells: stats.proceduralWallCells ?? 0,
cells: canvas.width * canvas.height,
unresolved: stats.unresolved,
unimplementedPasses: UNIMPLEMENTED_PASSES,

View File

@ -43,6 +43,10 @@ function makeStats(): WildernessStats {
anchors: 0,
specialPresets: [],
entrances: [],
proceduralRoadCells: 0,
proceduralDividerCells: 0,
proceduralWaterCells: 0,
proceduralWallCells: 0,
}
}
@ -276,3 +280,109 @@ describe('BordType 0 really is one cluster for the whole level', () => {
})
})
describe('Procedural terrain pass telemetry tracks real cell counts (Issue #62)', () => {
test('Act 1 wilderness tracks procedural road and divider wall cells matching canvas', () => {
const result = generateWilderness({
levelId: 2,
levelName: 'Cold Plains',
levelTypeName: 'Act 1 - Wilderness',
sizeX: 64,
sizeY: 64,
subType: 1,
subTheme: 0,
seed: 2001,
pieces: makeMockBorderPieces('Act 1'),
substitutions: [],
})
expect(result.stats.proceduralRoadCells).toBeGreaterThan(0)
expect(result.stats.proceduralDividerCells).toBeGreaterThan(0)
expect(result.stats.proceduralWallCells).toBe(result.stats.proceduralDividerCells)
// Count actual divider wall cells on canvas (walls with style 2, prop1 129)
let wallCount = 0
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.walls.some(w => !w.hidden && w.style === 2 && w.prop1 === 129)) {
wallCount += 1
}
}
}
expect(result.stats.proceduralWallCells).toBe(wallCount)
})
test('Act 2 desert tracks procedural roads, water (oasis), and walls (canyon + berms)', () => {
const rocky = generateWilderness({
levelId: 41,
levelName: 'Rocky Waste',
levelTypeName: 'Act 2 - Desert',
sizeX: 64,
sizeY: 64,
subType: 0,
subTheme: 0,
seed: 41001,
pieces: makeMockBorderPieces('Act 2'),
substitutions: [],
})
expect(rocky.stats.proceduralRoadCells).toBeGreaterThan(0)
expect(rocky.stats.proceduralWallCells).toBeGreaterThan(0)
const oasis = generateWilderness({
levelId: 43,
levelName: 'Far Oasis',
levelTypeName: 'Act 2 - Desert',
sizeX: 64,
sizeY: 64,
subType: 0,
subTheme: 0,
seed: 43001,
pieces: makeMockBorderPieces('Act 2'),
substitutions: [],
})
expect(oasis.stats.proceduralRoadCells).toBeGreaterThan(0)
expect(oasis.stats.proceduralWaterCells).toBeGreaterThan(0)
})
test('Act 3 Kurast Causeway tracks causeway, canal, and balustrade cells', () => {
const result = generateWilderness({
levelId: 82,
levelName: 'Kurast Causeway',
levelTypeName: 'Act 3 - Kurast',
sizeX: 32,
sizeY: 64,
subType: 0,
subTheme: 0,
seed: 82001,
pieces: [{ name: 'Act 3 - Bridge', border: false, levels: [makeMockDs1(16, 16)] }],
substitutions: [],
})
expect(result.stats.proceduralRoadCells).toBeGreaterThan(0)
expect(result.stats.proceduralWaterCells).toBeGreaterThan(0)
expect(result.stats.proceduralWallCells).toBeGreaterThan(0)
})
test('Act 5 Siege levels track barricade walls, trenches, and frozen lakes', () => {
const result = generateWilderness({
levelId: 112,
levelName: 'Arreat Plateau',
levelTypeName: 'Act 5 - Barricade',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 112001,
pieces: makeMockBorderPieces('Act 5'),
substitutions: [],
})
expect(result.stats.proceduralWallCells).toBeGreaterThan(0)
expect(result.stats.proceduralRoadCells).toBeGreaterThan(0)
expect(result.stats.proceduralWaterCells).toBeGreaterThan(0)
})
})