Newer
Older
rpg / v2 / packages / engine / src / render / __tests__ / particleLayers.test.ts
import { describe, expect, it } from 'vitest';
import * as THREE from 'three';
import {
    createQuadLayer, createSpriteLayer, makeParticleLightUniforms,
    type ParticleLayer, type ParticlePoint,
} from '../particles';

const pt = (i: number): ParticlePoint => ({ pos: [i, 1, 2], color: '#a8bacb' });
const lights = makeParticleLightUniforms();

/** Регрессия #218: держатель слоя (WeatherSystem) держит методы, снятые со
 *  слоя в момент создания (и вызывает их каждый тик). Раньше рост ёмкости
 *  перезаписывал методы слоя методами свежей реализации — но снятая ранее
 *  ссылка продолжала указывать в мёртвое замыкание: каждый тик сверх
 *  ёмкости создавал новый слой с однократной записью, и замороженные
 *  снимки дождя копились в сцене (пелена на экране). Теперь фасад
 *  стабильн: сколько бы ни было ростов, в сцене ровно один меш. */
describe('слои частиц: рост ёмкости не плодит меши (#218)', () => {
    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}: превышение ёмкости подменяет меш, а не плодит слои`, () => {
            const scene = new THREE.Scene();
            const layer = make(scene, 2);
            const update = layer.update; // как WeatherSystem: метод снят заранее
            const meshes = () => scene.children.length;
            expect(meshes()).toBe(1);
            update([pt(0), pt(1)]);
            expect(meshes()).toBe(1);
            update([pt(0), pt(1), pt(2), pt(3)]); // рост 2 → 4
            expect(meshes()).toBe(1);
            update([pt(0), pt(1), pt(2)]); // выше старой ёмкости, ниже новой
            expect(meshes()).toBe(1);
            update([pt(0), pt(1), pt(2), pt(3)]); // повторное превышение
            expect(meshes()).toBe(1);
            layer.setTint(0x40465c); // тинт после роста идёт в живой меш
            layer.dispose();
            expect(meshes()).toBe(0);
            layer.dispose(); // повторный dispose безопасен
            expect(meshes()).toBe(0);
        });
    }
});