From d0efb04395c3d8c9ff8bc194d32c697bc6f03f64 Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Fri, 18 Sep 2026 01:38:00 +0000 Subject: [PATCH] =?UTF-8?q?refactor(wilderness):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=87=AD=E7=A9=BA=E5=8F=91=E6=98=8E=E7=9A=84=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=8C=96=E5=8D=81=E5=AD=97=E7=9F=AE=E7=9F=B3=E5=A2=99=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=EF=BC=8C=E5=9B=9E=E5=BD=92=E5=8E=9F=E7=89=88=E8=87=AA?= =?UTF-8?q?=E7=84=B6=E5=9C=B0=E8=B2=8C=E9=98=BB=E9=9A=94=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 stampThemedDividers 重构为兼容性 no-op,完全废弃人为硬编码的十字切分矮石墙 - generateWilderness 移除 stampOutdoorDividers 调用,回归原版 Act 1-5 依据 DS1 预设边界及地形生成的自然阻隔机制 - 更新 wilderness 单元与多幕道路测试,验证 0 人造矮石墙及真实地貌阻隔保留 TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1 --- src/game/wilderness.ts | 161 ++--------------------- tests/wilderness-features.test.ts | 8 +- tests/wilderness-roads-multi-act.test.ts | 47 +++---- tests/wilderness-telemetry.test.ts | 12 +- 4 files changed, 44 insertions(+), 184 deletions(-) diff --git a/src/game/wilderness.ts b/src/game/wilderness.ts index b0e662d..2908635 100644 --- a/src/game/wilderness.ts +++ b/src/game/wilderness.ts @@ -3327,145 +3327,24 @@ export function stampThemedRoads( } /** - * General outdoor divider network laying connected stone walls, sandstone berms, - * or balustrades with proper corner joins, gate archways, and endcaps. + * General outdoor divider network. + * @deprecated Procedural cross divider networks were removed in Issue #65 to restore + * Blizzard DRLG fidelity. In original Diablo II, wilderness barriers are formed naturally + * by cliff borders, water bodies, and LvlSub substitutions without artificial cross stone walls. */ export function stampThemedDividers( - canvas: Canvas, - gridWidth: number, - gridHeight: number, - inX: number, - outY: number, - placedRects: readonly PlacedPresetRect[], - rng: Rng, - theme: OutdoorDividerTheme, + _canvas: Canvas, + _gridWidth: number, + _gridHeight: number, + _inX: number, + _outY: number, + _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() - - const makeWall = (type: number, sequence: number): Ds1Wall => ({ - prop1: wallProp1, - sequence, - style: wallStyle, - type, - unknown1: 0, - unknown2: 0, - hidden: false, - }) - - const placeWall = (x: number, y: number, wall: Ds1Wall): void => { - const cell = canvas.cells[y]?.[x] - if (!cell) return - const targetWalls = cell.walls as Ds1Wall[] - if (targetWalls.length > 0) { - targetWalls[0] = wall - } else { - targetWalls.push(wall) - } - placedCells.add(`${x},${y}`) - } - - // Offset divider lines by 2 blocks (16 tiles) from the main road spine - const offsetBlockY = rng.int(0, 1) === 0 ? -2 : 2 - const offsetBlockX = rng.int(0, 1) === 0 ? -2 : 2 - const wallY = Math.max(12, Math.min(canvas.height - 12, (outY + offsetBlockY) * TILES_PER_BLOCK)) - const wallX = Math.max(12, Math.min(canvas.width - 12, (inX + offsetBlockX) * TILES_PER_BLOCK)) - - const isCellBlockedForWall = (x: number, y: number): boolean => { - if (x < 6 || x >= canvas.width - 6 || y < 6 || y >= canvas.height - 6) return true - // Leave 2-tile margin around all placed special presets - for (const r of placedRects) { - if (x >= r.x - 2 && x < r.x + r.w + 2 && y >= r.y - 2 && y < r.y + r.h + 2) { - return true - } - } - // Leave 3-tile wide archway gate openings wherever a road, bridge, or water passes - for (let dy = -1; dy <= 1; dy += 1) { - for (let dx = -1; dx <= 1; dx += 1) { - const c = canvas.cells[y + dy]?.[x + dx] - if (!c) continue - if (c.floors.some(f => !f.hidden && (f.style === 5 || f.style === 1 || f.style === 4 || f.style === 2 || f.style === 3 || theme.roadStyles?.includes(f.style)))) { - return true - } - } - } - const cell = canvas.cells[y]?.[x] - if (!cell) return true - if (cell.walls.some(w => !w.hidden && w.prop1 !== 0)) return true - return false - } - - // Build valid tile masks along wallY (EW) and wallX (NS), inserting periodic pasture gates - const validEW: boolean[] = Array.from({ length: canvas.width }, (_, x) => { - const isPastureGate = (x % 24) >= 11 && (x % 24) <= 13 && Math.abs(x - wallX) > 3 - return !isPastureGate && !isCellBlockedForWall(x, wallY) - }) - const validNS: boolean[] = Array.from({ length: canvas.height }, (_, y) => { - const isPastureGate = (y % 24) >= 11 && (y % 24) <= 13 && Math.abs(y - wallY) > 3 - return !isPastureGate && !isCellBlockedForWall(wallX, y) - }) - - const extractRuns = (mask: readonly boolean[]): Array<{ start: number; end: number }> => { - const runs: Array<{ start: number; end: number }> = [] - let start = -1 - for (let i = 0; i < mask.length; i += 1) { - if (mask[i]) { - if (start === -1) start = i - } else if (start !== -1) { - if (i - start >= 4) runs.push({ start, end: i - 1 }) - start = -1 - } - } - if (start !== -1 && mask.length - start >= 4) { - runs.push({ start, end: mask.length - 1 }) - } - return runs - } - - const ewRuns = extractRuns(validEW) - const nsRuns = extractRuns(validNS) - - const ewCoversCorner = ewRuns.some(r => wallX >= r.start && wallX <= r.end) - const nsCoversCorner = nsRuns.some(r => wallY >= r.start && wallY <= r.end) - const hasCornerJoin = ewCoversCorner && nsCoversCorner - - // Stamp East-West divider wall segments (`type = 2`) - for (const run of ewRuns) { - for (let x = run.start; x <= run.end; x += 1) { - if (x === wallX && hasCornerJoin) { - placeWall(x, wallY, makeWall(3, 0)) // Corner / Cross join (`type=3`) - } else if (x === run.start) { - placeWall(x, wallY, makeWall(2, 1)) // West / Right-of-gap End-Cap (`type=2, seq=1`) - } else if (x === run.end) { - placeWall(x, wallY, makeWall(2, 2)) // East / Left-of-gap End-Cap (`type=2, seq=2`) - } else { - placeWall(x, wallY, makeWall(2, 0)) // Continuous EW Wall (`type=2, seq=0`) - } - } - } - - // Stamp North-South divider wall segments (`type = 1`) - for (const run of nsRuns) { - for (let y = run.start; y <= run.end; y += 1) { - if (y === wallY && hasCornerJoin) { - placeWall(wallX, y, makeWall(3, 0)) // Corner / Cross join (`type=3`) - } else if (y === run.start) { - placeWall(wallX, y, makeWall(1, 1)) // North / Bottom-of-gap End-Cap (`type=1, seq=1`) - } else if (y === run.end) { - placeWall(wallX, y, makeWall(1, 2)) // South / Top-of-gap End-Cap (`type=1, seq=2`) - } else { - placeWall(wallX, y, makeWall(1, 0)) // Continuous NS Wall (`type=1, seq=0`) - } - } - } - if (stats) { - stats.proceduralDividerCells = (stats.proceduralDividerCells ?? 0) + placedCells.size - stats.proceduralWallCells = (stats.proceduralWallCells ?? 0) + placedCells.size + stats.proceduralDividerCells = 0 } } @@ -5480,20 +5359,6 @@ export function generateWilderness(request: WildernessRequest): WildernessResult request.levelsTable, stats, ) - stampOutdoorDividers( - canvas, - act, - request.levelId, - gridWidth, - gridHeight, - inX, - outY, - allPlacedRects, - rng, - request.levelTypeName, - request.levelsTable, - stats, - ) // Act 2 Desert Topography: Canyons, oasis pools, sand caravan trails, tomb entrances (DRLGOUTDESR_InitAct2OutdoorLevel) if ( diff --git a/tests/wilderness-features.test.ts b/tests/wilderness-features.test.ts index 14f20f0..e1b9581 100644 --- a/tests/wilderness-features.test.ts +++ b/tests/wilderness-features.test.ts @@ -194,7 +194,7 @@ describe("Wilderness Features & Border Rules", () => { } } - expect(stoneWallCount).toBeGreaterThan(20) + expect(stoneWallCount).toBe(0) expect(dirtRoadCount).toBeGreaterThan(30) }) @@ -364,7 +364,7 @@ describe("Wilderness Features & Border Rules", () => { expect(result.level.cells[75]![44]!.floors[0]!.style).toBe(5) expect(result.level.cells[44]![75]!.floors[0]!.style).toBe(5) - // 3. Verify interior stone wall segments (style=2, prop1=129) use continuous sequence=0 and end-caps sequence=1/2 + // 3. Verify no artificial interior stone wall segments (style=2, prop1=129) are stamped (Issue #65) let foundContinuousEW = false let foundEndCapEW = false for (let y = 10; y < 70; y += 1) { @@ -376,8 +376,8 @@ describe("Wilderness Features & Border Rules", () => { } } } - expect(foundContinuousEW).toBe(true) - expect(foundEndCapEW).toBe(true) + expect(foundContinuousEW).toBe(false) + expect(foundEndCapEW).toBe(false) }) test("records unresolved presets and notes when requested preset piece is missing", () => { diff --git a/tests/wilderness-roads-multi-act.test.ts b/tests/wilderness-roads-multi-act.test.ts index 8166ca3..120dac8 100644 --- a/tests/wilderness-roads-multi-act.test.ts +++ b/tests/wilderness-roads-multi-act.test.ts @@ -116,7 +116,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { expect(spurFound).toBe(true) }) - it('stampAct2SandstoneBerms paints sandstone berms with style: 1, prop1: 129, corner joins, and road archway breaks', () => { + it('stampAct2SandstoneBerms is a no-op removing artificial cross dividers (Issue #65)', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(42) const placedRects: PlacedPresetRect[] = [] @@ -126,12 +126,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { stampAct2SandstoneBerms(canvas, gridW, gridH, inX, outY, placedRects, rng) const bermWalls = countWallTiles(canvas, 1, 129) - expect(bermWalls).toBeGreaterThan(20) - - // Berms should not block the road where the road crosses the divider - // Highway crosses dividing lines at (inX * 8 + 4, outY * 8 + 4) - const crossRoad = canvas.cells[outY * TILES_PER_BLOCK + 4]?.[inX * TILES_PER_BLOCK + 4] - expect(crossRoad?.walls.some(w => w.style === 1 && w.prop1 === 129)).toBe(false) + expect(bermWalls).toBe(0) }) }) @@ -149,7 +144,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { expect(avenueFloors).toBeGreaterThan(50) }) - it('stampAct3KurastBalustrades paints balustrades with style: 1, prop1: 129', () => { + it('stampAct3KurastBalustrades is a no-op removing artificial cross dividers (Issue #65)', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(100) const placedRects: PlacedPresetRect[] = [] @@ -158,7 +153,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { stampAct3KurastBalustrades(canvas, gridW, gridH, inX, outY, placedRects, rng) const balustradeWalls = countWallTiles(canvas, 1, 129) - expect(balustradeWalls).toBeGreaterThan(20) + expect(balustradeWalls).toBe(0) }) }) @@ -176,7 +171,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { expect(snowRoadFloors).toBeGreaterThan(50) }) - it('stampAct5FrozenTrailDivides paints frozen trail divides with style: 2, prop1: 129', () => { + it('stampAct5FrozenTrailDivides is a no-op removing artificial cross dividers (Issue #65)', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(555) const placedRects: PlacedPresetRect[] = [] @@ -185,49 +180,49 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { stampAct5FrozenTrailDivides(canvas, gridW, gridH, inX, outY, placedRects, rng) const frozenDivides = countWallTiles(canvas, 2, 129) - expect(frozenDivides).toBeGreaterThan(20) + expect(frozenDivides).toBe(0) }) }) describe('Unified Dispatchers: stampOutdoorRoads & stampOutdoorDividers', () => { - it('dispatches Act 2 Desert based on levelId 41', () => { + it('dispatches Act 2 Desert based on levelId 41 without artificial cross dividers', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(123) stampOutdoorRoads(canvas, 1, 41, gridW, gridH, inX, outY, [], rng) stampOutdoorDividers(canvas, 1, 41, gridW, gridH, inX, outY, [], rng) expect(countFloorTiles(canvas, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(canvas, 1, 129)).toBeGreaterThan(15) + expect(countWallTiles(canvas, 1, 129)).toBe(0) }) - it('dispatches Act 3 Kurast based on levelId 80', () => { + it('dispatches Act 3 Kurast based on levelId 80 without artificial cross dividers', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(123) stampOutdoorRoads(canvas, 1, 80, gridW, gridH, inX, outY, [], rng) stampOutdoorDividers(canvas, 1, 80, gridW, gridH, inX, outY, [], rng) expect(countFloorTiles(canvas, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(canvas, 1, 129)).toBeGreaterThan(15) + expect(countWallTiles(canvas, 1, 129)).toBe(0) }) - it('dispatches Act 5 Snow based on levelId 111', () => { + it('dispatches Act 5 Snow based on levelId 111 without artificial cross dividers', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(123) stampOutdoorRoads(canvas, 1, 111, gridW, gridH, inX, outY, [], rng) stampOutdoorDividers(canvas, 1, 111, gridW, gridH, inX, outY, [], rng) expect(countFloorTiles(canvas, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(canvas, 2, 129)).toBeGreaterThan(15) + expect(countWallTiles(canvas, 2, 129)).toBe(0) }) - it('dispatches Act 1 Dirt Roads based on levelId 3', () => { + it('dispatches Act 1 Dirt Roads based on levelId 3 without artificial cross dividers', () => { const canvas = createCanvas(width, height, 1, 1, 0) const rng = new Rng(123) stampOutdoorRoads(canvas, 1, 3, gridW, gridH, inX, outY, [], rng) stampOutdoorDividers(canvas, 1, 3, gridW, gridH, inX, outY, [], rng) expect(countFloorTiles(canvas, 5, 194)).toBeGreaterThan(40) - expect(countWallTiles(canvas, 2, 129)).toBeGreaterThan(15) + expect(countWallTiles(canvas, 2, 129)).toBe(0) }) it('dispatches based on levelTypeName when levelId is generic', () => { @@ -235,19 +230,19 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { stampOutdoorRoads(c2, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 2 - Desert') stampOutdoorDividers(c2, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 2 - Desert') expect(countFloorTiles(c2, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(c2, 1, 129)).toBeGreaterThan(15) + expect(countWallTiles(c2, 1, 129)).toBe(0) const c3 = createCanvas(width, height, 1, 1, 0) stampOutdoorRoads(c3, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 3 - Kurast') stampOutdoorDividers(c3, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 3 - Kurast') expect(countFloorTiles(c3, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(c3, 1, 129)).toBeGreaterThan(15) + expect(countWallTiles(c3, 1, 129)).toBe(0) const c5 = createCanvas(width, height, 1, 1, 0) stampOutdoorRoads(c5, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 5 - Barricade') stampOutdoorDividers(c5, 1, 999, gridW, gridH, inX, outY, [], new Rng(1), 'Act 5 - Barricade') expect(countFloorTiles(c5, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(c5, 2, 129)).toBeGreaterThan(15) + expect(countWallTiles(c5, 2, 129)).toBe(0) }) it('dispatches based on act number fallback', () => { @@ -255,7 +250,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { stampOutdoorRoads(c2, 2, 999, gridW, gridH, inX, outY, [], new Rng(1)) stampOutdoorDividers(c2, 2, 999, gridW, gridH, inX, outY, [], new Rng(1)) expect(countFloorTiles(c2, 1, 194)).toBeGreaterThan(40) - expect(countWallTiles(c2, 1, 129)).toBeGreaterThan(15) + expect(countWallTiles(c2, 1, 129)).toBe(0) }) it('strictly skips Cow Level (levelId 39)', () => { @@ -279,7 +274,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { levels: [borderDs1], } - it('generates Act 2 Rocky Waste (Level 41) with caravan routes and sandstone berms', () => { + it('generates Act 2 Rocky Waste (Level 41) with caravan routes and authentic canyon topography walls', () => { const result = generateWilderness({ levelId: 41, levelName: 'Rocky Waste', @@ -309,7 +304,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { expect(sandstoneBerms).toBeGreaterThan(15) }) - it('generates Act 3 Kurast Bazaar (Level 80) with Kurast avenues and balustrades', () => { + it('generates Act 3 Kurast Bazaar (Level 80) with Kurast avenues and authentic causeway balustrades', () => { const act3Border: WildernessPiece = { name: 'Act 3 - Kurast Border', border: true, @@ -344,7 +339,7 @@ describe('Multi-Act Outdoor Roads & Boundary Divider Networks', () => { expect(balustrades).toBeGreaterThan(15) }) - it('generates Act 5 Frigid Highlands (Level 111) with snow roads and frozen trail divides', () => { + it('generates Act 5 Frigid Highlands (Level 111) with snow roads and authentic siege barricade walls', () => { const act5Border: WildernessPiece = { name: 'Act 5 - Barricade Border', border: true, diff --git a/tests/wilderness-telemetry.test.ts b/tests/wilderness-telemetry.test.ts index a73dc4d..8bf937d 100644 --- a/tests/wilderness-telemetry.test.ts +++ b/tests/wilderness-telemetry.test.ts @@ -280,8 +280,8 @@ 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', () => { +describe('Procedural terrain pass telemetry tracks real cell counts (Issue #62 & #65)', () => { + test('Act 1 wilderness tracks procedural road cells with zero artificial dividers (Issue #65)', () => { const result = generateWilderness({ levelId: 2, levelName: 'Cold Plains', @@ -296,10 +296,10 @@ describe('Procedural terrain pass telemetry tracks real cell counts (Issue #62)' }) expect(result.stats.proceduralRoadCells).toBeGreaterThan(0) - expect(result.stats.proceduralDividerCells).toBeGreaterThan(0) - expect(result.stats.proceduralWallCells).toBe(result.stats.proceduralDividerCells) + expect(result.stats.proceduralDividerCells).toBe(0) + expect(result.stats.proceduralWallCells).toBe(0) - // Count actual divider wall cells on canvas (walls with style 2, prop1 129) + // Verify 0 artificial 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) { @@ -309,7 +309,7 @@ describe('Procedural terrain pass telemetry tracks real cell counts (Issue #62)' } } } - expect(result.stats.proceduralWallCells).toBe(wallCount) + expect(wallCount).toBe(0) }) test('Act 2 desert tracks procedural roads, water (oasis), and walls (canyon + berms)', () => {