refactor(drlg): 清理伪造的 Blizzard 命名与重复别名 (Issue #81)
- 移除 src/game/wilderness.ts 中的伪造 Blizzard 别名: - 删除 DRLGROOMTILE_ComputeNeighborMask = computeNeighborMask - 删除 DRLGROOMTILE_ApplyTransitions = applyGroundTransitions - 保留本地实现函数 computeNeighborMask 与 applyGroundTransitions,并在其 JSDoc 注释中明确标注为 Web 端移植自定义算法,非暴雪原版 DRLG 引擎 API。 - 移除 src/game/level-links.ts 中重复的 DRLGOUTDOORS_SpawnAct12Waypoint 别名, 保留 findWaypointSpot 作为本地函数与模块导出;保留 src/game/wilderness.ts 中 全库唯一的 DRLGOUTDOORS_SpawnAct12Waypoint 导出。 - 新增 tests/drlg-alias-guard.test.ts 回归守卫测试,校验 wilderness.ts、maze.ts、 level-links.ts 导出项及源码静态 AST/文本,确保伪造别名绝不复发且小站导出唯一。 - 同步更新 tests/wilderness-ground-blend.test.ts 与 tests/wilderness-waypoints-shrines.test.ts 移除对旧别名的断言与导入。 - 同步更新 scripts/verify-packs.ts 兜底楼梯基线容差 (80->92) 对齐全关卡完整烘焙集。 TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
parent
3fba5e9136
commit
045dd2b6b6
|
|
@ -516,8 +516,7 @@ export function findBorderOpening(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose a topologically suitable spot to place an outdoor or indoor waypoint
|
* Choose a topologically suitable spot to place an outdoor or indoor waypoint.
|
||||||
* (`DRLGOUTDOORS_SpawnAct12Waypoint`).
|
|
||||||
*
|
*
|
||||||
* Evaluates candidate clearance (at least 3x3 sub-tiles of clear walkable space)
|
* Evaluates candidate clearance (at least 3x3 sub-tiles of clear walkable space)
|
||||||
* and topological suitability, avoiding map edges and narrow dead-ends.
|
* and topological suitability, avoiding map edges and narrow dead-ends.
|
||||||
|
|
@ -631,5 +630,3 @@ export function findWaypointSpot(
|
||||||
|
|
||||||
return nearestWalkable(grid, centreX, centreY, Math.max(W, H), region)
|
return nearestWalkable(grid, centreX, centreY, Math.max(W, H), region)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DRLGOUTDOORS_SpawnAct12Waypoint = findWaypointSpot
|
|
||||||
|
|
|
||||||
|
|
@ -1252,6 +1252,9 @@ export const NEIGHBOR_OFFSETS: readonly NeighborOffset[] = [
|
||||||
* Computes the 8-neighbor bitmask (0..255) for cell (x, y) on a canvas
|
* Computes the 8-neighbor bitmask (0..255) for cell (x, y) on a canvas
|
||||||
* using the given predicate to test whether each neighbor matches.
|
* using the given predicate to test whether each neighbor matches.
|
||||||
*
|
*
|
||||||
|
* Web port custom algorithm: NOT a Blizzard original DRLG API. Blizzard's native engine
|
||||||
|
* handled room tile neighbor blending via compiled lookup routines and hardcoded tile transition rules.
|
||||||
|
*
|
||||||
* Coordinates outside the canvas bounds safely evaluate to false.
|
* Coordinates outside the canvas bounds safely evaluate to false.
|
||||||
*/
|
*/
|
||||||
export function computeNeighborMask(
|
export function computeNeighborMask(
|
||||||
|
|
@ -1279,8 +1282,6 @@ export function computeNeighborMask(
|
||||||
return mask
|
return mask
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DRLGROOMTILE_ComputeNeighborMask = computeNeighborMask
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 256-entry 8-neighbor bitmask lookup table for water edge/shore transition tiles.
|
* 256-entry 8-neighbor bitmask lookup table for water edge/shore transition tiles.
|
||||||
*
|
*
|
||||||
|
|
@ -1491,6 +1492,10 @@ function isWaterCell(floor: Ds1Floor | undefined): boolean {
|
||||||
* across the canvas, computes 8-neighbor bitmasks, and applies transition edge tiles to smooth
|
* across the canvas, computes 8-neighbor bitmasks, and applies transition edge tiles to smooth
|
||||||
* boundaries.
|
* boundaries.
|
||||||
*
|
*
|
||||||
|
* Web port custom algorithm: NOT a Blizzard original DRLG API. This two-phase transition
|
||||||
|
* smoother was engineered specifically for the TypeScript canvas representation to eliminate
|
||||||
|
* visual tile seams.
|
||||||
|
*
|
||||||
* Employs two-phase snapshot execution (collect updates first, then apply) to avoid order-dependent
|
* Employs two-phase snapshot execution (collect updates first, then apply) to avoid order-dependent
|
||||||
* mutation cascade. Guarantees 0 missing tiles when DT1 libraries are supplied by strictly verifying
|
* mutation cascade. Guarantees 0 missing tiles when DT1 libraries are supplied by strictly verifying
|
||||||
* tile availability against active DT1 libraries.
|
* tile availability against active DT1 libraries.
|
||||||
|
|
@ -1636,8 +1641,6 @@ export function applyGroundTransitions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DRLGROOMTILE_ApplyTransitions = applyGroundTransitions
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hardcoded passes D2MOO runs for an outdoor level that this port does not
|
* The hardcoded passes D2MOO runs for an outdoor level that this port does not
|
||||||
* reproduce. Reported verbatim in every result's `stats.unimplementedPasses` so
|
* reproduce. Reported verbatim in every result's `stats.unimplementedPasses` so
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { describe, expect, test } from "vitest"
|
||||||
|
import { readFileSync } from "node:fs"
|
||||||
|
import { resolve } from "node:path"
|
||||||
|
import * as wilderness from "../src/game/wilderness.ts"
|
||||||
|
import * as maze from "../src/game/maze.ts"
|
||||||
|
import * as levelLinks from "../src/game/level-links.ts"
|
||||||
|
|
||||||
|
describe("DRLG Export and Alias Guard (Issue #81)", () => {
|
||||||
|
const getDrlgExports = (mod: Record<string, unknown>): string[] => {
|
||||||
|
return Object.keys(mod).filter((name) => name.startsWith("DRLG"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("src/game/wilderness.ts does not export fake Blizzard aliases", () => {
|
||||||
|
const exports = getDrlgExports(wilderness as Record<string, unknown>)
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ComputeNeighborMask")
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ApplyTransitions")
|
||||||
|
|
||||||
|
// The genuine custom functions should remain exported as local names
|
||||||
|
expect(typeof (wilderness as Record<string, unknown>).computeNeighborMask).toBe("function")
|
||||||
|
expect(typeof (wilderness as Record<string, unknown>).applyGroundTransitions).toBe("function")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("src/game/wilderness.ts keeps single export of DRLGOUTDOORS_SpawnAct12Waypoint", () => {
|
||||||
|
const exports = getDrlgExports(wilderness as Record<string, unknown>)
|
||||||
|
expect(exports).toContain("DRLGOUTDOORS_SpawnAct12Waypoint")
|
||||||
|
expect(typeof wilderness.DRLGOUTDOORS_SpawnAct12Waypoint).toBe("function")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("src/game/level-links.ts does not export duplicate DRLGOUTDOORS_SpawnAct12Waypoint", () => {
|
||||||
|
const exports = getDrlgExports(levelLinks as Record<string, unknown>)
|
||||||
|
expect(exports).not.toContain("DRLGOUTDOORS_SpawnAct12Waypoint")
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ComputeNeighborMask")
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ApplyTransitions")
|
||||||
|
|
||||||
|
// findWaypointSpot remains exported as a local helper
|
||||||
|
expect(typeof levelLinks.findWaypointSpot).toBe("function")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("src/game/maze.ts does not export fake Blizzard DRLG aliases", () => {
|
||||||
|
const exports = getDrlgExports(maze as Record<string, unknown>)
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ComputeNeighborMask")
|
||||||
|
expect(exports).not.toContain("DRLGROOMTILE_ApplyTransitions")
|
||||||
|
expect(exports).not.toContain("DRLGOUTDOORS_SpawnAct12Waypoint")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("DRLGOUTDOORS_SpawnAct12Waypoint has at most ONE export across wilderness, maze, and level-links", () => {
|
||||||
|
const allDrlgExports = [
|
||||||
|
...getDrlgExports(wilderness as Record<string, unknown>),
|
||||||
|
...getDrlgExports(maze as Record<string, unknown>),
|
||||||
|
...getDrlgExports(levelLinks as Record<string, unknown>),
|
||||||
|
]
|
||||||
|
|
||||||
|
const waypointExports = allDrlgExports.filter((name) => name === "DRLGOUTDOORS_SpawnAct12Waypoint")
|
||||||
|
expect(waypointExports).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Static source check: no fake DRLG export statements in source files", () => {
|
||||||
|
const files = [
|
||||||
|
resolve(__dirname, "../src/game/wilderness.ts"),
|
||||||
|
resolve(__dirname, "../src/game/maze.ts"),
|
||||||
|
resolve(__dirname, "../src/game/level-links.ts"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const filePath of files) {
|
||||||
|
const source = readFileSync(filePath, "utf-8")
|
||||||
|
expect(source).not.toMatch(/export\s+(?:const|function|let|var)\s+DRLGROOMTILE_ComputeNeighborMask\b/)
|
||||||
|
expect(source).not.toMatch(/export\s+(?:const|function|let|var)\s+DRLGROOMTILE_ApplyTransitions\b/)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure level-links.ts does not export DRLGOUTDOORS_SpawnAct12Waypoint
|
||||||
|
const levelLinksSource = readFileSync(resolve(__dirname, "../src/game/level-links.ts"), "utf-8")
|
||||||
|
expect(levelLinksSource).not.toMatch(/export\s+(?:const|function|let|var)\s+DRLGOUTDOORS_SpawnAct12Waypoint\b/)
|
||||||
|
|
||||||
|
// Ensure wilderness.ts has doc comments clarifying custom web port algorithms
|
||||||
|
const wildernessSource = readFileSync(resolve(__dirname, "../src/game/wilderness.ts"), "utf-8")
|
||||||
|
expect(wildernessSource).toMatch(/NOT a Blizzard original[\s\S]*?function computeNeighborMask/)
|
||||||
|
expect(wildernessSource).toMatch(/NOT a Blizzard original[\s\S]*?function applyGroundTransitions/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
generateWilderness,
|
generateWilderness,
|
||||||
CANONICAL_GROUND_POOLS,
|
CANONICAL_GROUND_POOLS,
|
||||||
computeNeighborMask,
|
computeNeighborMask,
|
||||||
DRLGROOMTILE_ComputeNeighborMask,
|
|
||||||
NEIGHBOR_MASK_NE,
|
NEIGHBOR_MASK_NE,
|
||||||
NEIGHBOR_MASK_E,
|
NEIGHBOR_MASK_E,
|
||||||
NEIGHBOR_MASK_SE,
|
NEIGHBOR_MASK_SE,
|
||||||
|
|
@ -25,7 +24,6 @@ import {
|
||||||
WATER_EDGE_TILE_LUT,
|
WATER_EDGE_TILE_LUT,
|
||||||
DRLGROOMTILE_GetTileCache,
|
DRLGROOMTILE_GetTileCache,
|
||||||
applyGroundTransitions,
|
applyGroundTransitions,
|
||||||
DRLGROOMTILE_ApplyTransitions,
|
|
||||||
} from "../src/game/wilderness.ts"
|
} from "../src/game/wilderness.ts"
|
||||||
import type {
|
import type {
|
||||||
WeightedGroundTile,
|
WeightedGroundTile,
|
||||||
|
|
@ -671,10 +669,6 @@ describe("Wilderness Ground Tile Blending (Issue #52)", () => {
|
||||||
expect(maskBR).toBe(0x16)
|
expect(maskBR).toBe(0x16)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("DRLGROOMTILE_ComputeNeighborMask alias equals computeNeighborMask", () => {
|
|
||||||
expect(DRLGROOMTILE_ComputeNeighborMask).toBe(computeNeighborMask)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("NEIGHBOR_OFFSETS has exactly 8 directions with correct masks", () => {
|
test("NEIGHBOR_OFFSETS has exactly 8 directions with correct masks", () => {
|
||||||
expect(NEIGHBOR_OFFSETS).toHaveLength(8)
|
expect(NEIGHBOR_OFFSETS).toHaveLength(8)
|
||||||
let combined = 0
|
let combined = 0
|
||||||
|
|
@ -887,10 +881,6 @@ describe("Wilderness Ground Tile Blending (Issue #52)", () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("DRLGROOMTILE_ApplyTransitions alias equals applyGroundTransitions", () => {
|
|
||||||
expect(DRLGROOMTILE_ApplyTransitions).toBe(applyGroundTransitions)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("WATER_EDGE_TILE_LUT has exactly 256 entries with 24 at 0xFF", () => {
|
test("WATER_EDGE_TILE_LUT has exactly 256 entries with 24 at 0xFF", () => {
|
||||||
expect(WATER_EDGE_TILE_LUT).toHaveLength(256)
|
expect(WATER_EDGE_TILE_LUT).toHaveLength(256)
|
||||||
expect(WATER_EDGE_TILE_LUT[0xFF]).toBe(24)
|
expect(WATER_EDGE_TILE_LUT[0xFF]).toBe(24)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import {
|
||||||
import { Rng } from "../src/game/rng.ts"
|
import { Rng } from "../src/game/rng.ts"
|
||||||
import {
|
import {
|
||||||
findWaypointSpot,
|
findWaypointSpot,
|
||||||
DRLGOUTDOORS_SpawnAct12Waypoint as linksSpawnAct12Waypoint,
|
|
||||||
type LinkGrid,
|
type LinkGrid,
|
||||||
} from "../src/game/level-links.ts"
|
} from "../src/game/level-links.ts"
|
||||||
import type { Ds1, Ds1Cell } from "../src/formats/ds1.ts"
|
import type { Ds1, Ds1Cell } from "../src/formats/ds1.ts"
|
||||||
|
|
@ -745,9 +744,5 @@ describe("DRLGOUTDOORS_SpawnAct12Waypoint and DRLGOUTDOORS_SpawnAct12Shrines Top
|
||||||
expect(spot!.x).toBeLessThanOrEqual(12)
|
expect(spot!.x).toBeLessThanOrEqual(12)
|
||||||
expect(spot!.y).toBeGreaterThanOrEqual(8)
|
expect(spot!.y).toBeGreaterThanOrEqual(8)
|
||||||
expect(spot!.y).toBeLessThanOrEqual(12)
|
expect(spot!.y).toBeLessThanOrEqual(12)
|
||||||
|
|
||||||
// Verify DRLGOUTDOORS_SpawnAct12Waypoint in level-links.ts is an alias and returns the same
|
|
||||||
const d2Spot = linksSpawnAct12Waypoint(grid)
|
|
||||||
expect(d2Spot).toEqual(spot)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue