diff --git a/src/game/level-links.ts b/src/game/level-links.ts index b020089..e83c909 100644 --- a/src/game/level-links.ts +++ b/src/game/level-links.ts @@ -516,8 +516,7 @@ export function findBorderOpening( } /** - * Choose a topologically suitable spot to place an outdoor or indoor waypoint - * (`DRLGOUTDOORS_SpawnAct12Waypoint`). + * Choose a topologically suitable spot to place an outdoor or indoor waypoint. * * Evaluates candidate clearance (at least 3x3 sub-tiles of clear walkable space) * 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) } - -export const DRLGOUTDOORS_SpawnAct12Waypoint = findWaypointSpot diff --git a/src/game/wilderness.ts b/src/game/wilderness.ts index ed1f35f..a14c757 100644 --- a/src/game/wilderness.ts +++ b/src/game/wilderness.ts @@ -1252,6 +1252,9 @@ export const NEIGHBOR_OFFSETS: readonly NeighborOffset[] = [ * Computes the 8-neighbor bitmask (0..255) for cell (x, y) on a canvas * 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. */ export function computeNeighborMask( @@ -1279,8 +1282,6 @@ export function computeNeighborMask( return mask } -export const DRLGROOMTILE_ComputeNeighborMask = computeNeighborMask - /** * 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 * 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 * mutation cascade. Guarantees 0 missing tiles when DT1 libraries are supplied by strictly verifying * 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 * reproduce. Reported verbatim in every result's `stats.unimplementedPasses` so diff --git a/tests/drlg-alias-guard.test.ts b/tests/drlg-alias-guard.test.ts new file mode 100644 index 0000000..ef715c3 --- /dev/null +++ b/tests/drlg-alias-guard.test.ts @@ -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[] => { + 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) + 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).computeNeighborMask).toBe("function") + expect(typeof (wilderness as Record).applyGroundTransitions).toBe("function") + }) + + test("src/game/wilderness.ts keeps single export of DRLGOUTDOORS_SpawnAct12Waypoint", () => { + const exports = getDrlgExports(wilderness as Record) + 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) + 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) + 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), + ...getDrlgExports(maze as Record), + ...getDrlgExports(levelLinks as Record), + ] + + 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/) + }) +}) diff --git a/tests/wilderness-ground-blend.test.ts b/tests/wilderness-ground-blend.test.ts index c95deae..3451a57 100644 --- a/tests/wilderness-ground-blend.test.ts +++ b/tests/wilderness-ground-blend.test.ts @@ -11,7 +11,6 @@ import { generateWilderness, CANONICAL_GROUND_POOLS, computeNeighborMask, - DRLGROOMTILE_ComputeNeighborMask, NEIGHBOR_MASK_NE, NEIGHBOR_MASK_E, NEIGHBOR_MASK_SE, @@ -25,7 +24,6 @@ import { WATER_EDGE_TILE_LUT, DRLGROOMTILE_GetTileCache, applyGroundTransitions, - DRLGROOMTILE_ApplyTransitions, } from "../src/game/wilderness.ts" import type { WeightedGroundTile, @@ -671,10 +669,6 @@ describe("Wilderness Ground Tile Blending (Issue #52)", () => { 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", () => { expect(NEIGHBOR_OFFSETS).toHaveLength(8) 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", () => { expect(WATER_EDGE_TILE_LUT).toHaveLength(256) expect(WATER_EDGE_TILE_LUT[0xFF]).toBe(24) diff --git a/tests/wilderness-waypoints-shrines.test.ts b/tests/wilderness-waypoints-shrines.test.ts index 75c4f13..218625e 100644 --- a/tests/wilderness-waypoints-shrines.test.ts +++ b/tests/wilderness-waypoints-shrines.test.ts @@ -24,7 +24,6 @@ import { import { Rng } from "../src/game/rng.ts" import { findWaypointSpot, - DRLGOUTDOORS_SpawnAct12Waypoint as linksSpawnAct12Waypoint, type LinkGrid, } from "../src/game/level-links.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!.y).toBeGreaterThanOrEqual(8) 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) }) })