diablo2-web/tests/issue-606-614-621-parity.te...

232 lines
7.1 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
applyLightingInputs,
CANONICAL_MONSTER_LIGHTS,
CANONICAL_OBJECT_LIGHTS,
CANONICAL_MISSILE_LIGHTS,
CANONICAL_OVERLAY_LIGHTS,
getMonsterLightConfig,
getObjectLightConfig,
getMissileLightConfig,
getOverlayLightConfig,
} from '../src/client/scene/lighting-presets.ts'
import type { CollisionGrid } from '../src/common/world/d2map.ts'
import { LightGrid, LIGHT_GRID_SIZE } from '../src/common/drlg/light-grid.ts'
import {
velocityToDccDirection,
pointsToDccDirection,
} from '../src/client/render/missiles-meta.ts'
import {
fragmentShaderSource,
} from '../src/client/render/renderer.ts'
describe('Issue #606: Data-Driven Point Lights & Authentic 1.13c Player Radius', () => {
const mockCollision: CollisionGrid = {
cellsX: 10,
cellsY: 10,
gridWidth: 50,
gridHeight: 50,
originX: 0,
originY: 0,
blocked: new Uint8Array(2500),
collisionMasks: new Uint16Array(2500),
} as unknown as CollisionGrid
it('provides base 1.13c player light radius of 11 sub-tiles with pure white color', () => {
const lightGrid = new LightGrid()
let appliedLighting: any = null
const renderer = {
setLighting: (params: any) => {
appliedLighting = params
},
}
applyLightingInputs(
{
collisionGrid: mockCollision,
playerSubTile: { x: 50, y: 50 },
pointLights: [],
preset: 'auto',
levelId: 1,
tick: 0,
environmentIntensity: 100,
},
{ lightGrid, renderer },
)
expect(appliedLighting).not.toBeNull()
// Player center cell is (24, 24)
const centerIdx = 24 * LIGHT_GRID_SIZE + 24
const intensity = lightGrid.intensity[centerIdx]
expect(intensity).toBeGreaterThan(0)
})
it('adds player light radius bonus correctly', () => {
const gridNoBonus = new LightGrid()
const gridWithBonus = new LightGrid()
const renderer = { setLighting: () => {} }
applyLightingInputs(
{
collisionGrid: mockCollision,
playerSubTile: { x: 50, y: 50 },
pointLights: [],
preset: 'auto',
levelId: 1,
tick: 0,
playerLightRadiusBonus: 0,
environmentIntensity: 0,
},
{ lightGrid: gridNoBonus, renderer },
)
applyLightingInputs(
{
collisionGrid: mockCollision,
playerSubTile: { x: 50, y: 50 },
pointLights: [],
preset: 'auto',
levelId: 1,
tick: 0,
playerLightRadiusBonus: 5,
environmentIntensity: 0,
},
{ lightGrid: gridWithBonus, renderer },
)
// Check cells 2 cells away from player center (24, 24) -> (26, 24)
const targetIdx = 24 * LIGHT_GRID_SIZE + 26
const intensityNoBonus = gridNoBonus.intensity[targetIdx]!
const intensityWithBonus = gridWithBonus.intensity[targetIdx]!
expect(intensityWithBonus).toBeGreaterThanOrEqual(intensityNoBonus)
})
it('preserves true pitch-black darkness when ambient environment intensity is 0', () => {
const lightGrid = new LightGrid()
let appliedLighting: any = null
const renderer = {
setLighting: (params: any) => {
appliedLighting = params
},
}
applyLightingInputs(
{
collisionGrid: mockCollision,
playerSubTile: { x: 50, y: 50 },
pointLights: [],
preset: 'auto',
levelId: 1,
tick: 0,
environmentIntensity: 0,
environmentColor: [20, 20, 20],
},
{ lightGrid, renderer },
)
expect(appliedLighting.ambient[0]).toBe(0)
expect(appliedLighting.ambient[1]).toBe(0)
expect(appliedLighting.ambient[2]).toBe(0)
})
it('includes canonical 1.13c static tables for lit entities', () => {
expect(Object.keys(CANONICAL_MONSTER_LIGHTS).length).toBeGreaterThanOrEqual(80)
expect(Object.keys(CANONICAL_OBJECT_LIGHTS).length).toBeGreaterThanOrEqual(150)
expect(Object.keys(CANONICAL_MISSILE_LIGHTS).length).toBeGreaterThanOrEqual(100)
expect(Object.keys(CANONICAL_OVERLAY_LIGHTS).length).toBeGreaterThanOrEqual(40)
// Andariel has a lit config in monstats2
const andariel = getMonsterLightConfig('andariel')
expect(andariel).toBeDefined()
expect(andariel?.radius).toBeGreaterThan(0)
// Mephisto has a lit config in monstats2
const mephisto = getMonsterLightConfig('mephisto')
expect(mephisto).toBeDefined()
expect(mephisto?.radius).toBeGreaterThan(0)
// Firebolt missile (missiles.txt id 58) has a lit config
const firebolt = getMissileLightConfig('firebolt')
expect(firebolt).toBeDefined()
expect(firebolt?.radius).toBe(7)
expect(firebolt?.r).toBe(255)
const fireboltById = getMissileLightConfig(58)
expect(fireboltById?.radius).toBe(7)
// Torch / campfire object
const campfire = getObjectLightConfig(14)
expect(campfire).toBeDefined()
// Overlay lookup
const overlay = getOverlayLightConfig(1)
expect(overlay).toBeDefined()
})
it('processes point lights with optional flicker into lightGrid', () => {
const lightGrid = new LightGrid()
const renderer = { setLighting: () => {} }
applyLightingInputs(
{
collisionGrid: mockCollision,
playerSubTile: { x: 50, y: 50 },
pointLights: [
{
subTileX: 60,
subTileY: 60,
radius: 10,
intensity: 200,
r: 255,
g: 100,
b: 50,
flicker: 1,
},
],
preset: 'auto',
levelId: 1,
tick: 0,
environmentIntensity: 0,
},
{ lightGrid, renderer },
)
const cellX = Math.floor((60 - lightGrid.originSubX) / 8)
const cellY = Math.floor((60 - lightGrid.originSubY) / 8)
const cellIdx = cellY * LIGHT_GRID_SIZE + cellX
expect(lightGrid.intensity[cellIdx]).toBeGreaterThan(0)
expect(lightGrid.red[cellIdx]).toBe(255)
})
})
describe('Issue #614: Elevation-Aware Shaders & Linear Dynamic Light', () => {
it('uses authentic linear dynamic lighting without arbitrary non-linear curve in fragment shader', () => {
const source = fragmentShaderSource(8)
expect(source).toContain('vec3 dynamicLight = lightSample.rgb;')
expect(source).not.toContain('0.22 + 0.78 * lightSample.a')
})
})
describe('Issue #621: Dead Code Cleanup & Canonical Direction Mapping', () => {
it('pointsToDccDirection maps positions to 0-63 DCC frames', () => {
// South in world coordinates (dx > 0, dy > 0): dir64 = 0 -> DCC-16 index 4
const south = pointsToDccDirection(0, 0, 10, 10, 16)
expect(south).toBe(4)
// Result is always a valid DCC direction within range
const anyDir = pointsToDccDirection(10, 20, 50, 40, 16)
expect(anyDir).toBeGreaterThanOrEqual(0)
expect(anyDir).toBeLessThan(16)
})
it('velocityToDccDirection maps velocity components to valid DCC frames', () => {
// South velocity (vx = 0, vy > 0): DCC-16 index 4
const south = velocityToDccDirection(0, 10, 16)
expect(south).toBe(4)
const east = velocityToDccDirection(10, 0, 16)
expect(east).toBeGreaterThanOrEqual(0)
expect(east).toBeLessThan(16)
})
})