Merge branch 'fix/cave-darkness-tuning'

This commit is contained in:
troytt 2026-09-24 10:46:43 +00:00
commit a19ded4c7b
4 changed files with 63 additions and 31 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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)
})
})