diff --git a/tests/unit/quantized-environment-blend-cache.test.ts b/tests/unit/quantized-environment-blend-cache.test.ts new file mode 100644 index 00000000..ab3a5649 --- /dev/null +++ b/tests/unit/quantized-environment-blend-cache.test.ts @@ -0,0 +1,135 @@ +import { Texture } from "three"; +import { describe, expect, it, vi } from "vitest"; + +import { + createQuantizedEnvironmentBlendCacheKey, + getQuantizedEnvironmentBlendAmount, + QuantizedEnvironmentBlendCache, + quantizeEnvironmentBlendBucket +} from "../../src/rendering/quantized-environment-blend-cache"; + +describe("quantized environment blend cache helpers", () => { + it("quantizes twilight blend amounts into fixed buckets", () => { + expect(quantizeEnvironmentBlendBucket(0, 8)).toBe(0); + expect(quantizeEnvironmentBlendBucket(0.24, 8)).toBe(2); + expect(quantizeEnvironmentBlendBucket(0.51, 8)).toBe(4); + expect(quantizeEnvironmentBlendBucket(1, 8)).toBe(8); + expect(getQuantizedEnvironmentBlendAmount(5, 8)).toBeCloseTo(0.625); + }); + + it("keys cached blends by ordered source pair and bucket", () => { + const dayTexture = new Texture(); + const nightTexture = new Texture(); + + expect( + createQuantizedEnvironmentBlendCacheKey(dayTexture, nightTexture, 3) + ).not.toBe( + createQuantizedEnvironmentBlendCacheKey(nightTexture, dayTexture, 3) + ); + expect( + createQuantizedEnvironmentBlendCacheKey(dayTexture, nightTexture, 2) + ).not.toBe( + createQuantizedEnvironmentBlendCacheKey(dayTexture, nightTexture, 3) + ); + }); +}); + +describe("QuantizedEnvironmentBlendCache", () => { + it("queues exact bucket builds, reuses the nearest cached bucket while pending, and disposes cached entries", () => { + const scheduledBuilds: Array<() => void> = []; + const disposeSpies: Array> = []; + const buildBlendTexture = vi.fn( + ( + _baseTexture: Texture, + _overlayTexture: Texture, + _blendAmount: number + ) => { + const dispose = vi.fn(); + disposeSpies.push(dispose); + + return { + texture: new Texture(), + dispose + }; + } + ); + const onTextureReady = vi.fn(); + const cache = new QuantizedEnvironmentBlendCache({ + bucketCount: 8, + buildBlendTexture, + onTextureReady, + scheduleBuild: (callback) => { + scheduledBuilds.push(callback); + } + }); + const dayTexture = new Texture(); + const nightTexture = new Texture(); + + expect(cache.resolveBlendTexture(dayTexture, nightTexture, 0.26)).toBeNull(); + expect(buildBlendTexture).not.toHaveBeenCalled(); + expect(scheduledBuilds).toHaveLength(1); + + scheduledBuilds.shift()?.(); + + expect(buildBlendTexture).toHaveBeenCalledWith( + dayTexture, + nightTexture, + 0.25 + ); + expect(onTextureReady).toHaveBeenCalledTimes(1); + + const firstBlendTexture = cache.resolveBlendTexture( + dayTexture, + nightTexture, + 0.24 + ); + + expect(firstBlendTexture).not.toBeNull(); + expect( + cache.resolveBlendTexture(dayTexture, nightTexture, 0.62) + ).toBe(firstBlendTexture); + expect(scheduledBuilds).toHaveLength(1); + + scheduledBuilds.shift()?.(); + + const laterBlendTexture = cache.resolveBlendTexture( + dayTexture, + nightTexture, + 0.62 + ); + + expect(laterBlendTexture).not.toBe(firstBlendTexture); + expect(onTextureReady).toHaveBeenCalledTimes(2); + + cache.clear(); + + expect(disposeSpies).toHaveLength(2); + expect(disposeSpies[0]).toHaveBeenCalledTimes(1); + expect(disposeSpies[1]).toHaveBeenCalledTimes(1); + }); + + it("returns the authored single-image environment at the quantized endpoints and disposes builder resources", () => { + const disposeBuildResources = vi.fn(); + const buildBlendTexture = vi.fn(); + const cache = new QuantizedEnvironmentBlendCache({ + bucketCount: 8, + buildBlendTexture, + disposeBuildResources, + scheduleBuild: () => undefined + }); + const dayTexture = new Texture(); + const nightTexture = new Texture(); + + expect(cache.resolveBlendTexture(dayTexture, nightTexture, 0.01)).toBe( + dayTexture + ); + expect(cache.resolveBlendTexture(dayTexture, nightTexture, 0.99)).toBe( + nightTexture + ); + expect(buildBlendTexture).not.toHaveBeenCalled(); + + cache.dispose(); + + expect(disposeBuildResources).toHaveBeenCalledTimes(1); + }); +});