import { describe, expect, it, vi } from 'vitest'; import { Scene3dPresentationController } from './scene3d-presentation-controller'; import { Scene3dSupersededError } from './scene3d-worker-client'; import type { Scene3dPartResult } from './scene3d-merge'; import type { Scene3dViewSnapshot } from './scene3d-view'; import type { GraphDocument3d, MeshResource3d, RenderScene3d, Scene3dRefinementTarget } from './types'; const bounds = { x_min: -1, x_max: 1, y_min: -1, y_max: 1, z_min: -1, z_max: 1 }; const document: GraphDocument3d = { bounds, expressions: [{ id: 'implicit', input_mode: 'text', source: 'x^2+y^2+z^2=1', enabled: true, color: '#2563eb' }] }; function scene(resources: RenderScene3d['resources'] = []): RenderScene3d { return { bounds, resources, diagnostics: [], variables: [], expression_roles: { implicit: 'plot' }, model_revisions: { implicit: 3 }, implicit_expression_ids: ['implicit'], implicit_cell_targets: [ { expression_id: 'implicit', cell: { level: 0, index: [0, 0, 0] }, model_revision: 3 } ] }; } function meshResource( qualityLevel: 0 | 1, resourceBounds = bounds, geometryId = qualityLevel + 1 ): MeshResource3d { return { type: 'mesh', key: { expression_id: 'implicit', representation: 'surface_mesh', region: 'cell-l0-x0-y0-z0', quality_level: qualityLevel }, geometry_id: geometryId, model_revision: 3, bounds: resourceBounds, positions: [[0, 0, 0], [0.1, 0, 0], [0, 0.1, 0]], normals: [[0, 0, 1], [0, 0, 1], [0, 0, 1]], indices: [0, 1, 2], color: '#2563eb' }; } function sceneWithMesh(resourceBounds = bounds, qualityLevel: 0 | 1 = 0) { return { ...scene([meshResource(qualityLevel, resourceBounds)]), bounds: resourceBounds }; } function sceneWithQ3Candidate() { return { ...scene(), implicit_adaptive_targets: [{ expression_id: 'implicit', parent: { level: 0, index: [0, 0, 0] as [number, number, number] }, estimated_error: 1, quality_level: 3 as const, transition_mask: 0, model_revision: 3, coverage_revision: 0 }] }; } const settledDocument: GraphDocument3d = { bounds, expressions: [ { id: 'variable', input_mode: 'text', source: 'a := 1', enabled: true, color: null }, { id: 'crease', input_mode: 'text', source: 'z = a*abs(x)', enabled: true, color: '#16a34a' } ] }; function settledPreviewScene(): RenderScene3d { const preview = scene([{ type: 'gpu_surface', key: { expression_id: 'crease', representation: 'surface_gpu', region: 'whole', quality_level: 0 }, geometry_id: 90, model_revision: 11, coverage_revision: 4, bounds, grid_size: 257, depth_mode: 'interpolated', value_glsl: 'abs(x)', dx_glsl: '1.0', dy_glsl: '0.0', uniforms: [{ name: 'u_cartesium_a', variable: 'a', value: 1 }], color: '#16a34a' }]); preview.expression_roles = { variable: 'variable', crease: 'plot' }; preview.model_revisions = { crease: 11 }; preview.coverage_revisions = { crease: 4 }; preview.variables = [{ expression_id: 'variable', name: 'a', value: 1, slider: null }]; preview.implicit_expression_ids = []; preview.implicit_cell_targets = []; preview.settled_continuous_crease_patch_targets = [{ expression_id: 'crease', patch: [0, 0], model_revision: 11, coverage_revision: 4, estimated_error: 1, bounds, feature_kind: 'crease', settled: true }]; return preview; } function settledPartFor( target: Extract, geometryId: number ): Scene3dPartResult { const resource = { type: 'mesh' as const, key: { expression_id: target.expressionId, representation: 'surface_mesh' as const, region: target.region, quality_level: 1 }, geometry_id: geometryId, model_revision: target.modelRevision, coverage_revision: target.coverageRevision, bounds, positions: [[0, 0, 0] as [number, number, number]], normals: [[0, 0, 1] as [number, number, number]], indices: [0], color: '#16a34a' }; const fragment = scene([resource]); fragment.expression_roles = { crease: 'plot' }; fragment.model_revisions = { crease: target.modelRevision }; fragment.coverage_revisions = { crease: target.coverageRevision ?? 0 }; return { part: target, scene: fragment, geometry: { meshes: [{ geometryId, positions: new Float32Array([0, 0, 0]), normals: new Float32Array([0, 0, 1]), indices: new Uint32Array([0]) }], points: [], curves: [], vectors: [] } }; } function settledAdaptivePartFor( target: Extract, geometryId: number ): Scene3dPartResult { const resource = { type: 'mesh' as const, key: { expression_id: target.expressionId, representation: 'surface_mesh' as const, region: target.region, quality_level: 2 }, geometry_id: geometryId, model_revision: target.modelRevision, coverage_revision: target.coverageRevision, bounds, positions: [[0, 0, 0] as [number, number, number]], normals: [[0, 0, 1] as [number, number, number]], indices: [0], color: '#16a34a' }; const fragment = scene([resource]); fragment.expression_roles = { crease: 'plot' }; fragment.model_revisions = { crease: target.modelRevision }; fragment.coverage_revisions = { crease: target.coverageRevision ?? 0 }; return { part: target, scene: fragment, geometry: { meshes: [{ geometryId, positions: new Float32Array([0, 0, 0]), normals: new Float32Array([0, 0, 1]), indices: new Uint32Array([0]) }], points: [], curves: [], vectors: [] } }; } function deferred() { let resolve!: (value: T) => void; let reject!: (error: unknown) => void; const promise = new Promise((resolvePromise, rejectPromise) => { resolve = resolvePromise; reject = rejectPromise; }); return { promise, resolve, reject }; } function harness() { const previews: Array>> = []; const previewViews: Array = []; const refinements: Array<{ resolve: () => void; reject: (error: unknown) => void; targets: Scene3dRefinementTarget[]; onpart: (part: Scene3dPartResult) => void; onstage?: (stage: 'q2') => void; view?: Scene3dViewSnapshot; }> = []; const q3Refinements: Array<{ resolve: () => void; reject: (error: unknown) => void; candidates: RenderScene3d['implicit_adaptive_targets']; onpart: (part: Scene3dPartResult) => void; view?: Scene3dViewSnapshot; }> = []; const adaptiveRefinements: Array<{ resolve: () => void; reject: (error: unknown) => void; targets: Scene3dRefinementTarget[]; onpart: (part: Scene3dPartResult) => void; view?: Scene3dViewSnapshot; }> = []; const timers = new Map void>(); const frames = new Map void>(); const delays: number[] = []; let nextTimer = 1; let now = 1000; const publish = vi.fn(); const setBusy = vi.fn(); const setError = vi.fn(); const cancel = vi.fn(); const setPhase = vi.fn(); const controller = new Scene3dPresentationController({ publish, setBusy, setError, cancel, buildPreview: (_document, _revision, view) => { previewViews.push(view); const result = deferred(); previews.push(result); return result.promise; }, refine: (_document, _revision, _targets, onpart, onstage, view) => { const result = deferred(); refinements.push({ resolve: result.resolve, reject: result.reject, targets: _targets, onpart, onstage, view }); return result.promise; }, refineQ3: (_document, _revision, candidates, onpart, view) => { const result = deferred(); q3Refinements.push({ resolve: result.resolve, reject: result.reject, candidates, onpart, view }); return result.promise; }, refineAdaptive: (_document, _revision, targets, onpart, view) => { const result = deferred(); adaptiveRefinements.push({ resolve: result.resolve, reject: result.reject, targets, onpart, view }); return result.promise; }, setTimer: (callback, delay) => { const handle = nextTimer++; timers.set(handle, callback); delays.push(delay); return handle; }, clearTimer: (handle) => timers.delete(handle), requestAnimationFrame: (callback) => { const handle = nextTimer++; frames.set(handle, callback); return handle; }, cancelAnimationFrame: (handle) => frames.delete(handle), now: () => now, setPhase }); return { controller, previews, previewViews, refinements, q3Refinements, adaptiveRefinements, timers, frames, delays, publish, setBusy, setError, cancel, setPhase, setNow: (value: number) => (now = value) }; } describe('Scene3dPresentationController', () => { it('captures the latest view for a scheduled refinement without creating camera demand', async () => { const test = harness(); const firstView: Scene3dViewSnapshot = { viewMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], projectionMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], viewportWidth: 640, viewportHeight: 480 }; const latestView = { ...firstView, viewportWidth: 800 }; test.controller.setViewSnapshot(firstView); test.controller.setDocument(document); test.controller.setViewSnapshot(latestView); expect(test.cancel).not.toHaveBeenCalled(); expect(test.previewViews[0]).toEqual(firstView); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); expect(test.refinements[0].view).toEqual(latestView); expect(test.cancel).not.toHaveBeenCalled(); }); it('debounces settled view changes into silent q3-only refinement', async () => { const test = harness(); const view: Scene3dViewSnapshot = { viewMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], projectionMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], viewportWidth: 640, viewportHeight: 480 }; test.controller.setViewSnapshot(view); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const q2Scene = sceneWithQ3Candidate(); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0, cell: { level: 0, index: [0, 0, 0] } }, scene: q2Scene, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].onstage?.('q2'); expect(test.q3Refinements).toHaveLength(0); const busyCalls = test.setBusy.mock.calls.length; const phaseCalls = test.setPhase.mock.calls.length; test.setNow(1100); test.controller.setViewSnapshot({ ...view, viewportWidth: 800 }); test.setNow(1150); test.controller.setViewSnapshot({ ...view, viewportWidth: 900 }); expect(test.q3Refinements).toHaveLength(0); const cameraTimer = [...test.timers.values()].at(-1)!; cameraTimer(); expect(test.q3Refinements).toHaveLength(1); expect(test.q3Refinements[0].view?.viewportWidth).toBe(900); expect(test.q3Refinements[0].candidates).toHaveLength(1); expect(test.setBusy.mock.calls.length).toBe(busyCalls); expect(test.setPhase.mock.calls.length).toBe(phaseCalls); test.setNow(1400); test.controller.setViewSnapshot({ ...view, viewportWidth: 1000 }); [...test.timers.values()].at(-1)!(); expect(test.q3Refinements).toHaveLength(2); expect(test.cancel).toHaveBeenCalledTimes(2); expect(test.q3Refinements[1].view?.viewportWidth).toBe(1000); test.q3Refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); const q3Scene = scene(); q3Scene.implicit_adaptive_targets = []; test.q3Refinements[1].onpart({ part: { kind: 'implicit-adaptive-child', expressionId: 'implicit', representation: 'surface_mesh', region: 'cell-l-1-x0-y0-z0', qualityLevel: 3, modelRevision: 3, coverageRevision: 0, priority: -1, parent: { level: 0, index: [0, 0, 0] }, cell: { level: -1, index: [0, 0, 0] }, childBits: [0, 0, 0], atomicGroup: 'implicit|cell-l0-x0-y0-z0', replacementRegion: 'cell-l0-x0-y0-z0', transitionMask: 0, estimatedError: 1 }, scene: q3Scene, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.q3Refinements[1].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(3); }); it('admits settled-view explicit groups and never publishes an incomplete group', async () => { const test = harness(); const view: Scene3dViewSnapshot = { viewMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], projectionMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], viewportWidth: 640, viewportHeight: 480 }; test.controller.setViewSnapshot(view); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const q2Scene = scene(); q2Scene.explicit_surface_adaptive_targets = [{ expression_id: 'implicit', patch: [0, 0], depth: 0, index: [0, 0], model_revision: 3, coverage_revision: 0, estimated_error: 1, bounds, feature_kind: 'crease' }]; test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0, cell: { level: 0, index: [0, 0, 0] } }, scene: q2Scene, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].onstage?.('q2'); const settledPublishCount = test.publish.mock.calls.length; test.setNow(1100); test.controller.setViewSnapshot({ ...view, viewportWidth: 800 }); [...test.timers.values()].at(-1)!(); expect(test.adaptiveRefinements).toHaveLength(1); expect(test.adaptiveRefinements[0].targets).toHaveLength(4); expect(new Set(test.adaptiveRefinements[0].targets.map((target) => target.kind === 'explicit-surface-adaptive-child' ? target.atomicGroup : '' )).size).toBe(1); for (const target of test.adaptiveRefinements[0].targets.slice(0, 3)) { test.adaptiveRefinements[0].onpart({ part: target, scene: { ...scene(), explicit_surface_adaptive_targets: [] }, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); } test.adaptiveRefinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(settledPublishCount); test.setNow(1400); test.controller.setViewSnapshot({ ...view, viewportWidth: 900 }); [...test.timers.values()].at(-1)!(); expect(test.adaptiveRefinements).toHaveLength(2); for (const target of test.adaptiveRefinements[1].targets) { test.adaptiveRefinements[1].onpart({ part: target, scene: { ...scene(), explicit_surface_adaptive_targets: [] }, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); } test.adaptiveRefinements[1].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(settledPublishCount + 1); }); it('publishes coarse preview before settled cell refinement and merges parts', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; expect(test.publish).toHaveBeenCalledTimes(1); expect(test.timers.size).toBe(1); expect(test.delays).toEqual([200]); const timer = [...test.timers.values()][0]; timer(); expect(test.refinements).toHaveLength(1); expect(test.setPhase).toHaveBeenLastCalledWith('refining'); const refinedPart = scene(); refinedPart.implicit_cell_targets = []; test.refinements[0].onpart({ part: { expressionId: 'implicit', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3 }, scene: refinedPart, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].implicit_cell_targets).toEqual([]); expect(test.setBusy).toHaveBeenLastCalledWith(false); }); it('fences an obsolete preview and cancels its worker request', async () => { const test = harness(); test.controller.setDocument(document); const changed = { ...document, expressions: [{ ...document.expressions[0], source: 'x^2+y^2+z^2=2' }] }; test.controller.setDocument(changed); expect(test.cancel).toHaveBeenCalled(); test.previews[1].resolve(scene()); test.previews[0].resolve(scene()); await Promise.all([test.previews[0].promise, test.previews[1].promise]); expect(test.publish).toHaveBeenCalledTimes(1); }); it('treats a bounds-only edit as a new preview instead of an override update', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; test.controller.setDocument({ ...document, bounds: { ...bounds, z_min: -2, z_max: 2 } }); expect(test.cancel).toHaveBeenCalledTimes(1); expect(test.previews).toHaveLength(2); expect(test.publish).toHaveBeenCalledTimes(1); }); it('publishes only the latest settled scene after rapid bounds revisions', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const firstExpanded = { ...bounds, z_min: -2, z_max: 2 }; const secondExpanded = { ...bounds, x_min: -3, x_max: 3 }; test.controller.setDocument({ ...document, bounds: firstExpanded }); test.controller.setDocument({ ...document, bounds: secondExpanded }); test.previews[2].resolve(sceneWithMesh(secondExpanded)); await test.previews[2].promise; expect(test.publish).toHaveBeenCalledTimes(1); const timer = [...test.timers.values()][0]; timer(); const refined = sceneWithMesh(secondExpanded, 1); refined.implicit_cell_targets = []; test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: refined, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].bounds).toEqual(secondExpanded); }); it('publishes a compatible bounds preview at q2 before optional q3 closes', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); const staged = sceneWithMesh(expanded); staged.implicit_cell_targets = [ { expression_id: 'implicit', cell: { level: 0, index: [0, 0, 0] }, model_revision: 3 }, { expression_id: 'implicit', cell: { level: 0, index: [1, 0, 0] }, model_revision: 3 } ]; test.previews[1].resolve(staged); await test.previews[1].promise; expect(test.publish).toHaveBeenCalledTimes(1); [...test.timers.values()][0](); const q1 = scene(); q1.bounds = expanded; q1.implicit_cell_targets = []; test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: q1, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [1, 0, 0] }, region: 'cell-l0-x1-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: q1, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].onstage?.('q2'); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.setBusy).toHaveBeenLastCalledWith(false); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].bounds).toEqual(expanded); expect(test.publish.mock.calls[1][0].resources).toEqual([]); }); it('uses displayed geometry for bounds compatibility but never hides a changed-math preview', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const changed = { ...document, expressions: [{ ...document.expressions[0], source: 'x^2+y^2+z^2=2' }] }; test.controller.setDocument(changed); const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...changed, bounds: expanded }); test.previews[2].resolve(sceneWithMesh(expanded)); await test.previews[2].promise; expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].bounds).toEqual(expanded); }); it('publishes a staged preview immediately when no q1 targets remain', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); const emptyPreview = scene(); emptyPreview.bounds = expanded; emptyPreview.resources = []; emptyPreview.implicit_cell_targets = []; test.previews[1].resolve(emptyPreview); await test.previews[1].promise; expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].bounds).toEqual(expanded); }); it('keeps the old scene when a retained bounds refinement is cancelled before q1', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); test.previews[1].resolve(sceneWithMesh(expanded)); await test.previews[1].promise; [...test.timers.values()][0](); test.refinements[0].reject(new Scene3dSupersededError()); await Promise.resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.setBusy).toHaveBeenLastCalledWith(false); }); it('keeps the old scene when a retained bounds refinement errors', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); test.previews[1].resolve(sceneWithMesh(expanded)); await test.previews[1].promise; [...test.timers.values()][0](); const q1 = sceneWithMesh(expanded, 1); q1.implicit_cell_targets = []; test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: q1, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].reject(new Error('q2 failed')); await Promise.resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.setError).toHaveBeenLastCalledWith('q2 failed'); }); it('keeps bounds-transition q2 parts hidden until refinement completes', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); test.previews[1].resolve(sceneWithMesh(expanded)); await test.previews[1].promise; [...test.timers.values()][0](); const q1 = sceneWithMesh(expanded, 1); q1.implicit_cell_targets = []; test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: q1, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0 }, scene: { ...scene(), bounds: expanded }, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); }); it('does not publish a staged preview when only q2 work remains', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(sceneWithMesh()); await test.previews[0].promise; const expanded = { ...bounds, z_min: -2, z_max: 2 }; test.controller.setDocument({ ...document, bounds: expanded }); const staged = sceneWithMesh(expanded, 1); staged.implicit_cell_targets = []; staged.packed_geometry = { meshes: [{ geometryId: 2, positions: new Float32Array([0, 0, 0, 0.1, 0, 0, 0, 0.1, 0]), normals: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1]), indices: new Uint32Array([0, 1, 2]) }], points: [], curves: [], vectors: [] }; test.previews[1].resolve(staged); await test.previews[1].promise; expect(test.publish).toHaveBeenCalledTimes(1); [...test.timers.values()][0](); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0 }, scene: { ...scene(), bounds: expanded }, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); }); it('publishes several refined parts once per animation frame', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const refinedPart = scene(); refinedPart.implicit_cell_targets = []; const part = { part: { kind: 'implicit-cell' as const, expressionId: 'implicit', representation: 'surface_mesh' as const, cell: { level: 0, index: [0, 0, 0] as [number, number, number] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1 as const, modelRevision: 3, priority: 0 }, scene: refinedPart, geometry: { meshes: [], points: [], curves: [], vectors: [] } }; test.refinements[0].onpart(part); test.refinements[0].onpart(part); expect(test.publish).toHaveBeenCalledTimes(1); const frame = [...test.frames.values()][0]; frame(1010); expect(test.publish).toHaveBeenCalledTimes(2); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); }); it('buffers quality-2 replacements until the refinement transaction completes', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const refinedPart = scene(); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0 }, scene: refinedPart, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.publish).toHaveBeenCalledTimes(1); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].resources).toEqual([]); }); it('settles at q2 while retaining an atomic q3 upgrade for later', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const q2Scene = scene(); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 2, modelRevision: 3, priority: 0 }, scene: q2Scene, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].onstage?.('q2'); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.setBusy).toHaveBeenLastCalledWith(false); expect(test.setPhase).toHaveBeenLastCalledWith('idle'); test.refinements[0].onpart({ part: { kind: 'implicit-adaptive-child', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: -1, index: [0, 0, 0] }, region: 'cell-l-1-x0-y0-z0', qualityLevel: 3, modelRevision: 3, priority: 0, parent: { level: 0, index: [0, 0, 0] }, childBits: [0, 0, 0], atomicGroup: 'group', replacementRegion: 'cell-l0-x0-y0-z0', transitionMask: 0, estimatedError: 1 }, scene: scene(), geometry: { meshes: [], points: [], curves: [], vectors: [] } }); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(3); }); it('keeps q2 visible when optional q3 refinement fails', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); test.refinements[0].onstage?.('q2'); test.refinements[0].reject(new Error('q3 failed')); await Promise.resolve(); await Promise.resolve(); expect(test.setError).not.toHaveBeenCalledWith('q3 failed'); expect(test.setBusy).toHaveBeenLastCalledWith(false); expect(test.setPhase).toHaveBeenLastCalledWith('idle'); }); it('discards an unflushed refinement batch when the document changes', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].resolve(scene()); await test.previews[0].promise; [...test.timers.values()][0](); const refinedPart = scene(); test.refinements[0].onpart({ part: { kind: 'implicit-cell', expressionId: 'implicit', representation: 'surface_mesh', cell: { level: 0, index: [0, 0, 0] }, region: 'cell-l0-x0-y0-z0', qualityLevel: 1, modelRevision: 3, priority: 0 }, scene: refinedPart, geometry: { meshes: [], points: [], curves: [], vectors: [] } }); expect(test.frames.size).toBe(1); test.controller.setDocument({ ...document, expressions: [{ ...document.expressions[0], source: 'x^2+y^2+z^2=2' }] }); expect(test.frames.size).toBe(0); test.previews[1].resolve(scene()); await test.previews[1].promise; expect(test.publish).toHaveBeenCalledTimes(2); }); it('fences preview errors and returns to idle after the current revision fails', async () => { const test = harness(); test.controller.setDocument(document); test.previews[0].reject(new Error('preview failed')); await expect(test.previews[0].promise).rejects.toThrow('preview failed'); await Promise.resolve(); expect(test.setBusy).toHaveBeenLastCalledWith(false); expect(test.setPhase).toHaveBeenLastCalledWith('idle'); }); it('keeps the GPU preview until a complete settled crease group is available', async () => { const test = harness(); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; [...test.timers.values()][0](); const targets = test.refinements[0].targets.filter( (target): target is Extract => target.kind === 'settled-continuous-crease-patch' ); expect(targets).toHaveLength(16); for (const target of targets.slice(0, 15)) { test.refinements[0].onpart(settledPartFor(target, target.patch[1] * 4 + target.patch[0] + 1)); } test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.publish.mock.calls[0][0].resources.some((resource: RenderScene3d['resources'][number]) => resource.type === 'gpu_surface')).toBe(true); const second = harness(); second.controller.setDocument(settledDocument); second.previews[0].resolve(settledPreviewScene()); await second.previews[0].promise; [...second.timers.values()][0](); const secondTargets = second.refinements[0].targets.filter( (target): target is Extract => target.kind === 'settled-continuous-crease-patch' ); for (const target of secondTargets) { second.refinements[0].onpart(settledPartFor(target, target.patch[1] * 4 + target.patch[0] + 1)); } second.refinements[0].resolve(); await Promise.resolve(); expect(second.publish).toHaveBeenCalledTimes(2); const settled = second.publish.mock.calls[1][0] as RenderScene3d; expect(settled.resources.some((resource) => resource.type === 'gpu_surface')).toBe(false); expect(settled.resources.filter((resource) => resource.type === 'mesh' && resource.key.expression_id === 'crease' )).toHaveLength(16); }); it('treats a failed optional settled crease group as a silent GPU-preserving fallback', async () => { const test = harness(); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; [...test.timers.values()][0](); const target = test.refinements[0].targets.find( (candidate): candidate is Extract => candidate.kind === 'settled-continuous-crease-patch' )!; test.refinements[0].onpart(settledPartFor(target, 1)); test.refinements[0].reject(new Error('optional settled mesh failed')); await Promise.resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(1); expect(test.setError.mock.calls.filter(([value]) => value !== null)).toHaveLength(0); expect(test.publish.mock.calls[0][0].resources.some((resource: RenderScene3d['resources'][number]) => resource.type === 'gpu_surface')).toBe(true); }); it('routes retained settled roots to atomic q2 children after the q1 publication', async () => { const test = harness(); const view: Scene3dViewSnapshot = { viewMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], projectionMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], viewportWidth: 640, viewportHeight: 480 }; test.controller.setViewSnapshot(view); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; [...test.timers.values()][0](); const roots = test.refinements[0].targets.filter( (target): target is Extract => target.kind === 'settled-continuous-crease-patch' ); expect(roots).toHaveLength(16); for (const target of roots) { test.refinements[0].onpart(settledPartFor(target, target.patch[1] * 4 + target.patch[0] + 1)); } test.refinements[0].onstage?.('q2'); test.refinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(2); test.setNow(1100); test.controller.setViewSnapshot({ ...view, viewportWidth: 800 }); [...test.timers.values()].at(-1)!(); expect(test.adaptiveRefinements).toHaveLength(1); const children = test.adaptiveRefinements[0].targets; expect(children).toHaveLength(64); expect(children.every((target) => target.kind === 'settled-continuous-crease-adaptive-child')).toBe(true); for (const [index, target] of children.entries()) { if (target.kind === 'settled-continuous-crease-adaptive-child') { test.adaptiveRefinements[0].onpart(settledAdaptivePartFor(target, 100 + index)); } } test.adaptiveRefinements[0].resolve(); await Promise.resolve(); expect(test.publish).toHaveBeenCalledTimes(3); const merged = test.publish.mock.calls[2][0] as RenderScene3d; expect(merged.resources.some((resource) => resource.type === 'gpu_surface')).toBe(false); expect(merged.resources.filter((resource) => resource.type === 'mesh' && resource.key.expression_id === 'crease' )).toHaveLength(64); expect(merged.resources.some((resource) => /^patch-\d-\d$/.test(resource.key.region))).toBe(false); expect(merged.settled_continuous_crease_adaptive_targets).toEqual([]); }); it('restores the cached GPU immediately for an override-only edit after settlement', async () => { const test = harness(); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; [...test.timers.values()][0](); const targets = test.refinements[0].targets.filter( (target): target is Extract => target.kind === 'settled-continuous-crease-patch' ); for (const target of targets) { test.refinements[0].onpart(settledPartFor(target, target.patch[1] * 4 + target.patch[0] + 1)); } test.refinements[0].resolve(); await Promise.resolve(); test.controller.setDocument({ ...settledDocument, variable_overrides: { a: 2 } }); expect(test.publish).toHaveBeenCalledTimes(3); const restored = test.publish.mock.calls[2][0] as RenderScene3d; expect(restored.resources.filter((resource) => resource.type === 'mesh')).toHaveLength(0); expect(restored.resources[0]).toMatchObject({ type: 'gpu_surface', depth_mode: 'interpolated', uniforms: [{ variable: 'a', value: 2 }] }); expect(test.previews).toHaveLength(2); }); it.each([ ['source', { expressions: settledDocument.expressions.map((expression) => expression.id === 'crease' ? { ...expression, source: 'z = a*x' } : expression ) }], ['complex mode', { complex_mode: true }], ['bounds', { bounds: { ...bounds, x_max: 2 } }] ])('does not resurrect cached GPU after an incompatible %s edit', async (_kind, change) => { const test = harness(); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; [...test.timers.values()][0](); const targets = test.refinements[0].targets.filter( (target): target is Extract => target.kind === 'settled-continuous-crease-patch' ); for (const target of targets) { test.refinements[0].onpart(settledPartFor(target, target.patch[1] * 4 + target.patch[0] + 1)); } test.refinements[0].resolve(); await Promise.resolve(); const changed = { ...settledDocument, ...change }; test.controller.setDocument(changed); test.previews[1].resolve(scene()); await test.previews[1].promise; // A later override-only update is the synchronous restore entry point. If // the old cache survived the incompatible edit, it would reintroduce the // settled document's whole GPU surface here. test.controller.setDocument({ ...changed, variable_overrides: { a: 2 } }); const restored = test.publish.mock.calls.at(-1)?.[0] as RenderScene3d; expect(restored.resources.some((resource) => resource.type === 'gpu_surface')).toBe(false); }); it('updates colors without rebuilding an already displayed settled scene', async () => { const test = harness(); test.controller.setDocument(settledDocument); test.previews[0].resolve(settledPreviewScene()); await test.previews[0].promise; const recolored = { ...settledDocument, expressions: settledDocument.expressions.map((expression) => expression.id === 'crease' ? { ...expression, color: '#dc2626' } : expression ) }; test.controller.setDocument(recolored); expect(test.previews).toHaveLength(1); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].resources[0]).toMatchObject({ color: '#dc2626' }); }); it('updates retained GPU uniforms immediately for override-only edits', async () => { const test = harness(); const dynamicDocument: GraphDocument3d = { ...document, expressions: [{ id: 'surface', input_mode: 'text', source: 'a := 1', enabled: true, color: '#2563eb' }], variable_overrides: { a: 1 } }; const gpuScene = scene([{ type: 'gpu_surface', key: { expression_id: 'surface', representation: 'surface_gpu', region: 'whole', quality_level: 0 }, geometry_id: 7, model_revision: 1, bounds, grid_size: 257, depth_mode: 'exact', value_glsl: 'u_cartesium_a * x', dx_glsl: 'u_cartesium_a', dy_glsl: '0.0', uniforms: [{ name: 'u_cartesium_a', variable: 'a', value: 1 }], color: '#2563eb' }]); gpuScene.variables = [{ expression_id: 'surface', name: 'a', value: 1, slider: null }]; test.controller.setDocument(dynamicDocument); test.previews[0].resolve(gpuScene); await test.previews[0].promise; test.controller.setDocument({ ...dynamicDocument, variable_overrides: { a: 2 } }); expect(test.publish).toHaveBeenCalledTimes(2); expect(test.publish.mock.calls[1][0].resources[0]).toMatchObject({ type: 'gpu_surface', depth_mode: 'interpolated', uniforms: [{ variable: 'a', value: 2 }] }); expect(test.publish.mock.calls[1][0].variables[0].value).toBe(2); const authoritativeScene = { ...gpuScene, implicit_cell_targets: [], resources: [{ ...gpuScene.resources[0], depth_mode: 'exact' as const, uniforms: [{ name: 'u_cartesium_a', variable: 'a', value: 2 }] }], variables: [{ expression_id: 'surface', name: 'a', value: 2, slider: null }] }; test.previews[1].resolve(authoritativeScene); await test.previews[1].promise; expect(test.publish).toHaveBeenCalledTimes(3); expect(test.publish.mock.calls[2][0].resources[0]).toMatchObject({ type: 'gpu_surface', depth_mode: 'exact', uniforms: [{ variable: 'a', value: 2 }] }); }); });