@hology/core 0.0.247 → 0.0.248

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/gameplay/actors/actor.js +1 -1
  2. package/dist/gameplay/actors/builtin/components/spline-component.js +1 -1
  3. package/dist/gameplay/actors/builtin/components/water-post-process-settings.d.ts +24 -0
  4. package/dist/gameplay/actors/builtin/components/water-post-process-settings.js +4 -0
  5. package/dist/gameplay/actors/builtin/post-process-volume-actor.d.ts +1 -2
  6. package/dist/gameplay/actors/builtin/water-spline-volume-actor.d.ts +11 -1
  7. package/dist/gameplay/actors/builtin/water-spline-volume-actor.js +1 -1
  8. package/dist/gameplay/actors/builtin/water-volume-actor.d.ts +12 -1
  9. package/dist/gameplay/actors/builtin/water-volume-actor.js +1 -1
  10. package/dist/gameplay/actors/camera/camera-component.js +1 -1
  11. package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
  12. package/dist/gameplay/actors/component.d.ts +0 -1
  13. package/dist/gameplay/actors/component.js +1 -1
  14. package/dist/gameplay/actors/internal/component-init.d.ts +1 -1
  15. package/dist/gameplay/actors/internal/component-init.js +1 -1
  16. package/dist/gameplay/inject.d.ts +3 -0
  17. package/dist/gameplay/inject.js +1 -1
  18. package/dist/gameplay/services/actor-update-registry.d.ts +13 -0
  19. package/dist/gameplay/services/actor-update-registry.js +4 -0
  20. package/dist/gameplay/services/render.d.ts +1 -0
  21. package/dist/gameplay/services/render.js +1 -1
  22. package/dist/gameplay/services/world.js +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1 -1
  25. package/dist/rendering/webgpu-experimental-backend.js +1 -1
  26. package/dist/rendering.d.ts +8 -0
  27. package/dist/rendering.js +1 -1
  28. package/dist/scene/materializer.js +1 -1
  29. package/dist/shader/graph/compiler.js +1 -1
  30. package/dist/shader/graph/model.d.ts +1 -1
  31. package/dist/shader/graph/registry.js +1 -1
  32. package/dist/shader/parameter.d.ts +3 -0
  33. package/dist/shader/post-process-shader.js +1 -1
  34. package/dist/shader-nodes/depth.d.ts +3 -1
  35. package/dist/shader-nodes/depth.js +1 -1
  36. package/dist/shader-nodes/index.d.ts +1 -0
  37. package/dist/shader-nodes/index.js +1 -1
  38. package/dist/shader-nodes/water-volume.d.ts +9 -0
  39. package/dist/shader-nodes/water-volume.js +4 -0
  40. package/dist/shader-nodes/water.d.ts +12 -0
  41. package/dist/shader-nodes/water.js +4 -0
  42. package/dist/test/materializer-dynamic-transform.test.js +1 -1
  43. package/dist/test/post-process-effect.test.js +1 -1
  44. package/dist/test/spline-component.test.js +1 -1
  45. package/dist/test/view-controller-updates.test.d.ts +2 -0
  46. package/dist/test/view-controller-updates.test.js +4 -0
  47. package/dist/test/water-spline-volume-actor.test.js +1 -1
  48. package/package.json +3 -3
  49. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import{describe as e,expect as t,it as o,vi as r}from"vitest";import*as n from"three";import{compileNodeShaderMaterialForWebGpu as a,float as s,NodeShaderMaterial as i,uniformFloat as l}from"../shader-nodes/index.js";import{depthUniformName as u,farUniformName as m,linearEyeDepth as f,nearUniformName as d,resolutionUniformName as p,screenUV as c,sceneNormalUniformName as h}from"../shader-nodes/depth.js";import{aoMapUniformName as g}from"../shader-nodes/scene-sample.js";import{elapsedTimeUniformName as T,time as v}from"../shader-nodes/time.js";import{sampleSceneColor as b}from"../shader-nodes/scene-sample.js";import{NodeMaterialWebGpuResolver as w}from"../rendering/node-material-webgpu-resolver.js";import{DecalStandardShader as A}from"../shader/builtin/decal-standard-shader.js";import{applyPostProcessEffectUniformState as y,defaultPostProcessEffectStage as C,PostProcessEffectPass as B,PostProcessEffectRegistration as P,postProcessEffectStages as V}from"../rendering/post-process-effect.js";import{createPostProcessMaterial as x,PostProcessShader as S}from"../shader/post-process-shader.js";r.hoisted(()=>{if("undefined"!=typeof window){if("undefined"!=typeof HTMLCanvasElement){const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&r.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}class e{constructor(){this.currentTime=0,this.destination={},this.listener={positionX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},positionY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},positionZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()}}}createGain(){return{connect:r.fn(),disconnect:r.fn(),gain:{value:1,setValueAtTime:r.fn()}}}}Object.defineProperty(window,"AudioContext",{configurable:!0,value:e}),Object.defineProperty(window,"webkitAudioContext",{configurable:!0,value:e})}});class j extends S{output(){return s(1)}}e("post-process effect",()=>{o("exports stable stage names with a default late-stage slot",()=>{t(V).toEqual(["beforeFog","beforeDepthOfField","beforeColorAdjustment","beforeOutline","beforeAntiAliasing","beforeLut","beforeOutput"]),t(C).toBe("beforeOutput")}),o("builds post-process shaders with fullscreen-safe defaults",()=>{const e=(new j).build();t(e).toBeInstanceOf(i),t(e.depthWrite).toBe(!1),t(e.depthTest).toBe(!1),t(e.toneMapped).toBe(!1)}),o("updates shared renderer uniforms on post-process effect materials",()=>{const e=x(s(1));e.uniforms[p]={value:new n.Vector2},e.uniforms[u]={value:null},e.uniforms[h]={value:null},e.uniforms[d]={value:0},e.uniforms[m]={value:0},e.uniforms[T]={value:0},e.uniforms[g]={value:null};const o=new n.Texture,r=new n.Texture,a=new n.Texture,i=new n.Vector2(640,360);y(e,{aoEnabled:!0,aoTexture:a,cameraFar:500,cameraNear:.25,depthTexture:o,normalTexture:r,resolution:i,simulationTime:12.5}),t(e.uniforms[p].value.toArray()).toEqual([640,360]),t(e.uniforms[u].value).toBe(o),t(e.uniforms[h].value).toBe(r),t(e.uniforms[d].value).toBe(.25),t(e.uniforms[m].value).toBe(500),t(e.uniforms[T].value).toBe(12.5),t(e.uniforms[g].value).toBe(a),t(e.defines.USE_SSAO_MAP).toBe(""),y(e,{aoEnabled:!1,aoTexture:null,cameraFar:500,cameraNear:.25,depthTexture:o,normalTexture:r,resolution:i,simulationTime:13}),t(e.uniforms[g].value).toBeNull(),t(e.defines.USE_SSAO_MAP).toBeUndefined()}),o("compiles screen UV against the WebGPU fullscreen ABI",()=>{const e=x(b(c)),o=new w({}).compileFullscreen(e);t(o.fragmentShader).toContain("input.uv"),t(o.fragmentShader).not.toContain("rendererCamera.viewportAndTime"),t(o.fragmentShader).not.toContain("gl_FragCoord"),t(o.uniforms[p]).toBeUndefined();const r=a(e);t(r.fragmentShader).not.toContain("hologyFullscreenUv"),t(r.fragmentShader).toContain("input.position.xy"),t(r.fragmentShader).toContain("materialUniforms.hology_resolution"),t(r.fragmentShader).toContain("(input.position.xy / max(materialUniforms.hology_resolution, vec2<f32>(1.0, 1.0)))");const n=new w({}).compileFullscreen(x(v));t(n.fragmentShader).not.toContain("rendererElapsedTime"),t(n.uniforms[T]).toBeTruthy();const s=new w({}).compileFullscreen(x(f));t(s.fragmentShader).toContain("input.uv"),t(s.fragmentShader).toContain("textureSample(hology_depth_mapTexture, hology_depth_mapSampler,")}),o("uses runtime material uniform values in WebGPU fullscreen passes",()=>{const e="hology_post_process_weight",o=x(l(e,1)),r=new w({}).compileFullscreen(o);o.uniforms[e].value=.25,r.syncLiveUniforms(r.uniforms),t(r.uniforms[e].value).toBe(.25)}),o("keeps WebGPU decal varyings within the portable location limit",()=>{const e=(new A).build();t(e).toBeInstanceOf(i);const o=a(e),r=[...o.vertexShader.matchAll(/@location\((\d+)\)/g)].map(e=>Number(e[1]));t(Math.max(...r)).toBeLessThan(16),t(o.vertexShader).not.toContain("undefined"),t(o.fragmentShader).not.toContain("undefined")}),o("keeps registration state shared with both renderer adapters",()=>{const e=x(s(1)),o=new B(e),n={id:7,material:e,pass:o,enabled:!0,priority:0,stage:"beforeOutput"},a={refreshPostProcessEffectPassOrder:r.fn(),removePostProcessEffectEntry:r.fn()},i=new P(a,n);t(i.material).toBe(e),i.enabled=!1,t(n.enabled).toBe(!1),t(o.enabled).toBe(!1),i.priority=3,i.stage="beforeFog",t(n.priority).toBe(3),t(n.stage).toBe("beforeFog"),t(a.refreshPostProcessEffectPassOrder).toHaveBeenCalledTimes(3),i.dispose(),i.dispose(),t(a.removePostProcessEffectEntry).toHaveBeenCalledTimes(1),o.dispose()}),o("exposes post-process effect registration through ViewController",async()=>{const e=x(s(1)),o={material:e,enabled:!1,priority:4,stage:"beforeColorAdjustment",dispose:r.fn()},a=r.fn().mockReturnValue(o),{ViewController:i}=await import("../gameplay/services/render.js"),l=new i({camera:new n.PerspectiveCamera,running:!1,paused:!1,onLoop:r.fn(),setCamera(e){this.camera=e},stop:r.fn(),addPostProcessEffect:a,addPostProcessVolume:r.fn(),removePostProcessVolume:r.fn()}).addPostProcessEffect(e,{enabled:!1,priority:4,stage:"beforeColorAdjustment"});t(a).toHaveBeenCalledWith(e,{enabled:!1,priority:4,stage:"beforeColorAdjustment"}),t(l).toBe(o)})});/*
1
+ import{describe as e,expect as t,it as o,vi as r}from"vitest";import*as n from"three";import{compileNodeShaderMaterialForWebGpu as a,float as s,NodeShaderMaterial as i,uniformFloat as l}from"../shader-nodes/index.js";import{depthUniformName as m,farUniformName as u,linearEyeDepth as f,nearUniformName as d,resolutionUniformName as p,screenUV as c,sceneNormalUniformName as h}from"../shader-nodes/depth.js";import{aoMapUniformName as g}from"../shader-nodes/scene-sample.js";import{elapsedTimeUniformName as T,time as v}from"../shader-nodes/time.js";import{sampleSceneColor as b}from"../shader-nodes/scene-sample.js";import{waterVolumeMask as w,waterVolumeWorldToLocalUniformName as A}from"../shader-nodes/water-volume.js";import{NodeMaterialWebGpuResolver as C}from"../rendering/node-material-webgpu-resolver.js";import{DecalStandardShader as y}from"../shader/builtin/decal-standard-shader.js";import{applyPostProcessEffectUniformState as B,defaultPostProcessEffectStage as P,PostProcessEffectPass as V,PostProcessEffectRegistration as x,postProcessEffectStages as S}from"../rendering/post-process-effect.js";import{createPostProcessMaterial as j,PostProcessShader as E}from"../shader/post-process-shader.js";r.hoisted(()=>{if("undefined"!=typeof window){if("undefined"!=typeof HTMLCanvasElement){const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&r.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}class e{constructor(){this.currentTime=0,this.destination={},this.listener={positionX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},positionY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},positionZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},forwardZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upX:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upY:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()},upZ:{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()}}}createGain(){return{connect:r.fn(),disconnect:r.fn(),gain:{value:1,setValueAtTime:r.fn()}}}}Object.defineProperty(window,"AudioContext",{configurable:!0,value:e}),Object.defineProperty(window,"webkitAudioContext",{configurable:!0,value:e})}});class _ extends E{output(){return s(1)}}e("post-process effect",()=>{o("exports stable stage names with a default late-stage slot",()=>{t(S).toEqual(["beforeFog","beforeDepthOfField","beforeColorAdjustment","beforeOutline","beforeAntiAliasing","beforeLut","beforeOutput"]),t(P).toBe("beforeOutput")}),o("builds post-process shaders with fullscreen-safe defaults",()=>{const e=(new _).build();t(e).toBeInstanceOf(i),t(e.depthWrite).toBe(!1),t(e.depthTest).toBe(!1),t(e.toneMapped).toBe(!1)}),o("updates shared renderer uniforms on post-process effect materials",()=>{const e=j(s(1));e.uniforms[p]={value:new n.Vector2},e.uniforms[m]={value:null},e.uniforms[h]={value:null},e.uniforms[d]={value:0},e.uniforms[u]={value:0},e.uniforms[T]={value:0},e.uniforms[g]={value:null};const o=new n.Texture,r=new n.Texture,a=new n.Texture,i=new n.Vector2(640,360);B(e,{aoEnabled:!0,aoTexture:a,cameraFar:500,cameraNear:.25,depthTexture:o,normalTexture:r,resolution:i,simulationTime:12.5}),t(e.uniforms[p].value.toArray()).toEqual([640,360]),t(e.uniforms[m].value).toBe(o),t(e.uniforms[h].value).toBe(r),t(e.uniforms[d].value).toBe(.25),t(e.uniforms[u].value).toBe(500),t(e.uniforms[T].value).toBe(12.5),t(e.uniforms[g].value).toBe(a),t(e.defines.USE_SSAO_MAP).toBe(""),B(e,{aoEnabled:!1,aoTexture:null,cameraFar:500,cameraNear:.25,depthTexture:o,normalTexture:r,resolution:i,simulationTime:13}),t(e.uniforms[g].value).toBeNull(),t(e.defines.USE_SSAO_MAP).toBeUndefined()}),o("compiles screen UV against the WebGPU fullscreen ABI",()=>{const e=j(b(c)),o=new C({}).compileFullscreen(e);t(o.fragmentShader).toContain("input.uv"),t(o.fragmentShader).not.toContain("rendererCamera.viewportAndTime"),t(o.fragmentShader).not.toContain("gl_FragCoord"),t(o.uniforms[p]).toBeUndefined();const r=a(e);t(r.fragmentShader).not.toContain("hologyFullscreenUv"),t(r.fragmentShader).toContain("input.position.xy"),t(r.fragmentShader).toContain("materialUniforms.hology_resolution"),t(r.fragmentShader).toContain("(input.position.xy / max(materialUniforms.hology_resolution, vec2<f32>(1.0, 1.0)))");const n=new C({}).compileFullscreen(j(v));t(n.fragmentShader).not.toContain("rendererElapsedTime"),t(n.uniforms[T]).toBeTruthy();const s=new C({}).compileFullscreen(j(f));t(s.fragmentShader).toContain("input.uv"),t(s.fragmentShader).toContain("textureSample(hology_depth_mapTexture, hology_depth_mapSampler,")}),o("uses runtime material uniform values in WebGPU fullscreen passes",()=>{const e="hology_post_process_weight",o=j(l(e,1)),r=new C({}).compileFullscreen(o);o.uniforms[e].value=.25,r.syncLiveUniforms(r.uniforms),t(r.uniforms[e].value).toBe(.25)}),o("compiles the depth-reconstructed water volume mask for WebGPU fullscreen passes",()=>{const e=j(w),o=new C({}).compileFullscreen(e);t(o.fragmentShader).toContain("hology_depth_map"),t(o.uniforms[A]).toBeTruthy(),t(o.fragmentShader).not.toContain("rendererViewMatrix"),t(o.fragmentShader).not.toContain("rendererProjectionMatrix")}),o("keeps WebGPU decal varyings within the portable location limit",()=>{const e=(new y).build();t(e).toBeInstanceOf(i);const o=a(e),r=[...o.vertexShader.matchAll(/@location\((\d+)\)/g)].map(e=>Number(e[1]));t(Math.max(...r)).toBeLessThan(16),t(o.vertexShader).not.toContain("undefined"),t(o.fragmentShader).not.toContain("undefined")}),o("keeps registration state shared with both renderer adapters",()=>{const e=j(s(1)),o=new V(e),n={id:7,material:e,pass:o,enabled:!0,priority:0,stage:"beforeOutput"},a={refreshPostProcessEffectPassOrder:r.fn(),removePostProcessEffectEntry:r.fn()},i=new x(a,n);t(i.material).toBe(e),i.enabled=!1,t(n.enabled).toBe(!1),t(o.enabled).toBe(!1),i.priority=3,i.stage="beforeFog",t(n.priority).toBe(3),t(n.stage).toBe("beforeFog"),t(a.refreshPostProcessEffectPassOrder).toHaveBeenCalledTimes(3),i.dispose(),i.dispose(),t(a.removePostProcessEffectEntry).toHaveBeenCalledTimes(1),o.dispose()}),o("exposes post-process effect registration through ViewController",async()=>{const e=j(s(1)),o={material:e,enabled:!1,priority:4,stage:"beforeColorAdjustment",dispose:r.fn()},a=r.fn().mockReturnValue(o),{ViewController:i}=await import("../gameplay/services/render.js"),l=new i({camera:new n.PerspectiveCamera,running:!1,paused:!1,onLoop:r.fn(),setCamera(e){this.camera=e},stop:r.fn(),addPostProcessEffect:a,addPostProcessVolume:r.fn(),removePostProcessVolume:r.fn()}).addPostProcessEffect(e,{enabled:!1,priority:4,stage:"beforeColorAdjustment"});t(a).toHaveBeenCalledWith(e,{enabled:!1,priority:4,stage:"beforeColorAdjustment"}),t(l).toBe(o)})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,4 +1,4 @@
1
- import{describe as n,expect as t,it as e,vi as o}from"vitest";import{Vector3 as s}from"three";import{SplineComponent as i,packSplineData as a}from"../gameplay/actors/builtin/components/spline-component.js";function p(n=!1){const t=new i;return t.setData(a([{position:new s(0,0,0),inTangent:new s,outTangent:new s(10/3,0,0)},{position:new s(10,0,0),inTangent:new s(-10/3,0,0),outTangent:new s}],n),!1),t}o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const n=new Proxy({},{get:(n,t)=>(t in n||(n[t]=o.fn()),n[t]),set:(n,t,e)=>(n[t]=e,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>n})}),n("SplineComponent",()=>{e("samples cubic position and tangent",()=>{const n=p();t(n.samplePosition(.5).x).toBeCloseTo(5),t(n.sampleTangent(.5).toArray()).toEqual([1,0,0])}),e("builds and uses an arc-length approximation",()=>{const n=p();t(n.length).toBeCloseTo(10),t(n.samplePositionAtDistance(2.5).x).toBeCloseTo(2.5,2)}),e("finds the closest point and distance along the spline",()=>{const n=p().closestPoint(new s(4,3,0));t(n.position.x).toBeCloseTo(4,2),t(n.distance).toBeCloseTo(3,2),t(n.distanceAlongSpline).toBeCloseTo(4,2)}),e("round trips compact point data",()=>{const n=p(!0),e=new i;e.setData(n.splineData,!1),t(e.closed).toBe(!0),t(e.points[1].inTangent.x).toBeCloseTo(-10/3),t(n.splineData).not.toContain("\n")}),e("inserts a point without changing the Bezier shape",()=>{const n=p(),e=Array.from({length:9},(t,e)=>n.samplePosition(e/8).clone());t(n.insertPointOnSegment(0)).toBe(1),t(n.points).toHaveLength(3),e.forEach((e,o)=>{t(n.samplePosition(o/8).distanceTo(e)).toBeLessThan(1e-5)})})});/*
1
+ import{describe as e,expect as t,it as n,vi as o}from"vitest";import{Vector3 as s}from"three";import{SplineComponent as i,packSplineData as a}from"../gameplay/actors/builtin/components/spline-component.js";import{extractShaderParameters as r}from"../shader/parameter.js";function p(e=!1){const t=new i;return t.setData(a([{position:new s(0,0,0),inTangent:new s,outTangent:new s(10/3,0,0)},{position:new s(10,0,0),inTangent:new s(-10/3,0,0),outTangent:new s}],e),!1),t}o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),e("SplineComponent",()=>{n("samples cubic position and tangent",()=>{const e=p();t(e.samplePosition(.5).x).toBeCloseTo(5),t(e.sampleTangent(.5).toArray()).toEqual([1,0,0])}),n("builds and uses an arc-length approximation",()=>{const e=p();t(e.length).toBeCloseTo(10),t(e.samplePositionAtDistance(2.5).x).toBeCloseTo(2.5,2)}),n("finds the closest point and distance along the spline",()=>{const e=p().closestPoint(new s(4,3,0));t(e.position.x).toBeCloseTo(4,2),t(e.distance).toBeCloseTo(3,2),t(e.distanceAlongSpline).toBeCloseTo(4,2)}),n("round trips compact point data",()=>{const e=p(!0),n=new i;n.setData(e.splineData,!1),t(n.closed).toBe(!0),t(n.points[1].inTangent.x).toBeCloseTo(-10/3),t(e.splineData).not.toContain("\n")}),n("declares spline data as a string editor parameter",()=>{const e=r(i).find(e=>"splineData"===e.name);t(e?.type).toBe(String)}),n("inserts a point without changing the Bezier shape",()=>{const e=p(),n=Array.from({length:9},(t,n)=>e.samplePosition(n/8).clone());t(e.insertPointOnSegment(0)).toBe(1),t(e.points).toHaveLength(3),n.forEach((n,o)=>{t(e.samplePosition(o/8).distanceTo(n)).toBeLessThan(1e-5)})})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=view-controller-updates.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as e,expect as t,it as o,vi as r}from"vitest";import*as n from"three";import{Subject as s}from"rxjs";import{ViewController as a}from"../gameplay/services/render.js";function i(){let e;const t={camera:new n.PerspectiveCamera,running:!1,paused:!1,simulationTimeScale:1,onLoop(t){e=t},stop:r.fn()};return{viewController:new a(t),stepFrame(t){e?.(t)}}}r.hoisted(()=>{if("undefined"!=typeof HTMLCanvasElement){HTMLCanvasElement.prototype.getContext=r.fn(()=>new Proxy({},{get:(e,t)=>(t in e||(e[t]=r.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)}));class e{constructor(){this.currentTime=0,this.destination={},this.listener=Object.fromEntries(["positionX","positionY","positionZ","forwardX","forwardY","forwardZ","upX","upY","upZ"].map(e=>[e,{linearRampToValueAtTime:r.fn(),setValueAtTime:r.fn()}]))}createGain(){return{connect:r.fn(),disconnect:r.fn(),gain:{value:1,setValueAtTime:r.fn()}}}}Object.defineProperty(window,"AudioContext",{configurable:!0,value:e}),Object.defineProperty(window,"webkitAudioContext",{configurable:!0,value:e})}}),e("ViewController actor updates",()=>{o("dispatches framework updates directly and preserves observable updates",()=>{const{viewController:e,stepFrame:o}=i(),r={disposed:new s},n=[];e.onUpdate().subscribe(()=>n.push("observable")),e.registerActorUpdate(r,()=>n.push("actor")),e.registerActorUpdate(r,()=>n.push("late"),!0),o(1/60),t(n).toEqual(["observable","actor","late"])}),o("stops actor-scoped observable and direct updates when disposed",()=>{const{viewController:e,stepFrame:o}=i(),n={disposed:new s},a=r.fn(),p=r.fn();e.onUpdate(n).subscribe(a),e.registerActorUpdate(n,p),o(1/60),n.disposed.next(!0),o(1/60),t(a).toHaveBeenCalledTimes(1),t(p).toHaveBeenCalledTimes(1)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,4 +1,4 @@
1
- import{describe as e,expect as t,it as n,vi as o}from"vitest";import{Box3 as i,Vector3 as a}from"three";import{NavigationArea as s}from"../gameplay/ai/navigation-areas.js";import{WaterSplineVolume as r}from"../gameplay/actors/builtin/water-spline-volume-actor.js";import{packSplineData as l}from"../gameplay/actors/builtin/components/spline-component.js";function m(e,t){e.spline.setData(l([{position:new a,inTangent:new a,outTangent:new a(t/3,0,0)},{position:new a(t,0,0),inTangent:new a(-t/3,0,0),outTangent:new a}],!1),!1)}o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),e("WaterSplineVolume",()=>{n("creates water navigation prisms centered below the spline surface",()=>{const e=new r;e.width=4,e.depth=3,m(e,10);const n=e.getNavigationAreaModifiers();t(n).toHaveLength(20),t(n.every(e=>e.area===s.Water)).toBe(!0),t(n[0].vertices[0]).toEqual([0,0,2]),t(n[0].vertices[1]).toEqual([0,0,-2]),t(n[0].minHeight).toBe(-3),t(n[0].maxHeight).toBe(0)}),n("rebuilds the editor visualization when the spline changes",()=>{const e=new r;e.width=2,e.depth=1,m(e,2);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=[...n.visual.children],a=(new i).setFromObject(n.visual);t(a.max.x).toBeCloseTo(2),m(e,6),e.spline.changed.next(e.spline),a.setFromObject(n.visual),t(a.max.x).toBeCloseTo(6),t(a.min.y).toBeCloseTo(-1),t(n.visual.children).toEqual(o),t(o.every(e=>!1===e.frustumCulled)).toBe(!0)}),n("clamps offset joins at sharp turns",()=>{const e=new r;e.width=10,e.depth=1,e.spline.setData(l([{position:new a(-1,0,0),inTangent:new a,outTangent:new a(1/3,0,0)},{position:new a(0,0,0),inTangent:new a(-1/3,0,0),outTangent:new a(0,0,1/3)},{position:new a(0,0,1),inTangent:new a(0,0,-1/3),outTangent:new a}],!1),!1);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=(new i).setFromObject(n.visual);t(o.min.x).toBeGreaterThanOrEqual(-1-e.width*u/2),t(o.max.x).toBeLessThanOrEqual(e.width*u/2),t(o.min.z).toBeGreaterThanOrEqual(-e.width*u/2),t(o.max.z).toBeLessThanOrEqual(1+e.width*u/2)})});const u=1.5;/*
1
+ import{describe as e,expect as t,it as n,vi as o}from"vitest";import{Box3 as a,ShaderMaterial as i,Vector3 as s}from"three";import{NavigationArea as r}from"../gameplay/ai/navigation-areas.js";import{createWaterSplineSurfaceGeometry as l,WaterSplineVolume as c}from"../gameplay/actors/builtin/water-spline-volume-actor.js";import{packSplineData as u}from"../gameplay/actors/builtin/components/spline-component.js";function p(e,t){e.spline.setData(u([{position:new s,inTangent:new s,outTangent:new s(t/3,0,0)},{position:new s(t,0,0),inTangent:new s(-t/3,0,0),outTangent:new s}],!1),!1)}o.hoisted(()=>{if("undefined"!=typeof HTMLCanvasElement){const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),e("WaterSplineVolume",()=>{n("creates water navigation prisms centered below the spline surface",()=>{const e=new c;e.width=4,e.depth=3,p(e,10);const n=e.getNavigationAreaModifiers();t(n).toHaveLength(20),t(n.every(e=>e.area===r.Water)).toBe(!0),t(n[0].vertices[0]).toEqual([0,0,2]),t(n[0].vertices[1]).toEqual([0,0,-2]),t(n[0].minHeight).toBe(-3),t(n[0].maxHeight).toBe(0)}),n("rebuilds the editor visualization when the spline changes",()=>{const e=new c;e.width=2,e.depth=1,p(e,2);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=[...n.visual.children],i=(new a).setFromObject(n.visual);t(i.max.x).toBeCloseTo(2),p(e,6),e.spline.changed.next(e.spline),i.setFromObject(n.visual),t(i.max.x).toBeCloseTo(6),t(i.min.y).toBeCloseTo(-1),t(n.visual.children).toEqual(o),t(o.every(e=>!1===e.frustumCulled)).toBe(!0)}),n("clamps offset joins at sharp turns",()=>{const e=new c;e.width=10,e.depth=1,e.spline.setData(u([{position:new s(-1,0,0),inTangent:new s,outTangent:new s(1/3,0,0)},{position:new s(0,0,0),inTangent:new s(-1/3,0,0),outTangent:new s(0,0,1/3)},{position:new s(0,0,1),inTangent:new s(0,0,-1/3),outTangent:new s}],!1),!1);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=(new a).setFromObject(n.visual);t(o.min.x).toBeGreaterThanOrEqual(-1-e.width*g/2),t(o.max.x).toBeLessThanOrEqual(e.width*g/2),t(o.min.z).toBeGreaterThanOrEqual(-e.width*g/2),t(o.max.z).toBeLessThanOrEqual(1+e.width*g/2);const i=l(e.spline,e.width,.1,1),r=i.getAttribute("position"),p=i.getIndex();for(let e=0;e<p.count;e+=3){const n=p.getX(e),o=p.getX(e+1),a=p.getX(e+2),i=(r.getX(o)-r.getX(n))*(r.getZ(a)-r.getZ(n))-(r.getZ(o)-r.getZ(n))*(r.getX(a)-r.getX(n));t(i).toBeLessThanOrEqual(1e-6)}}),n("creates tessellated surface data for water shaders",()=>{const e=new c;p(e,10);const n=l(e.spline,4,2,2),o=n.getAttribute("position").count;t(o).toBeGreaterThan(0),t(n.getAttribute("uv").count).toBe(o),t(n.getAttribute("waterFlowDirection").count).toBe(o),t(Math.max(...Array.from({length:o},(e,t)=>n.getAttribute("waterDistanceFromBank").getX(t)))).toBeGreaterThan(0),t(Math.max(...Array.from({length:o},(e,t)=>n.getAttribute("waterDistanceAlongSpline").getX(t)))).toBeCloseTo(10)}),n("classifies points inside the authored spline prism",()=>{const e=new c;e.width=4,e.depth=3,p(e,10),t(e.containsWaterPoint(new s(5,-1,0))).toBe(!0),t(e.containsWaterPoint(new s(5,1,0))).toBe(!1),t(e.containsWaterPoint(new s(5,-4,0))).toBe(!1),t(e.containsWaterPoint(new s(5,-1,3))).toBe(!1)}),n("applies underwater graph changes without recreating the actor",()=>{const e=new c,n=new i;t(e.underwater.applySceneParameters({postProcessMaterial:n},new Set(["postProcessMaterial"]))).toBe(!0),t(e.underwater.postProcessMaterial).toBe(n)})});const g=1.5;/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hology/core",
3
- "version": "0.0.247",
3
+ "version": "0.0.248",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -203,7 +203,7 @@
203
203
  "license": "SEE LICENSE IN LICENSE.md",
204
204
  "peerDependencies": {
205
205
  "three": "0.169.0",
206
- "@hology/webgpu-renderer": "^0.1.0"
206
+ "@hology/webgpu-renderer": "^0.1.12"
207
207
  },
208
208
  "peerDependenciesMeta": {
209
209
  "@hology/webgpu-renderer": {
@@ -239,6 +239,6 @@
239
239
  "terser": "^5.25.0",
240
240
  "typescript": "~5.8.2",
241
241
  "vitest": "^4.1.9",
242
- "@hology/webgpu-renderer": "^0.1.10"
242
+ "@hology/webgpu-renderer": "^0.1.12"
243
243
  }
244
244
  }