diff --git a/v2/packages/engine/src/render/__tests__/lightning.test.ts b/v2/packages/engine/src/render/__tests__/lightning.test.ts index 44c6657..96d51b0 100644 --- a/v2/packages/engine/src/render/__tests__/lightning.test.ts +++ b/v2/packages/engine/src/render/__tests__/lightning.test.ts @@ -109,4 +109,13 @@ sysA.strike(); // удар 2 — реролл точки от номера удара expect(sysA.info().at).not.toEqual(first); }); + it('dispose посреди вспышки гасит свет и снапшот (свет не замирает)', () => { + const s = stub(); + const sys = new LightningSystem(s.renderer); + sys.strike([5, 0, 5]); // вспышка активна + expect(sys.info().active).toBe(true); + sys.dispose(); + expect(s.flashes.at(-1)).toEqual({ at: null, k: 0 }); // свет погашен + expect(sys.info()).toMatchObject({ active: false, flash: 0 }); + }); }); \ No newline at end of file diff --git a/v2/packages/engine/src/render/__tests__/particleLayers.test.ts b/v2/packages/engine/src/render/__tests__/particleLayers.test.ts index da96308..7a4c707 100644 --- a/v2/packages/engine/src/render/__tests__/particleLayers.test.ts +++ b/v2/packages/engine/src/render/__tests__/particleLayers.test.ts @@ -42,4 +42,27 @@ expect(meshes()).toBe(0); }); } +}); + +/** Регрессия ревью: рост ёмкости пересоздаёт impl с дефолтным белым uTint — + * слой должен помнить свой tint и переапплаить его в свежий меш. */ +describe('слои частиц: tint переживает рост ёмкости', () => { + const factories: [string, (scene: THREE.Scene, cap: number) => ParticleLayer][] = [ + ['спрайты', (s, cap) => createSpriteLayer(s, cap, 3, lights, false)], + ['квады', (s, cap) => createQuadLayer(s, cap, lights, false)], + ]; + for (const [name, make] of factories) { + it(`${name}: tint до роста остаётся в новом меше`, () => { + const scene = new THREE.Scene(); + const layer = make(scene, 2); + layer.setTint(0x40465c); + layer.update([pt(0), pt(1), pt(2), pt(3)]); // рост 2 → 4 + const mesh = scene.children[0] as THREE.Points; + const t = (mesh.material as THREE.ShaderMaterial).uniforms.uTint.value as THREE.Vector3; + expect(t.x).toBeCloseTo(0x40 / 255); + expect(t.y).toBeCloseTo(0x46 / 255); + expect(t.z).toBeCloseTo(0x5c / 255); + layer.dispose(); + }); + } }); \ No newline at end of file diff --git a/v2/packages/engine/src/render/fog.ts b/v2/packages/engine/src/render/fog.ts index f8c55be..cc0aa28 100644 --- a/v2/packages/engine/src/render/fog.ts +++ b/v2/packages/engine/src/render/fog.ts @@ -129,7 +129,7 @@ // клубы слоя: fbm по мировым XZ, свой дрейф; глубокие слои медленнее // (параллакс объёма) и с собственным сдвигом узора vec2 p = wp.xz * 0.09; - vec2 drift = uWind * uTime * (0.3 + fk * 0.06) + fk * vec2(13.7, 7.9); + vec2 drift = uWind * uTime * (0.42 - fk * 0.05) + fk * vec2(13.7, 7.9); float n = 0.6 * afbm(p + drift) + 0.4 * afbm(p * 1.7 + drift * 0.6 + (fk + 1.0) * uTime * vec2(0.021, -0.017)); float billow = clamp(0.5 + (n - 0.5) * 1.9, 0.0, 1.2); diff --git a/v2/packages/engine/src/render/lightning.ts b/v2/packages/engine/src/render/lightning.ts index ba0475f..c2a801d 100644 --- a/v2/packages/engine/src/render/lightning.ts +++ b/v2/packages/engine/src/render/lightning.ts @@ -182,7 +182,11 @@ } dispose(): void { + // гасим всё, что могло остаться от незавершённой вспышки: слой и + // свет рендера иначе замирают навсегда (update уже не догонит) this.layer.dispose(); + this.renderer.setLightning(null, 0); this.strikeT = -1; + this.flashK = 0; } } \ No newline at end of file diff --git a/v2/packages/engine/src/render/particles.ts b/v2/packages/engine/src/render/particles.ts index b778364..67e6917 100644 --- a/v2/packages/engine/src/render/particles.ts +++ b/v2/packages/engine/src/render/particles.ts @@ -331,17 +331,22 @@ lit: boolean, ): ParticleLayer { let cap = capacity; - let impl = createSpriteImpl(scene, cap, pxScale, lights, lit); + let tint = 0xffffff; // помним tint слоя: при росте ёмкости свежий impl + let impl = createSpriteImpl(scene, cap, pxScale, lights, lit); // получит дефолт const layer: ParticleLayer = { update: (points) => { if (points.length > cap) { impl.dispose(); cap = points.length; impl = createSpriteImpl(scene, cap, pxScale, lights, lit); + impl.setTint(tint); // иначе новый impl теряет tint слоя } impl.update(points); }, - setTint: (color) => impl.setTint(color), + setTint: (color) => { + tint = color; + impl.setTint(color); + }, dispose: () => impl.dispose(), }; return layer; @@ -394,6 +399,7 @@ * стабильный фасад, рост ёмкости подменяет impl (см. createSpriteLayer). */ function createQuadLayer(scene: THREE.Scene, capacity: number, lights: ParticleLightUniforms, lit: boolean): ParticleLayer { let cap = capacity; + let tint = 0xffffff; // помним tint: при росте ёмкости свежий impl получит его let impl = createQuadImpl(scene, cap, lights, lit); const layer: ParticleLayer = { update: (points) => { @@ -401,10 +407,14 @@ impl.dispose(); cap = points.length; impl = createQuadImpl(scene, cap, lights, lit); + impl.setTint(tint); } impl.update(points); }, - setTint: (color) => impl.setTint(color), + setTint: (color) => { + tint = color; + impl.setTint(color); + }, dispose: () => impl.dispose(), }; return layer;