import { describe, expect, it } from 'vitest'; import { hasCompleteVisibleSpatialTiles, planSpatialComposite, type MathRect } from './compositor'; import { tileLevel, tileMathSpan, visibleTileCoordinates, type SpatialResourceKey } from './tiles'; import type { Viewport } from './types'; const viewport: Viewport = { x_min: 0, x_max: 16, y_min: 0, y_max: 16, width: 512, height: 512 }; const target = { expressionId: 'inequality', modelRevision: 7, representation: 'inequality-mask' as const }; function resource(x: number, y: number, overrides: Partial = {}) { return { ...target, quality: 1, level: tileLevel(viewport), x, y, ...overrides }; } function area(rect: MathRect) { return (rect.x_max - rect.x_min) * (rect.y_max - rect.y_min); } describe('spatial compositor planning', () => { it('waits for every exact visible tile of every spatial target before the first reveal', () => { const coordinates = visibleTileCoordinates(viewport); const first = coordinates.map(({ x, y }) => resource(x, y)); const secondTarget = { ...target, expressionId: 'second' }; const second = coordinates.map(({ x, y }) => resource(x, y, { expressionId: 'second' })); expect(hasCompleteVisibleSpatialTiles(viewport, [target], [])).toBe(false); expect(hasCompleteVisibleSpatialTiles(viewport, [target], first.slice(0, -1))).toBe(false); expect(hasCompleteVisibleSpatialTiles(viewport, [target, secondTarget], first)).toBe(false); expect(hasCompleteVisibleSpatialTiles(viewport, [target, secondTarget], [...first, ...second])).toBe(true); expect(hasCompleteVisibleSpatialTiles(viewport, [], [])).toBe(true); }); it('does not count coarse or stale tiles toward the initial reveal', () => { const coordinates = visibleTileCoordinates(viewport); const exact = coordinates.map(({ x, y }) => resource(x, y)); const last = exact.at(-1)!; const partial = exact.slice(0, -1); expect(hasCompleteVisibleSpatialTiles(viewport, [target], [...partial, { ...last, level: last.level - 1 }])).toBe(false); expect(hasCompleteVisibleSpatialTiles(viewport, [target], [...partial, { ...last, modelRevision: 6 }])).toBe(false); expect(hasCompleteVisibleSpatialTiles(viewport, [target], exact)).toBe(true); }); it('uses fallback for the entire viewport before tiles arrive', () => { const plan = planSpatialComposite(viewport, target, []); expect(plan.tileRegions).toEqual([]); expect(plan.fallbackRegions).toEqual([{ source: 'fallback', rect: viewportRect() }]); }); it('replaces only a completed tile interior without overlapping fallback', () => { const level = tileLevel(viewport); const span = tileMathSpan(level); const plan = planSpatialComposite(viewport, target, [resource(0, 0)]); expect(plan.tileRegions).toEqual([ { source: 'tile', resource: resource(0, 0), resourceIndex: 0, rect: { x_min: 0, x_max: span, y_min: 0, y_max: span } } ]); const totalArea = plan.regions.reduce((total, region) => total + area(region.rect), 0); expect(totalArea).toBe(area(viewportRect())); for (const fallback of plan.fallbackRegions) { expect(rectanglesOverlap(fallback.rect, plan.tileRegions[0].rect)).toBe(false); } }); it('switches to tiles without a global all-or-nothing transition', () => { const level = tileLevel(viewport); const span = tileMathSpan(level); const coordinates = []; for (let y = 0; y < 16 / span; y += 1) { for (let x = 0; x < 16 / span; x += 1) coordinates.push(resource(x, y)); } const partial = planSpatialComposite(viewport, target, coordinates.slice(0, -1)); expect(partial.tileRegions).toHaveLength(coordinates.length - 1); expect(partial.fallbackRegions.length).toBeGreaterThan(0); const complete = planSpatialComposite(viewport, target, coordinates); expect(complete.fallbackRegions).toEqual([]); expect(complete.tileRegions).toHaveLength(coordinates.length); }); it('ignores stale, wrong-representation, and duplicate resources', () => { const current = resource(0, 0); const plan = planSpatialComposite(viewport, target, [ current, current, resource(0, 0, { modelRevision: 6 }), resource(0, 0, { representation: 'implicit-contour' }) ]); expect(plan.tileRegions).toHaveLength(1); }); it('uses a parent tile as fallback and lets exact tiles replace it without overlap', () => { const level = tileLevel(viewport); const parent = resource(0, 0, { level: level - 1 }); const exact = resource(0, 0); const parentOnly = planSpatialComposite(viewport, target, [parent]); expect(parentOnly.tileRegions.length).toBeGreaterThan(0); expect(parentOnly.tileRegions.every(({ resource }) => resource.level === level - 1)).toBe(true); const mixed = planSpatialComposite(viewport, target, [parent, exact]); expect(mixed.tileRegions.some(({ resource }) => resource.level === level)).toBe(true); expect(mixed.regions.reduce((total, region) => total + area(region.rect), 0)).toBe( area(viewportRect()) ); for (let index = 0; index < mixed.regions.length; index += 1) { for (let other = index + 1; other < mixed.regions.length; other += 1) { expect(rectanglesOverlap(mixed.regions[index].rect, mixed.regions[other].rect)).toBe(false); } } }); }); function viewportRect(): MathRect { return { x_min: viewport.x_min, x_max: viewport.x_max, y_min: viewport.y_min, y_max: viewport.y_max }; } function rectanglesOverlap(left: MathRect, right: MathRect) { return ( Math.max(left.x_min, right.x_min) < Math.min(left.x_max, right.x_max) && Math.max(left.y_min, right.y_min) < Math.min(left.y_max, right.y_max) ); }