Merge branch 'fix/cave-darkness-tuning'
This commit is contained in:
commit
a19ded4c7b
|
|
@ -233,8 +233,8 @@ export class LightGrid {
|
|||
const cellCenterSubX = cx * SUB_TILES_PER_LIGHT_CELL + (SUB_TILES_PER_LIGHT_CELL >> 1)
|
||||
const dSubX = cellCenterSubX - relSubX
|
||||
|
||||
// Fast Euclidean distance approximation
|
||||
const dist = fastDistanceApprox(dSubX, dSubY)
|
||||
// Fast Euclidean distance approximation (emitter's own cell receives full peak intensity at dist = 0)
|
||||
const dist = (cx === centerCellX && cy === centerCellY) ? 0 : fastDistanceApprox(dSubX, dSubY)
|
||||
if (dist >= effectiveRadius) continue
|
||||
|
||||
// Linear falloff: ((R - dist) * IntensitySlope) >> 16
|
||||
|
|
|
|||
|
|
@ -233,7 +233,8 @@ ${cases.join('\n')}
|
|||
${paletteLookup}${discardAlpha}
|
||||
if (u_lightMode == 1 && u_blendMode == 0) {
|
||||
vec4 lightSample = texture(u_lightmap, v_lightUv);
|
||||
vec3 totalLight = clamp(u_ambient.rgb + lightSample.rgb, 0.0, 1.0);
|
||||
vec3 dynamicLight = lightSample.rgb * (0.22 + 0.78 * lightSample.a);
|
||||
vec3 totalLight = clamp(u_ambient.rgb + dynamicLight, 0.0, 1.0);
|
||||
outColor = vec4(texel.rgb * totalLight * v_tint.rgb, texel.a * v_tint.a);
|
||||
} else {
|
||||
outColor = texel * v_tint;
|
||||
|
|
|
|||
|
|
@ -407,25 +407,25 @@ export const LIGHTING_PRESETS: Readonly<Record<LightingPreset, LightingPresetCon
|
|||
id: 'night',
|
||||
labelZh: '午夜 (幽暗蓝月)',
|
||||
enabled: true,
|
||||
overrideAmbient: { intensity: 68, red: 125, green: 148, blue: 245 },
|
||||
overrideAmbient: { intensity: 38, red: 115, green: 140, blue: 240 },
|
||||
},
|
||||
cave: {
|
||||
id: 'cave',
|
||||
labelZh: '深渊地牢 (火炬暗黑)',
|
||||
enabled: true,
|
||||
overrideAmbient: { intensity: 14, red: 225, green: 215, blue: 205 },
|
||||
overrideAmbient: { intensity: 5, red: 185, green: 175, blue: 165 },
|
||||
},
|
||||
hell: {
|
||||
id: 'hell',
|
||||
labelZh: '地狱血月 (第四幕猩红)',
|
||||
enabled: true,
|
||||
overrideAmbient: { intensity: 105, red: 255, green: 68, blue: 58 },
|
||||
overrideAmbient: { intensity: 72, red: 255, green: 62, blue: 52 },
|
||||
},
|
||||
eclipse: {
|
||||
id: 'eclipse',
|
||||
labelZh: '蚀日之咒 (幽蓝暗蚀)',
|
||||
enabled: true,
|
||||
overrideAmbient: { intensity: 52, red: 45, green: 85, blue: 245 },
|
||||
overrideAmbient: { intensity: 28, red: 45, green: 85, blue: 245 },
|
||||
},
|
||||
off: {
|
||||
id: 'off',
|
||||
|
|
@ -6432,16 +6432,24 @@ if (typeof window !== 'undefined') {
|
|||
lightGrid.clear()
|
||||
lightGrid.updateObstacles(runtime.grid)
|
||||
|
||||
// Authentic 1.13c player torch light: base radius 11 cells * 8 sub-tiles = 88 sub-tiles
|
||||
const flicker = (Math.sin(engine.world.tick * 0.35) * 2) | 0
|
||||
// Resolve ambient from active lighting preset override or 1.13c Environment engine
|
||||
const presetCfg = LIGHTING_PRESETS[currentLightingPreset]
|
||||
const overrideAmb = presetCfg.overrideAmbient
|
||||
const rawIntensity = overrideAmb !== undefined ? overrideAmb.intensity : environment.currentIntensity
|
||||
const isDarkCave = rawIntensity <= 10 || currentLightingPreset === 'cave'
|
||||
|
||||
// Authentic 1.13c player torch light in isometric sub-tile space:
|
||||
// 20-22 sub-tiles = ~240-260px horizontal screen radius (~500px diameter pool around hero)
|
||||
const flicker = (Math.sin(engine.world.tick * 0.35) * 1.5) | 0
|
||||
const basePlayerRadius = isDarkCave ? 20 : 22
|
||||
lightGrid.addLight({
|
||||
subTileX: pSub.x,
|
||||
subTileY: pSub.y,
|
||||
radius: 88 + flicker,
|
||||
intensity: 255,
|
||||
radius: basePlayerRadius + flicker,
|
||||
intensity: 242,
|
||||
red: 255,
|
||||
green: 246,
|
||||
blue: 228,
|
||||
green: 234,
|
||||
blue: 196,
|
||||
})
|
||||
|
||||
// Active missile lights (with skillId fallback for all spells including Glacial Spike '55')
|
||||
|
|
@ -6474,8 +6482,8 @@ if (typeof window !== 'undefined') {
|
|||
lightGrid.addLight({
|
||||
subTileX: shotSubX,
|
||||
subTileY: shotSubY,
|
||||
radius: Math.max(48, lightUnits * 8),
|
||||
intensity: 255,
|
||||
radius: Math.max(14, lightUnits * 2),
|
||||
intensity: 250,
|
||||
red: isCold ? Math.max(120, mData.red) : mData.red,
|
||||
green: isCold ? Math.max(175, mData.green) : mData.green,
|
||||
blue: mData.blue,
|
||||
|
|
@ -6495,7 +6503,7 @@ if (typeof window !== 'undefined') {
|
|||
lightGrid.addLight({
|
||||
subTileX: expSubX,
|
||||
subTileY: expSubY,
|
||||
radius: lightUnits * 8,
|
||||
radius: Math.round(lightUnits * 2.5),
|
||||
intensity: 255,
|
||||
red: isColdExp ? 130 : (expData?.red ?? 255),
|
||||
green: isColdExp ? 185 : (expData?.green ?? 160),
|
||||
|
|
@ -6522,34 +6530,30 @@ if (typeof window !== 'undefined') {
|
|||
if (isTorchOrFire || isWaypoint || isShrineOrBeam) {
|
||||
const dx = obj.x - engine.world.player.x
|
||||
const dy = obj.y - engine.world.player.y
|
||||
if (dx * dx + dy * dy < 1500 * 1500) {
|
||||
if (dx * dx + dy * dy < 950 * 950) {
|
||||
const odx = (obj.x - runtime.grid.originX) / ORTHO_SUB_TILE_WIDTH
|
||||
const ody = (obj.y - runtime.grid.originY) / ORTHO_SUB_TILE_HEIGHT
|
||||
const objSubX = Math.round((ody + odx) / 2)
|
||||
const objSubY = Math.round((ody - odx) / 2)
|
||||
const objFlicker = isTorchOrFire ? ((Math.sin(engine.world.tick * 0.45 + obj.x * 0.05) * 2) | 0) : 0
|
||||
const objFlicker = isTorchOrFire ? ((Math.sin(engine.world.tick * 0.45 + obj.x * 0.05) * 1.2) | 0) : 0
|
||||
lightGrid.addLight({
|
||||
subTileX: objSubX,
|
||||
subTileY: objSubY,
|
||||
radius: (isTorchOrFire ? 56 : isWaypoint ? 52 : 44) + objFlicker,
|
||||
intensity: isTorchOrFire ? 235 : 210,
|
||||
radius: (isTorchOrFire ? 13 : isWaypoint ? 14 : 11) + objFlicker,
|
||||
intensity: isTorchOrFire ? 175 : 160,
|
||||
red: isWaypoint ? 120 : 255,
|
||||
green: isWaypoint ? 150 : (isTorchOrFire ? 220 : 210),
|
||||
blue: isWaypoint ? 255 : (isTorchOrFire ? 150 : 180),
|
||||
green: isWaypoint ? 150 : (isTorchOrFire ? 195 : 205),
|
||||
blue: isWaypoint ? 255 : (isTorchOrFire ? 115 : 175),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve ambient from active lighting preset override or 1.13c Environment engine
|
||||
const presetCfg = LIGHTING_PRESETS[currentLightingPreset]
|
||||
const overrideAmb = presetCfg.overrideAmbient
|
||||
const rawIntensity = overrideAmb !== undefined ? overrideAmb.intensity : environment.currentIntensity
|
||||
const effectiveAmbIntensity = rawIntensity > 0 ? rawIntensity : 14
|
||||
const effR = overrideAmb !== undefined ? overrideAmb.red : (rawIntensity > 0 ? environment.currentRed : 225)
|
||||
const effG = overrideAmb !== undefined ? overrideAmb.green : (rawIntensity > 0 ? environment.currentGreen : 215)
|
||||
const effB = overrideAmb !== undefined ? overrideAmb.blue : (rawIntensity > 0 ? environment.currentBlue : 205)
|
||||
const effectiveAmbIntensity = rawIntensity > 0 ? rawIntensity : 5
|
||||
const effR = overrideAmb !== undefined ? overrideAmb.red : (rawIntensity > 0 ? environment.currentRed : 185)
|
||||
const effG = overrideAmb !== undefined ? overrideAmb.green : (rawIntensity > 0 ? environment.currentGreen : 175)
|
||||
const effB = overrideAmb !== undefined ? overrideAmb.blue : (rawIntensity > 0 ? environment.currentBlue : 165)
|
||||
const ambNorm = effectiveAmbIntensity / 255
|
||||
const ambR = (effR / 255) * ambNorm
|
||||
const ambG = (effG / 255) * ambNorm
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ describe('Scene Lighting & Environment Integration', () => {
|
|||
expect(normalizeLightingPreset('eclipse')).toBe('eclipse')
|
||||
|
||||
expect(LIGHTING_PRESETS.noon.overrideAmbient?.intensity).toBe(255)
|
||||
expect(LIGHTING_PRESETS.cave.overrideAmbient?.intensity).toBe(14)
|
||||
expect(LIGHTING_PRESETS.cave.overrideAmbient?.intensity).toBe(5)
|
||||
expect(LIGHTING_PRESETS.off.enabled).toBe(false)
|
||||
|
||||
const actsHtml = fs.readFileSync('acts.html', 'utf8')
|
||||
|
|
@ -405,5 +405,32 @@ describe('Scene Lighting & Environment Integration', () => {
|
|||
for (const preset of Object.keys(LIGHTING_PRESETS)) {
|
||||
expect(actsHtml).toContain(`value="${preset}"`)
|
||||
}
|
||||
}, 15000)
|
||||
|
||||
it('keeps cave player torch (radius=20 sub-tiles) and wall torches (radius=13 sub-tiles) localized without saturating outer viewport cells', () => {
|
||||
const grid = new LightGrid()
|
||||
grid.centerOnSubTile(200, 200)
|
||||
|
||||
// Add calibrated cave player torch at center (200, 200), radius=20 sub-tiles (~240px horizontal radius)
|
||||
grid.addLight({
|
||||
subTileX: 200,
|
||||
subTileY: 200,
|
||||
radius: 20,
|
||||
intensity: 242,
|
||||
red: 255,
|
||||
green: 234,
|
||||
blue: 196,
|
||||
})
|
||||
|
||||
const centerIdx = 24 * LIGHT_GRID_SIZE + 24
|
||||
// 1 cell away (dx=+1, dy=-1 => 8 sub-tiles each => dist=11, 160px horizontally): illuminated
|
||||
const nearIdx = 23 * LIGHT_GRID_SIZE + 25
|
||||
// 3 cells away (dx=+3, dy=-3 => 24 sub-tiles each => dist=32, 480px horizontally, well within 640px half-screen): completely dark!
|
||||
const outerScreenIdx = 21 * LIGHT_GRID_SIZE + 27
|
||||
|
||||
expect(grid.intensity[centerIdx]).toBeGreaterThan(220)
|
||||
expect(grid.intensity[nearIdx]).toBeGreaterThan(80)
|
||||
expect(grid.intensity[outerScreenIdx]).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue