@hology/core 0.0.210 → 0.0.212

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 (129) hide show
  1. package/dist/effects/sequence/sequence-player.js +1 -1
  2. package/dist/effects/vfx/initializsers.d.ts +8 -1
  3. package/dist/effects/vfx/initializsers.js +1 -1
  4. package/dist/effects/vfx/vfx-collision-behaviour.js +1 -1
  5. package/dist/effects/vfx/vfx-defs.d.ts +10 -1
  6. package/dist/effects/vfx/vfx-defs.js +1 -1
  7. package/dist/effects/vfx/vfx-materializer.js +1 -1
  8. package/dist/effects/vfx/vfx-renderers.d.ts +1 -0
  9. package/dist/effects/vfx/vfx-renderers.js +1 -1
  10. package/dist/gameplay/actors/actor.d.ts +23 -1
  11. package/dist/gameplay/actors/actor.js +1 -1
  12. package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
  13. package/dist/gameplay/actors/builtin/components/character/character-movement-like.d.ts +23 -0
  14. package/dist/gameplay/actors/builtin/components/character/character-movement-like.js +4 -0
  15. package/dist/gameplay/actors/builtin/components/character/character-movement-policy.d.ts +26 -0
  16. package/dist/gameplay/actors/builtin/components/character/character-movement-policy.js +4 -0
  17. package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +145 -55
  18. package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
  19. package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.d.ts +125 -0
  20. package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.js +4 -0
  21. package/dist/gameplay/actors/builtin/components/character/old-character-movement.d.ts +100 -0
  22. package/dist/gameplay/actors/builtin/components/character/old-character-movement.js +4 -0
  23. package/dist/gameplay/actors/builtin/components/index.d.ts +2 -0
  24. package/dist/gameplay/actors/builtin/components/index.js +1 -1
  25. package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
  26. package/dist/gameplay/actors/camera/third-person-camera-component.d.ts +3 -0
  27. package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
  28. package/dist/gameplay/actors/component.js +1 -1
  29. package/dist/gameplay/actors/controller/actor-controller.d.ts +16 -0
  30. package/dist/gameplay/actors/controller/actor-controller.js +4 -0
  31. package/dist/gameplay/actors/factory.d.ts +3 -0
  32. package/dist/gameplay/actors/factory.js +1 -1
  33. package/dist/gameplay/actors/index.d.ts +4 -0
  34. package/dist/gameplay/actors/index.js +1 -1
  35. package/dist/gameplay/actors/internal/component-init.js +1 -1
  36. package/dist/gameplay/ai/behavior-tree/move.d.ts +2 -2
  37. package/dist/gameplay/index.d.ts +3 -1
  38. package/dist/gameplay/index.js +1 -1
  39. package/dist/gameplay/initiate.d.ts +4 -0
  40. package/dist/gameplay/initiate.js +1 -1
  41. package/dist/gameplay/net/browser/index.d.ts +147 -0
  42. package/dist/gameplay/net/browser/index.js +4 -0
  43. package/dist/gameplay/net/index.d.ts +7 -0
  44. package/dist/gameplay/net/index.js +4 -0
  45. package/dist/gameplay/net/net-connection.d.ts +25 -0
  46. package/dist/gameplay/net/net-connection.js +4 -0
  47. package/dist/gameplay/net/net-session.d.ts +70 -0
  48. package/dist/gameplay/net/net-session.js +4 -0
  49. package/dist/gameplay/net/service/net-actor-role.d.ts +12 -0
  50. package/dist/gameplay/net/service/net-actor-role.js +4 -0
  51. package/dist/gameplay/net/service/net-decorator.d.ts +29 -0
  52. package/dist/gameplay/net/service/net-decorator.js +4 -0
  53. package/dist/gameplay/net/service/net-serializer.d.ts +15 -0
  54. package/dist/gameplay/net/service/net-serializer.js +4 -0
  55. package/dist/gameplay/net/service/net-service.d.ts +171 -0
  56. package/dist/gameplay/net/service/net-service.js +4 -0
  57. package/dist/gameplay/net/service/net-utils.d.ts +8 -0
  58. package/dist/gameplay/net/service/net-utils.js +4 -0
  59. package/dist/gameplay/net/service/replication.d.ts +31 -0
  60. package/dist/gameplay/net/service/replication.js +4 -0
  61. package/dist/gameplay/net/service/rpc-decorator.d.ts +21 -0
  62. package/dist/gameplay/net/service/rpc-decorator.js +4 -0
  63. package/dist/gameplay/net/service/rpc.d.ts +35 -0
  64. package/dist/gameplay/net/service/rpc.js +4 -0
  65. package/dist/gameplay/services/asset-loader.d.ts +3 -2
  66. package/dist/gameplay/services/asset-loader.js +1 -1
  67. package/dist/gameplay/services/physics/abstract-physics-system.d.ts +1 -1
  68. package/dist/gameplay/services/physics/physics-system.d.ts +4 -2
  69. package/dist/gameplay/services/physics/physics-system.js +1 -1
  70. package/dist/gameplay/services/world.d.ts +13 -2
  71. package/dist/gameplay/services/world.js +1 -1
  72. package/dist/rendering/color-pass.js +1 -1
  73. package/dist/rendering.d.ts +2 -0
  74. package/dist/rendering.js +1 -1
  75. package/dist/scene/asset-resource-loader.js +1 -1
  76. package/dist/scene/batched-mesh-2.d.ts +9 -0
  77. package/dist/scene/batched-mesh-2.js +1 -1
  78. package/dist/scene/bootstrap.d.ts +2 -0
  79. package/dist/scene/bootstrap.js +1 -1
  80. package/dist/scene/custom-param-runtime-types.js +1 -1
  81. package/dist/scene/landscape/landscape-manager.d.ts +2 -1
  82. package/dist/scene/landscape/landscape-manager.js +1 -1
  83. package/dist/scene/materializer.d.ts +50 -1
  84. package/dist/scene/materializer.js +1 -1
  85. package/dist/scene/model.d.ts +2 -0
  86. package/dist/scene/scatter/painted-scatter-manager.d.ts +45 -0
  87. package/dist/scene/scatter/painted-scatter-manager.js +4 -0
  88. package/dist/scene/scatter/scatter-limits.d.ts +2 -0
  89. package/dist/scene/scatter/scatter-limits.js +4 -0
  90. package/dist/scene/scatter/surface-scatter-manager.js +1 -1
  91. package/dist/scene/storage/storage.d.ts +1 -1
  92. package/dist/scene/storage/storage.js +1 -1
  93. package/dist/shader/builtin/standard-shader.js +1 -1
  94. package/dist/shader/builtin/toon-shader.js +1 -1
  95. package/dist/shader/builtin/unlit-shader.js +1 -1
  96. package/dist/shader/color-layer.js +1 -1
  97. package/dist/shader/graph/compiler.d.ts +7 -4
  98. package/dist/shader/graph/compiler.js +1 -1
  99. package/dist/shader/graph/model.d.ts +1 -1
  100. package/dist/shader/graph/model.js +1 -1
  101. package/dist/shader/graph/parameters.js +1 -1
  102. package/dist/shader/parameter.d.ts +1 -1
  103. package/dist/shader/parameter.js +1 -1
  104. package/dist/shader/sprite-shader.js +1 -1
  105. package/dist/shader-nodes/depth.js +1 -1
  106. package/dist/shader-nodes/scene-sample.js +1 -1
  107. package/dist/test/batched-mesh-2.test.d.ts +2 -0
  108. package/dist/test/batched-mesh-2.test.js +4 -0
  109. package/dist/test/browser-net-session.test.d.ts +2 -0
  110. package/dist/test/browser-net-session.test.js +4 -0
  111. package/dist/test/first-person-camera-component.test.js +1 -1
  112. package/dist/test/net-character-movement.test.d.ts +2 -0
  113. package/dist/test/net-character-movement.test.js +4 -0
  114. package/dist/test/net-property-snapshot.test.d.ts +2 -0
  115. package/dist/test/net-property-snapshot.test.js +4 -0
  116. package/dist/test/painted-scatter-manager.test.d.ts +2 -0
  117. package/dist/test/painted-scatter-manager.test.js +4 -0
  118. package/dist/test/runtime-param-type-inference.test.js +1 -1
  119. package/dist/test/sequence-animation-retiming.test.js +1 -1
  120. package/dist/test/sequence-post-process.test.js +1 -1
  121. package/dist/test/shader-graph.test.js +1 -1
  122. package/dist/test/vfx-random-color-initializer.test.d.ts +2 -0
  123. package/dist/test/vfx-random-color-initializer.test.js +4 -0
  124. package/dist/test/world-prefab-spawn.test.d.ts +2 -0
  125. package/dist/test/world-prefab-spawn.test.js +4 -0
  126. package/dist/utils/three/placeholder-texture.d.ts +3 -0
  127. package/dist/utils/three/placeholder-texture.js +4 -0
  128. package/package.json +10 -2
  129. package/tsconfig.tsbuildinfo +1 -1
@@ -12,6 +12,7 @@ export interface ShaderGraphParameter {
12
12
  id?: string;
13
13
  name: string;
14
14
  type: ShaderGraphParameterType;
15
+ group?: string;
15
16
  defaultValue?: ShaderGraphSerializableValue;
16
17
  }
17
18
  export interface ShaderGraphSocketRef {
@@ -31,7 +32,6 @@ export interface ShaderGraphEdge {
31
32
  export interface ShaderGraphNode {
32
33
  id: string;
33
34
  kind: ShaderGraphNodeKind;
34
- label?: string;
35
35
  params?: Record<string, ShaderGraphSerializableValue>;
36
36
  inputs?: Record<string, ShaderGraphInput>;
37
37
  }
@@ -1,4 +1,4 @@
1
- export const ShaderGraphParameterTypes=["float","boolean","vec2","vec3","vec4","color","texture"];export function createDefaultShaderGraphDocument(e="surface"){return"postProcess"===e?createDefaultPostProcessShaderGraphDocument():{version:1,target:e,shadingModel:"standard",parameters:[{id:"base-color",name:"baseColor",type:"color",defaultValue:"#ffffff"}],nodes:[{id:"base-color",kind:"input.parameter",label:"Parameter",params:{parameter:"baseColor"}}],edges:[],outputs:{color:{input:{nodeId:"base-color",port:"value"}}},layout:{nodes:{"base-color":{x:80,y:80}}}}}export function createDefaultPostProcessShaderGraphDocument(){return{version:1,target:"postProcess",shadingModel:"unlit",parameters:[],nodes:[{id:"screen-uv",kind:"builtin.screenUv",label:"Screen UV"},{id:"scene-color",kind:"scene.sampleColor",label:"Sample Scene Color"}],edges:[{id:"screen-uv-to-scene-color",from:{nodeId:"screen-uv",port:"value"},to:{nodeId:"scene-color",port:"uv"}}],outputs:{color:{input:{nodeId:"scene-color",port:"rgba"}}},layout:{nodes:{"screen-uv":{x:80,y:80},"scene-color":{x:340,y:80}},groups:[]}}}export function createDefaultVfxShaderGraphDocument(e="sprite"){return{version:1,target:e,shadingModel:"unlit",parameters:[{id:"base-color",name:"baseColor",type:"color",defaultValue:"#ffffff"}],nodes:[{id:"base-color",kind:"input.parameter",label:"Parameter",params:{parameter:"baseColor"}},{id:"particle",kind:"builtin.particle",label:"Particle"},{id:"particle-color",kind:"math.multiply",label:"Multiply"}],edges:[{id:"base-color-to-particle-color",from:{nodeId:"base-color",port:"value"},to:{nodeId:"particle-color",port:"a"}},{id:"particle-color-to-particle-color",from:{nodeId:"particle",port:"color"},to:{nodeId:"particle-color",port:"b"}}],outputs:{color:{input:{nodeId:"particle-color",port:"value"}},opacity:{input:{nodeId:"particle",port:"opacity"}}},layout:{nodes:{"base-color":{x:80,y:80},particle:{x:80,y:260},"particle-color":{x:340,y:160}},groups:[]}}}/*
1
+ export const ShaderGraphParameterTypes=["float","boolean","vec2","vec3","vec4","color","texture"];export function createDefaultShaderGraphDocument(e="surface"){return"postProcess"===e?createDefaultPostProcessShaderGraphDocument():{version:1,target:e,shadingModel:"standard",parameters:[{id:"base-color",name:"baseColor",type:"color",defaultValue:"#ffffff"}],nodes:[{id:"base-color",kind:"input.parameter",params:{parameter:"baseColor"}}],edges:[],outputs:{color:{input:{nodeId:"base-color",port:"value"}}},layout:{nodes:{"base-color":{x:80,y:80}}}}}export function createDefaultPostProcessShaderGraphDocument(){return{version:1,target:"postProcess",shadingModel:"unlit",parameters:[],nodes:[{id:"screen-uv",kind:"builtin.screenUv"},{id:"scene-color",kind:"scene.sampleColor"}],edges:[{id:"screen-uv-to-scene-color",from:{nodeId:"screen-uv",port:"value"},to:{nodeId:"scene-color",port:"uv"}}],outputs:{color:{input:{nodeId:"scene-color",port:"rgba"}}},layout:{nodes:{"screen-uv":{x:80,y:80},"scene-color":{x:340,y:80}},groups:[]}}}export function createDefaultVfxShaderGraphDocument(e="sprite"){return{version:1,target:e,shadingModel:"unlit",parameters:[{id:"base-color",name:"baseColor",type:"color",defaultValue:"#ffffff"}],nodes:[{id:"base-color",kind:"input.parameter",params:{parameter:"baseColor"}},{id:"particle",kind:"builtin.particle"},{id:"particle-color",kind:"math.multiply"}],edges:[{id:"base-color-to-particle-color",from:{nodeId:"base-color",port:"value"},to:{nodeId:"particle-color",port:"a"}},{id:"particle-color-to-particle-color",from:{nodeId:"particle",port:"color"},to:{nodeId:"particle-color",port:"b"}}],outputs:{color:{input:{nodeId:"particle-color",port:"value"}},opacity:{input:{nodeId:"particle",port:"opacity"}}},layout:{nodes:{"base-color":{x:80,y:80},particle:{x:80,y:260},"particle-color":{x:340,y:160}},groups:[]}}}/*
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{BooleanNode as e,FloatNode as r,RgbNode as a,Sampler2DNode as t,Vec2Node as n,Vec3Node as o,Vec4Node as u}from"../../shader-nodes/index.js";export function shaderGraphParameterTypeToValueType(e){switch(e){case"boolean":return"boolean";case"float":return"float";case"texture":return"sampler2d";case"vec2":return"vec2";case"vec3":return"vec3";case"vec4":return"vec4";case"color":return"rgb"}}export function shaderGraphParameterTypeToRuntimeType(c){switch(c){case"float":return r;case"boolean":return e;case"texture":return t;case"vec2":return n;case"vec3":return o;case"vec4":return u;case"color":return a}}export function shaderGraphParametersToPropertyParameters(e){return(e.parameters??[]).map(c)}export function getShaderGraphParameterDefaults(e){return Object.fromEntries((e.parameters??[]).filter(e=>void 0!==e.defaultValue).map(e=>[e.name,e.defaultValue]))}function c(e){return{name:e.name,type:shaderGraphParameterTypeToRuntimeType(e.type),options:{defaultValue:e.defaultValue,optional:!0}}}/*
1
+ import{BooleanNode as e,FloatNode as r,RgbNode as t,Sampler2DNode as a,Vec2Node as n,Vec3Node as o,Vec4Node as u}from"../../shader-nodes/index.js";export function shaderGraphParameterTypeToValueType(e){switch(e){case"boolean":return"boolean";case"float":return"float";case"texture":return"sampler2d";case"vec2":return"vec2";case"vec3":return"vec3";case"vec4":return"vec4";case"color":return"rgb"}}export function shaderGraphParameterTypeToRuntimeType(c){switch(c){case"float":return r;case"boolean":return e;case"texture":return a;case"vec2":return n;case"vec3":return o;case"vec4":return u;case"color":return t}}export function shaderGraphParametersToPropertyParameters(e){return(e.parameters??[]).map(c)}export function getShaderGraphParameterDefaults(e){return Object.fromEntries((e.parameters??[]).filter(e=>void 0!==e.defaultValue).map(e=>[e.name,e.defaultValue]))}function c(e){const r="string"==typeof e.group&&e.group.trim().length>0?e.group.trim():void 0;return{name:e.name,type:shaderGraphParameterTypeToRuntimeType(e.type),options:{defaultValue:e.defaultValue,...null!=r?{group:r}:{},optional:!0}}}/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -122,6 +122,6 @@ export declare function getDataAssetDefinitionId(type: ParameterType | undefined
122
122
  export declare function getParameterDefinitionInfo(type: ParameterType | undefined | null): ParameterDefinitionInfo | undefined;
123
123
  export declare function resolveParameterType(type: ParameterOptions['type']): ParameterType | undefined;
124
124
  export declare function getDataAssetDefinitionInfo(type: ParameterType | undefined | null): DataAssetDefinitionInfo | undefined;
125
- export declare function Parameter(options?: ParameterOptions): (target: Object, propertyName: string | symbol, index?: unknown) => void;
125
+ export declare function Parameter(options?: ParameterOptions): any;
126
126
  export declare function ParameterCategory(name: string): void;
127
127
  //# sourceMappingURL=parameter.d.ts.map
@@ -1,4 +1,4 @@
1
- import"reflect-metadata";import{reflect as e,decorateProperty as t}from"@plumier/reflect";import{ArrayMap as n}from"../utils/collections.js";Symbol("format");const r=Symbol("parameterDefinition"),a=Symbol("dataAssetDefinition");export function shaderParameterUniformName(e){return`pu_${e}`}class i{constructor(e){this.options=e,this.isParameterDecorator=!0}}new Map;const o=new n,s=new Map,f=new Map,l=new WeakMap,p=new Map,u=new Map,c=new WeakMap;export function ParameterDefinition(e,t={}){return function(n){registerParameterDefinition(e??n.name,n,t)}}export function DataAssetDefinition(e,t={}){return function(n){registerDataAssetDefinition(e??n.name,n,t)}}export function registerParameterDefinition(e,t,n={}){if(null==e||""===e.trim())throw new Error("Parameter definition id must be a non-empty string");const a=s.get(e);null!=a&&a!==t&&console.warn(`Replacing parameter definition "${e}"`,{existing:a,type:t}),s.set(e,t),f.set(e,{id:e,type:t,...!0===n.abstract?{abstract:!0}:{}}),l.set(t,e),Reflect.defineMetadata(r,f.get(e),t)}export function getParameterDefinitionType(e){return s.get(e)}export function registerDataAssetDefinition(e,t,n={}){if(null==e||""===e.trim())throw new Error("Data asset definition id must be a non-empty string");const r=p.get(e);null!=r&&r!==t&&console.warn(`Replacing data asset definition "${e}"`,{existing:r,type:t}),p.set(e,t),u.set(e,{id:e,type:t,...!0===n.abstract?{abstract:!0}:{}}),c.set(t,e),Reflect.defineMetadata(a,u.get(e),t)}export function getDataAssetDefinitionType(e){return p.get(e)}export function getAssignableDataAssetDefinitions(e,t={}){if(null==e)return[];const n=getDataAssetDefinitionId(e);return Array.from(p.entries()).filter(([,t])=>isParameterDefinitionAssignableTo(t,e)).filter(([e])=>!0===t.includeAbstract||!isDataAssetDefinitionAbstract(e)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.id===n?-1:t.id===n?1:e.label.localeCompare(t.label))}export function getDataAssetDefinitions(e={}){return Array.from(p.entries()).filter(([t])=>!0===e.includeAbstract||!isDataAssetDefinitionAbstract(t)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.label.localeCompare(t.label))}export function getAssignableParameterDefinitions(e,t={}){if(null==e)return[];const n=getParameterDefinitionId(e);return Array.from(s.entries()).filter(([,t])=>isParameterDefinitionAssignableTo(t,e)).filter(([e])=>!0===t.includeAbstract||!isParameterDefinitionAbstract(e)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.id===n?-1:t.id===n?1:e.label.localeCompare(t.label))}export function isParameterDefinitionAssignableTo(e,t){return null!=e&&null!=t&&(e===t||e.prototype instanceof t)}export function isParameterDefinitionStructAssignableTo(e,t){return null!=e&&null!=t&&(e===t||isParameterDefinitionAssignableTo(getParameterDefinitionType(e),getParameterDefinitionType(t)))}export function isParameterDefinitionAbstract(e){if(null==e)return!1;const t="string"==typeof e?f.get(e):getParameterDefinitionInfo(e);return!0===t?.abstract}export function isDataAssetDefinitionAbstract(e){if(null==e)return!1;const t="string"==typeof e?u.get(e):getDataAssetDefinitionInfo(e);return!0===t?.abstract}export function getParameterDefinitionDisplayName(e){return("string"==typeof e?e:e.name).replace(/Definition$/,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[_-]+/g," ").trim().split(/\s+/).filter(e=>e.length>0).map(e=>e.length<=2&&e===e.toUpperCase()?e:e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}export function getParameterDefinitionId(e){if(null!=e)return l.get(e)??Reflect.getMetadata(r,e)?.id}export function getDataAssetDefinitionId(e){if(null!=e)return c.get(e)??Reflect.getMetadata(a,e)?.id}export function getParameterDefinitionInfo(e){if(null==e)return;const t=getParameterDefinitionId(e);return null!=t?f.get(t)??Reflect.getMetadata(r,e)??{id:t,type:e}:void 0}export function resolveParameterType(e){if(null!=e)return"function"==typeof e&&null==e.prototype?e():e}function m(e,t){const{enum:n,...r}=e,a=n??t;if(null==a||r.options?.length>0)return r;const i=function(e){return Object.entries(e).filter(([e])=>!function(e){return""!==e.trim()&&String(Number(e))===e}(e)).map(([e,t])=>({name:getParameterDefinitionDisplayName(e),value:t}))}(a);return{...r,type:r.type??g(i),defaultValue:void 0!==r.defaultValue?r.defaultValue:i[0]?.value,options:i}}function g(e){const t=new Set(e.map(e=>typeof e.value));if(1===t.size)return t.has("number")?Number:t.has("string")?String:void 0}export function getDataAssetDefinitionInfo(e){if(null==e)return;const t=getDataAssetDefinitionId(e);return null!=t?u.get(t)??Reflect.getMetadata(a,e)??{id:t,type:e}:void 0}export function Parameter(e){return function(n,r,a){if(null!=n){o.push(n.constructor,{name:r,options:e??{}});try{t(new i(e))(n,r,a)}catch(e){console.warn("Failed to decorate method ",n,r,a)}}}}export function extractShaderParameters(e){if(null==e)return[];const t=o.get(e).map(({name:t,options:n})=>{const r=Reflect.getMetadata("parameter:enum",e.prototype,t),a=Reflect.getMetadata("parameter:array",e.prototype,t),i=Reflect.getMetadata("parameter:arrayElementType",e.prototype,t),o=Reflect.getMetadata("parameter:dataAssetOf",e.prototype,t),s=m(n,r),f=Reflect.getMetadata("design:type",e.prototype,t),l=!0===s.array||!0===a||f===Array,p=resolveParameterType(s.type)??(l?i:void 0)??f,u=Reflect.getMetadata("prefab:type",e.prototype,t);return s.prefabOf??(s.prefabOf=u),s.dataAssetOf??(s.dataAssetOf=o),s.array=l,{name:t,type:p,options:s,definition:getParameterDefinitionInfo(p)}});return[...extractShaderParameters(Object.getPrototypeOf(e)).filter(e=>t.every(t=>t.name!==e.name)),...t]}export function ParameterCategory(e){}/*
1
+ import"reflect-metadata";import{reflect as e,decorateProperty as t}from"@plumier/reflect";import{ArrayMap as n}from"../utils/collections.js";Symbol("format");const r=Symbol("parameterDefinition"),a=Symbol("dataAssetDefinition");export function shaderParameterUniformName(e){return`pu_${e}`}class i{constructor(e){this.options=e,this.isParameterDecorator=!0}}new Map;const o=new n,s=new Map,f=new Map,l=new WeakMap,p=new Map,u=new Map,c=new WeakMap;export function ParameterDefinition(e,t={}){return function(n){registerParameterDefinition(e??n.name,n,t)}}export function DataAssetDefinition(e,t={}){return function(n){registerDataAssetDefinition(e??n.name,n,t)}}export function registerParameterDefinition(e,t,n={}){if(null==e||""===e.trim())throw new Error("Parameter definition id must be a non-empty string");const a=s.get(e);null!=a&&a!==t&&console.warn(`Replacing parameter definition "${e}"`,{existing:a,type:t}),s.set(e,t),f.set(e,{id:e,type:t,...!0===n.abstract?{abstract:!0}:{}}),l.set(t,e),Reflect.defineMetadata(r,f.get(e),t)}export function getParameterDefinitionType(e){return s.get(e)}export function registerDataAssetDefinition(e,t,n={}){if(null==e||""===e.trim())throw new Error("Data asset definition id must be a non-empty string");const r=p.get(e);null!=r&&r!==t&&console.warn(`Replacing data asset definition "${e}"`,{existing:r,type:t}),p.set(e,t),u.set(e,{id:e,type:t,...!0===n.abstract?{abstract:!0}:{}}),c.set(t,e),Reflect.defineMetadata(a,u.get(e),t)}export function getDataAssetDefinitionType(e){return p.get(e)}export function getAssignableDataAssetDefinitions(e,t={}){if(null==e)return[];const n=getDataAssetDefinitionId(e);return Array.from(p.entries()).filter(([,t])=>isParameterDefinitionAssignableTo(t,e)).filter(([e])=>!0===t.includeAbstract||!isDataAssetDefinitionAbstract(e)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.id===n?-1:t.id===n?1:e.label.localeCompare(t.label))}export function getDataAssetDefinitions(e={}){return Array.from(p.entries()).filter(([t])=>!0===e.includeAbstract||!isDataAssetDefinitionAbstract(t)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.label.localeCompare(t.label))}export function getAssignableParameterDefinitions(e,t={}){if(null==e)return[];const n=getParameterDefinitionId(e);return Array.from(s.entries()).filter(([,t])=>isParameterDefinitionAssignableTo(t,e)).filter(([e])=>!0===t.includeAbstract||!isParameterDefinitionAbstract(e)).map(([e,t])=>({id:e,type:t,label:getParameterDefinitionDisplayName(t)})).sort((e,t)=>e.id===n?-1:t.id===n?1:e.label.localeCompare(t.label))}export function isParameterDefinitionAssignableTo(e,t){return null!=e&&null!=t&&(e===t||e.prototype instanceof t)}export function isParameterDefinitionStructAssignableTo(e,t){return null!=e&&null!=t&&(e===t||isParameterDefinitionAssignableTo(getParameterDefinitionType(e),getParameterDefinitionType(t)))}export function isParameterDefinitionAbstract(e){if(null==e)return!1;const t="string"==typeof e?f.get(e):getParameterDefinitionInfo(e);return!0===t?.abstract}export function isDataAssetDefinitionAbstract(e){if(null==e)return!1;const t="string"==typeof e?u.get(e):getDataAssetDefinitionInfo(e);return!0===t?.abstract}export function getParameterDefinitionDisplayName(e){return("string"==typeof e?e:e.name).replace(/Definition$/,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[_-]+/g," ").trim().split(/\s+/).filter(e=>e.length>0).map(e=>e.length<=2&&e===e.toUpperCase()?e:e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}export function getParameterDefinitionId(e){if(null!=e)return l.get(e)??Reflect.getMetadata(r,e)?.id}export function getDataAssetDefinitionId(e){if(null!=e)return c.get(e)??Reflect.getMetadata(a,e)?.id}export function getParameterDefinitionInfo(e){if(null==e)return;const t=getParameterDefinitionId(e);return null!=t?f.get(t)??Reflect.getMetadata(r,e)??{id:t,type:e}:void 0}export function resolveParameterType(e){if(null!=e)return"function"==typeof e&&null==e.prototype?e():e}function m(e,t){const{enum:n,...r}=e,a=n??t;if(null==a||r.options?.length>0)return r;const i=function(e){return Object.entries(e).filter(([e])=>!function(e){return""!==e.trim()&&String(Number(e))===e}(e)).map(([e,t])=>({name:getParameterDefinitionDisplayName(e),value:t}))}(a);return{...r,type:r.type??g(i),defaultValue:void 0!==r.defaultValue?r.defaultValue:i[0]?.value,options:i}}function g(e){const t=new Set(e.map(e=>typeof e.value));if(1===t.size)return t.has("number")?Number:t.has("string")?String:void 0}export function getDataAssetDefinitionInfo(e){if(null==e)return;const t=getDataAssetDefinitionId(e);return null!=t?u.get(t)??Reflect.getMetadata(a,e)??{id:t,type:e}:void 0}export function Parameter(e){return function(n,r,a){if(a&&"object"==typeof a&&"field"===a.kind)return o.push(n,{name:r,options:e??{}}),a.addInitializer&&a.addInitializer(function(){}),Reflect.defineMetadata("isParameterDecorator",!0,n,r),void Reflect.defineMetadata("parameterOptions",e,n,r);if(null!=n){o.push(n.constructor,{name:r,options:e??{}});try{t(new i(e))(n,r,a)}catch(e){console.warn("Failed to decorate method ",n,r,a)}}}}export function extractShaderParameters(e){if(null==e)return[];const t=o.get(e).map(({name:t,options:n})=>{const r=Reflect.getMetadata("parameter:enum",e.prototype,t),a=Reflect.getMetadata("parameter:array",e.prototype,t),i=Reflect.getMetadata("parameter:arrayElementType",e.prototype,t),o=Reflect.getMetadata("parameter:dataAssetOf",e.prototype,t),s=m(n,r),f=Reflect.getMetadata("design:type",e.prototype,t),l=!0===s.array||!0===a||f===Array,p=resolveParameterType(s.type)??(l?i:void 0)??f,u=Reflect.getMetadata("prefab:type",e.prototype,t);return s.prefabOf??(s.prefabOf=u),s.dataAssetOf??(s.dataAssetOf=o),s.array=l,{name:t,type:p,options:s,definition:getParameterDefinitionInfo(p)}});return[...extractShaderParameters(Object.getPrototypeOf(e)).filter(e=>t.every(t=>t.name!==e.name)),...t]}export function ParameterCategory(e){}/*
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{__decorate as e,__metadata as i}from"tslib";import*as t from"three";import{batchingMatrix as r,ConstantMat4Node as o,float as a,glslFunction as n,ifDefApply as s,rgba as l,step as c,textureSampler2d as p,UniformFloatNode as d,uniforms as u,varying as h,varyingAttributes as S,vec3 as m,vec4 as x,Vec4Node as y}from"three-shader-graph";import{depthSampler as v,NodeShaderMaterial as f}from"../shader-nodes/index.js";import{Parameter as M}from"./shader.js";export const identityTransform=new o((new t.Matrix4).identity());const g=s("USE_BATCHING",identityTransform,e=>r);export class SpriteNodeShader{build(){const e=this.output(),i=getSpritePosition(new d("rotation",0),new d("screenSpaceSize",0));return new SpriteNodeShaderMaterial({color:e.color,discard:e.discard,transparent:e.transparent??!0,position:i,uniforms:{color:{value:new t.Color(0)}}})}}export class SpriteShader extends SpriteNodeShader{constructor(){super(...arguments),this.color=new t.Color("#FFFFFF"),this.opacity=1,this.intensity=1,this.lockY=!1,this.screenSpaceSize=0,this.lenseFlare=!1,this.occlusionRadius=.2}output(){let e=a(this.opacity);this.alphaMap&&(e=e.multiply(p(this.alphaMap).sample(S.uv).r));let i=l(this.color,e);if(this.map&&(i=i.multiply(p(this.map).sample(S.uv))),null!=this.intensity&&1!==this.intensity&&(i=l(i.rgb.multiplyScalar(this.intensity),i.a)),this.lenseFlare){const e=a(this.occlusionRadius),t=w(P(m(0,0,0))),r=w(P(m(e.multiply(-1),0,0))),o=w(P(m(e,0,0))),n=w(P(m(0,e,0))),s=w(P(m(0,e.multiply(-1),0))),c=t.add(r).add(o).add(n).add(s).divide(5),p=h(c);return{color:l(i.rgb,i.a.multiply(p))}}return{color:i}}build(){const e=super.build();return this.lenseFlare&&(e.depthTest=!1,e.depthWrite=!1),this.lockY&&(e.defines.LOCK_Y_AXIS=""),null!=this.screenSpaceSize&&this.screenSpaceSize>0&&(e.defines.SCREEN_SPACE_SIZE=""),e.uniforms.screenSpaceSize.value=this.screenSpaceSize,e}}function P(e){let i=u.instanceMatrix.multiplyVec(x(e,1));return i=s("USE_BATCHING",i,e=>r.multiplyVec(e)),u.modelMatrix.multiplyVec(i)}function w(e){const i=u.projectionMatrix.multiplyVec(u.viewMatrix.multiplyVec(e)),t=i.xyz.divideScalar(i.w),r=t.xy.multiplyScalar(.5).addScalar(.5),o=v.sample(r).x,n=t.z.multiply(.5).add(.5),s=c(n,o.add(a(1e-4))),l=c(a(0),i.w);return s.multiply(l)}e([M(),i("design:type",t.Color)],SpriteShader.prototype,"color",void 0),e([M({range:[0,1]}),i("design:type",Number)],SpriteShader.prototype,"opacity",void 0),e([M({range:[0,10]}),i("design:type",Number)],SpriteShader.prototype,"intensity",void 0),e([M(),i("design:type",t.Texture)],SpriteShader.prototype,"map",void 0),e([M(),i("design:type",t.Texture)],SpriteShader.prototype,"alphaMap",void 0),e([M({help:"Sprite will rotate around Y axis to face camera"}),i("design:type",Boolean)],SpriteShader.prototype,"lockY",void 0),e([M({range:[0,1],help:"0 = Perspective (gets smaller with distance), 1 = Screen Space (constant size)"}),i("design:type",Number)],SpriteShader.prototype,"screenSpaceSize",void 0),e([M({help:"Make the sprite behave as a lense flare that appear in front of geometry if center is visible.",label:"Lens Flare"}),i("design:type",Boolean)],SpriteShader.prototype,"lenseFlare",void 0),e([M({range:[0,2],help:"The radius of the occlusion check. Larger radius will make the sprite fade out more gradually.",requires:{lenseFlare:!0}}),i("design:type",Number)],SpriteShader.prototype,"occlusionRadius",void 0);export function getSpritePosition(e,i=a(0)){return n(y,{cameraPosition:u.cameraPosition,viewMatrix:u.viewMatrix,modelViewMatrix:u.modelViewMatrix,rotation:e,screenSpaceSize:i,batchingMatrix:g,instanceMatrix:u.instanceMatrix},"\n mat4 finalModelMatrix = modelMatrix * batchingMatrix * instanceMatrix;\n\n // Scale from your model matrix\n vec2 scale;\n scale.x = length(vec3(finalModelMatrix[0].x, finalModelMatrix[0].y, finalModelMatrix[0].z));\n scale.y = length(vec3(finalModelMatrix[1].x, finalModelMatrix[1].y, finalModelMatrix[1].z));\n\n vec3 right, up;\n vec3 objectPos = (finalModelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;\n\n #ifdef SCREEN_SPACE_SIZE\n float distanceToCamera = length(objectPos - cameraPosition);\n float screenScaleFactor = mix(1.0, distanceToCamera, screenSpaceSize);\n scale *= screenScaleFactor;\n #endif\n\n\n #ifdef LOCK_Y_AXIS\n vec3 cameraPos = cameraPosition;\n vec3 lookDir = normalize(vec3(cameraPos.x - objectPos.x, 0.0, cameraPos.z - objectPos.z));\n right = normalize(vec3(lookDir.z, 0.0, -lookDir.x));\n up = vec3(0.0, 1.0, 0.0);\n #else\n right = vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);\n up = vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]);\n #endif\n\n // then your shared code:\n vec2 center = vec2(0.5, 0.5);\n vec2 alignedPosition = (position.xy - (center - vec2(0.5))) * scale;\n\n vec2 rotatedPosition;\n rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;\n rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;\n\n vec3 billboardPos = objectPos + right * rotatedPosition.x + up * rotatedPosition.y;\n\n // This can be used just to test outputting without sprite functionality\n // return projectionMatrix * viewMatrix * finalModelMatrix * vec4(position, 1.0);\n return projectionMatrix * viewMatrix * vec4(billboardPos, 1.0);\n ")}export class SpriteNodeShaderMaterial extends f{get color(){return this.uniforms.color.value}set color(e){this.uniforms.color.value=new t.Color(e)}get rotation(){return this.uniforms.rotation.value}set rotation(e){this.uniforms.rotation.value=e}get screenSpaceSize(){return this.uniforms.screenSpaceSize.value}set screenSpaceSize(e){this.uniforms.screenSpaceSize.value=e}get occlusionRadius(){return this.uniforms.occlusionRadius.value}set occlusionRadius(e){this.uniforms.occlusionRadius.value=e}}/*
1
+ import{__decorate as e,__metadata as t}from"tslib";import*as i from"three";import{batchingMatrix as r,ConstantMat4Node as o,float as a,glslFunction as n,ifDefApply as s,rgba as l,step as c,textureSampler2d as p,UniformFloatNode as d,uniforms as u,varying as h,varyingAttributes as S,vec3 as m,vec4 as x,Vec4Node as y}from"three-shader-graph";import{depthSampler as v,NodeShaderMaterial as f}from"../shader-nodes/index.js";import{Parameter as M}from"./shader.js";export const identityTransform=new o((new i.Matrix4).identity());const g=s("USE_BATCHING",identityTransform,e=>r);export class SpriteNodeShader{build(){const e=this.output(),t=getSpritePosition(new d("rotation",0),new d("screenSpaceSize",0));return new SpriteNodeShaderMaterial({color:e.color,discard:e.discard,alphaTest:e.alphaTest,transparent:e.transparent??!0,position:t,uniforms:{color:{value:new i.Color(0)}}})}}export class SpriteShader extends SpriteNodeShader{constructor(){super(...arguments),this.color=new i.Color("#FFFFFF"),this.opacity=1,this.intensity=1,this.lockY=!1,this.screenSpaceSize=0,this.lenseFlare=!1,this.occlusionRadius=.2}output(){let e=a(this.opacity);this.alphaMap&&(e=e.multiply(p(this.alphaMap).sample(S.uv).r));let t=l(this.color,e);if(this.map&&(t=t.multiply(p(this.map).sample(S.uv))),null!=this.intensity&&1!==this.intensity&&(t=l(t.rgb.multiplyScalar(this.intensity),t.a)),this.lenseFlare){const e=a(this.occlusionRadius),i=w(P(m(0,0,0))),r=w(P(m(e.multiply(-1),0,0))),o=w(P(m(e,0,0))),n=w(P(m(0,e,0))),s=w(P(m(0,e.multiply(-1),0))),c=i.add(r).add(o).add(n).add(s).divide(5),p=h(c);return{color:l(t.rgb,t.a.multiply(p))}}return{color:t}}build(){const e=super.build();return this.lenseFlare&&(e.depthTest=!1,e.depthWrite=!1),this.lockY&&(e.defines.LOCK_Y_AXIS=""),null!=this.screenSpaceSize&&this.screenSpaceSize>0&&(e.defines.SCREEN_SPACE_SIZE=""),e.uniforms.screenSpaceSize.value=this.screenSpaceSize,e}}function P(e){let t=u.instanceMatrix.multiplyVec(x(e,1));return t=s("USE_BATCHING",t,e=>r.multiplyVec(e)),u.modelMatrix.multiplyVec(t)}function w(e){const t=u.projectionMatrix.multiplyVec(u.viewMatrix.multiplyVec(e)),i=t.xyz.divideScalar(t.w),r=i.xy.multiplyScalar(.5).addScalar(.5),o=v.sample(r).x,n=i.z.multiply(.5).add(.5),s=c(n,o.add(a(1e-4))),l=c(a(0),t.w);return s.multiply(l)}e([M(),t("design:type",i.Color)],SpriteShader.prototype,"color",void 0),e([M({range:[0,1]}),t("design:type",Number)],SpriteShader.prototype,"opacity",void 0),e([M({range:[0,10]}),t("design:type",Number)],SpriteShader.prototype,"intensity",void 0),e([M(),t("design:type",i.Texture)],SpriteShader.prototype,"map",void 0),e([M(),t("design:type",i.Texture)],SpriteShader.prototype,"alphaMap",void 0),e([M({help:"Sprite will rotate around Y axis to face camera"}),t("design:type",Boolean)],SpriteShader.prototype,"lockY",void 0),e([M({range:[0,1],help:"0 = Perspective (gets smaller with distance), 1 = Screen Space (constant size)"}),t("design:type",Number)],SpriteShader.prototype,"screenSpaceSize",void 0),e([M({help:"Make the sprite behave as a lense flare that appear in front of geometry if center is visible.",label:"Lens Flare"}),t("design:type",Boolean)],SpriteShader.prototype,"lenseFlare",void 0),e([M({range:[0,2],help:"The radius of the occlusion check. Larger radius will make the sprite fade out more gradually.",requires:{lenseFlare:!0}}),t("design:type",Number)],SpriteShader.prototype,"occlusionRadius",void 0);export function getSpritePosition(e,t=a(0)){return n(y,{cameraPosition:u.cameraPosition,viewMatrix:u.viewMatrix,modelViewMatrix:u.modelViewMatrix,rotation:e,screenSpaceSize:t,batchingMatrix:g,instanceMatrix:u.instanceMatrix},"\n mat4 finalModelMatrix = modelMatrix * batchingMatrix * instanceMatrix;\n\n // Scale from your model matrix\n vec2 scale;\n scale.x = length(vec3(finalModelMatrix[0].x, finalModelMatrix[0].y, finalModelMatrix[0].z));\n scale.y = length(vec3(finalModelMatrix[1].x, finalModelMatrix[1].y, finalModelMatrix[1].z));\n\n vec3 right, up;\n vec3 objectPos = (finalModelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;\n\n #ifdef SCREEN_SPACE_SIZE\n float distanceToCamera = length(objectPos - cameraPosition);\n float screenScaleFactor = mix(1.0, distanceToCamera, screenSpaceSize);\n scale *= screenScaleFactor;\n #endif\n\n\n #ifdef LOCK_Y_AXIS\n vec3 cameraPos = cameraPosition;\n vec3 lookDir = normalize(vec3(cameraPos.x - objectPos.x, 0.0, cameraPos.z - objectPos.z));\n right = normalize(vec3(lookDir.z, 0.0, -lookDir.x));\n up = vec3(0.0, 1.0, 0.0);\n #else\n right = vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);\n up = vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]);\n #endif\n\n // then your shared code:\n vec2 center = vec2(0.5, 0.5);\n vec2 alignedPosition = (position.xy - (center - vec2(0.5))) * scale;\n\n vec2 rotatedPosition;\n rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;\n rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;\n\n vec3 billboardPos = objectPos + right * rotatedPosition.x + up * rotatedPosition.y;\n\n // This can be used just to test outputting without sprite functionality\n // return projectionMatrix * viewMatrix * finalModelMatrix * vec4(position, 1.0);\n return projectionMatrix * viewMatrix * vec4(billboardPos, 1.0);\n ")}export class SpriteNodeShaderMaterial extends f{get color(){return this.uniforms.color.value}set color(e){this.uniforms.color.value=new i.Color(e)}get rotation(){return this.uniforms.rotation.value}set rotation(e){this.uniforms.rotation.value=e}get screenSpaceSize(){return this.uniforms.screenSpaceSize.value}set screenSpaceSize(e){this.uniforms.screenSpaceSize.value=e}get occlusionRadius(){return this.uniforms.occlusionRadius.value}set occlusionRadius(e){this.uniforms.occlusionRadius.value=e}}/*
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*as e from"three";import{Vector2 as t}from"three";import{cross as o,dot as r,float as n,FloatExpressionNode as c,FloatNode as s,inverse as a,normalize as i,texture2d as p,transformed as l,uniformFloat as m,uniforms as h,uniformSampler2d as u,uniformVec2 as d,unpackRGBToNormal as x,varying as f,vec2 as y,Vec2ExpressionNode as _,vec4 as U,Vec4Node as g}from"three-shader-graph";export function supportsDepthTextureExtension(e){return!0}export const depthUniformName="hology_depth_map";export const resolutionUniformName="hology_resolution";export const nearUniformName="hology_camera_near";export const farUniformName="hology_camera_far";export const sceneNormalUniformName="hology_scene_normal_map";export const depthSampler=u(depthUniformName,new e.DepthTexture(1,1));const k=m(nearUniformName,.5),$=m(farUniformName,500);class N extends s{compile(e){const t=e.variable();var o=e.get(k),r=e.get($);return{chunk:`\n float depth_${t} = 2.0 * ${o} * ${r} / (${r} + ${o} - (2.0 * ${e.get(this.depth)} - 1.0) * (${r} - ${o}));\n `,out:`depth_${t}`}}constructor(e){super(),this.depth=e}}function v(e){return new N(e)}new class extends g{constructor(){super(...arguments),this.k="31u50"}compile(e){return{pars:`\n const float UnpackDownscale_${this.k} = 255. / 256.; // 0..1 -> fraction (excluding 1)\n const vec4 PackFactors_${this.k} = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\n const vec4 UnpackFactors_${this.k} = vec4( UnpackDownscale_${this.k} / PackFactors_${this.k}.rgb, 1.0 / PackFactors_${this.k}.a );\n `,out:`UnpackFactors_${this.k}`}}};function w(e){return p(depthSampler,e).r}export function sampleSceneDepth(e){return w(e)}export function sampleSceneLinearEyeDepth(e){return v(w(e))}const V=f(h.projectionMatrix.multiplyVec(l.mvPosition).zw);export const highPrecisionEyeDepth=V.x.multiply(.5).divide(V.y).add(.5);const D=new c("gl_FragCoord.z");export const fragmentLinearEyeDepth=v(D);export const resolution=d("hology_resolution",new t(250,1e3));export const screenUV=new _("gl_FragCoord.xy").divide(resolution);export const linearEyeDepth=v(w(screenUV));const S=f(a(h.projectionMatrix)),P=f(a(h.viewMatrix));function b(e=screenUV){const t=w(e).multiply(2).subtract(1),o=e.multiplyScalar(2).subtractScalar(1),r=U(o.x,o.y,t,1),n=S.multiplyVec(r),c=n.xyz.divideScalar(n.w);return P.multiplyVec(U(c,1)).xyz}export const depthWorldPosition=b(screenUV);const F=n(1).divide(resolution.x),E=n(1).divide(resolution.y),z=b(screenUV.add(y(F,n(0)))),M=b(screenUV.add(y(n(0),E)));export const depthNormal=i(o(z.subtract(depthWorldPosition),M.subtract(depthWorldPosition)));export const sceneNormalSampler=u(sceneNormalUniformName,new e.Texture);export const sceneNormal=x(sceneNormalSampler.sample(screenUV).rgb);/*
1
+ import*as e from"three";import{Vector2 as t}from"three";import{cross as o,dot as r,float as n,FloatExpressionNode as s,FloatNode as c,inverse as a,normalize as i,texture2d as p,transformed as l,uniformFloat as m,uniforms as h,uniformSampler2d as u,uniformVec2 as d,unpackRGBToNormal as x,varying as f,vec2 as y,Vec2ExpressionNode as _,vec4 as U,Vec4Node as g}from"three-shader-graph";import{createPlaceholderTexture as k}from"../utils/three/placeholder-texture.js";export function supportsDepthTextureExtension(e){return!0}export const depthUniformName="hology_depth_map";export const resolutionUniformName="hology_resolution";export const nearUniformName="hology_camera_near";export const farUniformName="hology_camera_far";export const sceneNormalUniformName="hology_scene_normal_map";export const depthSampler=u(depthUniformName,new e.DepthTexture(1,1));const $=m(nearUniformName,.5),N=m(farUniformName,500);class v extends c{compile(e){const t=e.variable();var o=e.get($),r=e.get(N);return{chunk:`\n float depth_${t} = 2.0 * ${o} * ${r} / (${r} + ${o} - (2.0 * ${e.get(this.depth)} - 1.0) * (${r} - ${o}));\n `,out:`depth_${t}`}}constructor(e){super(),this.depth=e}}function w(e){return new v(e)}new class extends g{constructor(){super(...arguments),this.k="31u50"}compile(e){return{pars:`\n const float UnpackDownscale_${this.k} = 255. / 256.; // 0..1 -> fraction (excluding 1)\n const vec4 PackFactors_${this.k} = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\n const vec4 UnpackFactors_${this.k} = vec4( UnpackDownscale_${this.k} / PackFactors_${this.k}.rgb, 1.0 / PackFactors_${this.k}.a );\n `,out:`UnpackFactors_${this.k}`}}};function V(e){return p(depthSampler,e).r}export function sampleSceneDepth(e){return V(e)}export function sampleSceneLinearEyeDepth(e){return w(V(e))}const D=f(h.projectionMatrix.multiplyVec(l.mvPosition).zw);export const highPrecisionEyeDepth=D.x.multiply(.5).divide(D.y).add(.5);const S=new s("gl_FragCoord.z");export const fragmentLinearEyeDepth=w(S);export const resolution=d("hology_resolution",new t(250,1e3));export const screenUV=new _("gl_FragCoord.xy").divide(resolution);export const linearEyeDepth=w(V(screenUV));const P=f(a(h.projectionMatrix)),b=f(a(h.viewMatrix));function F(e=screenUV){const t=V(e).multiply(2).subtract(1),o=e.multiplyScalar(2).subtractScalar(1),r=U(o.x,o.y,t,1),n=P.multiplyVec(r),s=n.xyz.divideScalar(n.w);return b.multiplyVec(U(s,1)).xyz}export const depthWorldPosition=F(screenUV);const E=n(1).divide(resolution.x),z=n(1).divide(resolution.y),j=F(screenUV.add(y(E,n(0)))),M=F(screenUV.add(y(n(0),z)));export const depthNormal=i(o(j.subtract(depthWorldPosition),M.subtract(depthWorldPosition)));export const sceneNormalSampler=u(sceneNormalUniformName,k(128,128,255));export const sceneNormal=x(sceneNormalSampler.sample(screenUV).rgb);/*
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{float as o,ifDefApply as e,uniformSampler2d as r,Vec3ExpressionNode as a}from"three-shader-graph";import*as p from"three";import{screenUV as t}from"./depth.js";export const sceneMapUniformName="hology_scene_map";export const sceneColorSampler=r("hology_scene_map",new p.Texture);export function sampleSceneColor(o){return sceneColorSampler.sample(o)}export const fragCoord=new a("gl_FragCoord");export const aoMapUniformName="hology_ao_map";export const aoColorSampler=r("hology_ao_map",new p.Texture);export function sampleScreenAO(r=t){return e("USE_SSAO_MAP",o(1),()=>aoColorSampler.sample(r).r)}/*
1
+ import{float as o,ifDefApply as e,uniformSampler2d as r,Vec3ExpressionNode as p}from"three-shader-graph";import{screenUV as a}from"./depth.js";import{createPlaceholderTexture as t}from"../utils/three/placeholder-texture.js";export const sceneMapUniformName="hology_scene_map";export const sceneColorSampler=r("hology_scene_map",t());export function sampleSceneColor(o){return sceneColorSampler.sample(o)}export const fragCoord=new p("gl_FragCoord");export const aoMapUniformName="hology_ao_map";export const aoColorSampler=r("hology_ao_map",t());export function sampleScreenAO(r=a){return e("USE_SSAO_MAP",o(1),()=>aoColorSampler.sample(r).r)}/*
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=batched-mesh-2.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as t,expect as e,it as s}from"vitest";import{BoxGeometry as o,MeshBasicMaterial as a,PerspectiveCamera as n,Scene as d}from"three";import{BatchedMesh2 as r}from"../scene/batched-mesh-2.js";function i(){const t=new o,e=t.getAttribute("position").count,s=t.index?.count??e,n=new r(3,e,s,new a),d=n.addGeometry(t);return n.perObjectFrustumCulled=!1,n.sortObjects=!1,{mesh:n,instanceIds:[n.addInstance(d),n.addInstance(d),n.addInstance(d)]}}function c(t){const e=t._multiDrawCount,s=t._indirectTexture.image.data;return Array.from(s.slice(0,e))}t("BatchedMesh2",()=>{s("filters non-casting instances only while building the shadow draw list",()=>{const{mesh:t,instanceIds:s}=i(),o=new d,a=new n,r=t.material;t.setCastShadowAt(s[0],!1),t.setCastShadowAt(s[1],!0),t.setCastShadowAt(s[2],!1),t.receiveShadow=!1,t.onBeforeShadow({},o,a,a,t.geometry,r,null),e(t.receiveShadow).toBe(!1),e(t._multiDrawCount).toBe(1),e(c(t)).toEqual([s[1]]),e(s.map(e=>t.getVisibleAt(e))).toEqual([!0,!0,!0]),t.onBeforeRender({},o,a,t.geometry,r,null),e(t._multiDrawCount).toBe(3),e(c(t)).toEqual(s)}),s("keeps the object out of shadow passes when no instances cast shadows",()=>{const{mesh:t,instanceIds:s}=i();t.setCastShadowAt(s[0],!0),e(t.castShadow).toBe(!0),t.setCastShadowAt(s[0],!1),e(t.castShadow).toBe(!1),t.setCastShadowAt(s[1],!0),t.deleteInstance(s[1]),e(t.castShadow).toBe(!1)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-net-session.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{afterEach as e,beforeEach as s,expect as t,test as a,vi as r}from"vitest";import{BrowserNetSession as n}from"../gameplay/net/browser/index.js";import{NetMode as o}from"../gameplay/net/net-session.js";class l{constructor(e){this.name=e,this.listeners=new Set,this.closed=!1;const s=l.channels.get(e)??new Set;s.add(this),l.channels.set(e,s)}postMessage(e){const s=l.channels.get(this.name);if(null!=s)for(const t of s)t===this||t.closed||t.dispatch({data:e})}addEventListener(e,s){"message"===e&&this.listeners.add(s)}removeEventListener(e,s){"message"===e&&this.listeners.delete(s)}close(){this.closed=!0,l.channels.get(this.name)?.delete(this)}dispatch(e){for(const s of this.listeners)"function"==typeof s?s.call(this,e):s.handleEvent(e)}static reset(){l.channels.clear()}}l.channels=new Map;let i=[];function c(e){return i.push(e),e}function d(e){return Array.from(new Uint8Array(e))}s(()=>{l.reset(),r.stubGlobal("BroadcastChannel",l),Object.defineProperty(window,"BroadcastChannel",{configurable:!0,writable:!0,value:l}),localStorage.clear()}),e(()=>{i.forEach(e=>e.disconnect()),i=[],localStorage.clear(),r.useRealTimers(),r.unstubAllGlobals()}),a("sends raw buffers over BroadcastChannel without using localStorage as a message queue",()=>{const e=c(new n(o.listenServer,"room",1,{heartbeatIntervalMs:0})),s=c(new n(o.client,"room",2,{heartbeatIntervalMs:0})),a=[];e.playerJoined.subscribe(e=>a.push(Number(e.id))),e.connect(),s.connect(),t(a).toEqual([2]),t(e.clients.map(e=>e.id)).toEqual([2]),t(s.server?.id).toBe(1),s.sendMessage(s.server,!0,Uint8Array.of(7,8,9).buffer),t(e.hasMessage()).toBe(1);const r=e.readMessage();t(r?.from.id).toBe(2),t(d(r.buffer)).toEqual([7,8,9]),t(e.hasMessage()).toBe(0);const l=JSON.parse(localStorage.getItem("net_session_room"));t(l.members.map(e=>e.connectionId)).toEqual([1,2]),t(l.messages).toBeUndefined()}),a("emits playerLeft when a member disconnects",()=>{const e=c(new n(o.listenServer,"leave-room",1,{heartbeatIntervalMs:0})),s=c(new n(o.client,"leave-room",2,{heartbeatIntervalMs:0})),a=[];e.playerLeft.subscribe(e=>a.push(Number(e.id))),e.connect(),s.connect(),s.disconnect(),t(a).toEqual([2]),t(e.clients).toEqual([])}),a("applies latency and packet loss to unreliable messages",()=>{r.useFakeTimers();const e=c(new n(o.listenServer,"sim-room",1,{heartbeatIntervalMs:0})),s=c(new n(o.client,"sim-room",2,{heartbeatIntervalMs:0,latencyMs:25,packetLoss:1}));e.connect(),s.connect(),s.sendMessage(s.server,!1,Uint8Array.of(1).buffer),r.advanceTimersByTime(100),t(e.hasMessage()).toBe(0),s.configureNetworkSimulation({packetLoss:0}),s.sendMessage(s.server,!1,Uint8Array.of(2).buffer),r.advanceTimersByTime(24),t(e.hasMessage()).toBe(0),r.advanceTimersByTime(1),t(e.hasMessage()).toBe(1),t(d(e.readMessage().buffer)).toEqual([2])}),a("keeps reliable messages ordered when jitter would otherwise reorder them",()=>{r.useFakeTimers();r.spyOn(Math,"random").mockReturnValueOnce(1).mockReturnValueOnce(0);const e=c(new n(o.listenServer,"ordered-room",1,{heartbeatIntervalMs:0})),s=c(new n(o.client,"ordered-room",2,{heartbeatIntervalMs:0,jitterMs:10}));e.connect(),s.connect(),s.sendMessage(s.server,!0,Uint8Array.of(1).buffer),s.sendMessage(s.server,!0,Uint8Array.of(2).buffer),r.advanceTimersByTime(10),t(e.hasMessage()).toBe(2),t(d(e.readMessage().buffer)).toEqual([1]),t(d(e.readMessage().buffer)).toEqual([2])});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,4 +1,4 @@
1
- import"reflect-metadata";import{afterEach as e,expect as t,test as o,vi as n}from"vitest";n.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&n.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{},RayTestResult:class{}})),n.mock("../gameplay/services/world.js",()=>({World:class{}})),n.mock("@hology/nebula",()=>({ease:{easeInOutCubic:e=>e}}));import{Subject as a}from"rxjs";import{Container as i}from"typedi";import{Object3D as r,Ray as s,Scene as c,Vector3 as m}from"three";import{BaseActor as p}from"../gameplay/actors/actor.js";import{FirstPersonCameraComponent as l}from"../gameplay/actors/camera/first-person-camera-component.js";import{ThirdPersonCameraComponent as d}from"../gameplay/actors/camera/third-person-camera-component.js";import{PhysicsSystem as y}from"../gameplay/services/physics/physics-system.js";import{ViewController as u}from"../gameplay/services/render.js";import{World as v}from"../gameplay/services/world.js";function f(e=new B){const t=h(),o=new l;return o.actor=e,{camera:o,actor:e,element:t}}function h(){const e=document.createElement("div"),t=document.createElement("canvas");return e.appendChild(t),Object.defineProperty(e,"clientWidth",{configurable:!0,value:1280}),Object.defineProperty(e,"clientHeight",{configurable:!0,value:720}),i.set(u,{htmlElement:e,setCamera:n.fn(),onUpdate:()=>new a,onLateUpdate:()=>new a}),i.set(v,{scene:new c}),i.set(y,{sphereCast:n.fn()}),e}function w(){Object.defineProperty(document.body,"requestPointerLock",{configurable:!0,value:n.fn()}),Object.defineProperty(document,"pointerLockElement",{configurable:!0,value:null})}function g(e,t){return e.mock.calls.filter(e=>e[0]===t).length}e(()=>{n.restoreAllMocks(),i.reset()}),o("first-person camera clamps pitch input",()=>{const{camera:e}=f();e.rotationInput.rotateX(Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(Math.PI/2-.01),e.rotationInput.rotateX(2*-Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(-Math.PI/2+.01)}),o("first-person camera follows actor eye offset",async()=>{const e=new B;e.position.set(1,2,3),e.rotation.y=Math.PI/2,e.object.updateMatrixWorld(!0);const{camera:o}=f(e);o.eyeHeight=1.5,o.offsetZ=.25,o.autoActivate=!1,await o.onInit(),o.updateCameraTransform(),t(o.camera.position.x).toBeCloseTo(1.25),t(o.camera.position.y).toBeCloseTo(3.5),t(o.camera.position.z).toBeCloseTo(3)}),o("first-person aim helpers write to caller-provided objects",async()=>{const e=new B;e.position.set(0,1,0);const{camera:o}=f(e);o.autoActivate=!1,await o.onInit();const n=new m,a=new m,i=new s;t(o.getAimOrigin(n)).toBe(n),t(o.getAimDirection(a)).toBe(a),t(o.getAimRay(i)).toBe(i),t(i.origin).toEqual(n),t(i.direction).toEqual(a),t(a.x).toBeCloseTo(0),t(a.y).toBeCloseTo(0),t(a.z).toBeCloseTo(1),t(a.length()).toBeCloseTo(1)}),o("first-person camera pitch input tilts aim downward when positive",async()=>{const{camera:e}=f();e.autoActivate=!1,await e.onInit(),e.rotationInput.rotateX(.25),e.updateCameraTransform();const o=e.getAimDirection(new m);t(o.y).toBeLessThan(0),t(o.z).toBeGreaterThan(0),t(o.length()).toBeCloseTo(1)}),o("first-person hidden objects restore their previous visibility",()=>{const{camera:e}=f(),o=new r,n=new r;n.visible=!1,e.hideObjects(o,n),t(o.visible).toBe(!1),t(n.visible).toBe(!1),e.restoreHiddenObjects(),t(o.visible).toBe(!0),t(n.visible).toBe(!1)}),o("first-person activation is idempotent",async()=>{const{camera:e,element:o}=f();e.autoActivate=!1,await e.onInit(),w();const a=n.spyOn(o,"addEventListener"),i=n.spyOn(o,"removeEventListener"),r=n.spyOn(document,"addEventListener"),s=n.spyOn(document,"removeEventListener");e.activate(),e.activate(),t(g(a,"pointerdown")).toBe(1),t(g(a,"keydown")).toBe(1),t(g(r,"pointerlockchange")).toBe(1),e.deactivate(),e.deactivate(),t(g(i,"pointerdown")).toBe(1),t(g(i,"keydown")).toBe(1),t(g(s,"pointerlockchange")).toBe(1)}),o("third-person activation remains idempotent",async()=>{const e=new B,o=h(),a=new d;a.actor=e,a.autoActivate=!1,await a.onInit(),w();const i=n.spyOn(o,"addEventListener"),r=n.spyOn(o,"removeEventListener"),s=n.spyOn(document,"addEventListener"),c=n.spyOn(document,"removeEventListener");a.activate(),a.activate(),t(g(i,"pointerdown")).toBe(1),t(g(i,"keydown")).toBe(1),t(g(s,"pointerlockchange")).toBe(1),a.deactivate(),a.deactivate(),t(g(r,"pointerdown")).toBe(1),t(g(r,"keydown")).toBe(1),t(g(c,"pointerlockchange")).toBe(1)});class B extends p{constructor(){super(),this.__isInitialised=!0}}/*
1
+ import"reflect-metadata";import{afterEach as e,expect as t,test as o,vi as n}from"vitest";n.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&n.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{},RayTestResult:class{}})),n.mock("../gameplay/services/world.js",()=>({World:class{}})),n.mock("@hology/nebula",()=>({ease:{easeInOutCubic:e=>e}}));import{Subject as a}from"rxjs";import{Container as i}from"typedi";import{Object3D as s,Ray as r,Scene as c,Vector3 as p}from"three";import{BaseActor as m}from"../gameplay/actors/actor.js";import{FirstPersonCameraComponent as l}from"../gameplay/actors/camera/first-person-camera-component.js";import{ThirdPersonCameraComponent as d}from"../gameplay/actors/camera/third-person-camera-component.js";import{PhysicsSystem as y}from"../gameplay/services/physics/physics-system.js";import{ViewController as u}from"../gameplay/services/render.js";import{World as v}from"../gameplay/services/world.js";function f(e=new g){const t=h(),o=new l;return o.actor=e,{camera:o,actor:e,element:t}}function h(){const e=document.createElement("div"),t=document.createElement("canvas");return e.appendChild(t),Object.defineProperty(e,"clientWidth",{configurable:!0,value:1280}),Object.defineProperty(e,"clientHeight",{configurable:!0,value:720}),i.set(u,{htmlElement:e,setCamera:n.fn(),onUpdate:()=>new a,onLateUpdate:()=>new a}),i.set(v,{scene:new c}),i.set(y,{sphereCast:n.fn()}),e}function w(){Object.defineProperty(document.body,"requestPointerLock",{configurable:!0,value:n.fn()}),Object.defineProperty(document,"pointerLockElement",{configurable:!0,value:null})}function B(e,t){return e.mock.calls.filter(e=>e[0]===t).length}e(()=>{n.restoreAllMocks(),i.reset()}),o("first-person camera clamps pitch input",()=>{const{camera:e}=f();e.rotationInput.rotateX(Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(Math.PI/2-.01),e.rotationInput.rotateX(2*-Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(-Math.PI/2+.01)}),o("first-person camera follows actor eye offset",async()=>{const e=new g;e.position.set(1,2,3),e.rotation.y=Math.PI/2,e.object.updateMatrixWorld(!0);const{camera:o}=f(e);o.eyeHeight=1.5,o.offsetZ=.25,o.autoActivate=!1,await o.onInit(),o.updateCameraTransform(),t(o.camera.position.x).toBeCloseTo(1.25),t(o.camera.position.y).toBeCloseTo(3.5),t(o.camera.position.z).toBeCloseTo(3)}),o("first-person aim helpers write to caller-provided objects",async()=>{const e=new g;e.position.set(0,1,0);const{camera:o}=f(e);o.autoActivate=!1,await o.onInit();const n=new p,a=new p,i=new r;t(o.getAimOrigin(n)).toBe(n),t(o.getAimDirection(a)).toBe(a),t(o.getAimRay(i)).toBe(i),t(i.origin).toEqual(n),t(i.direction).toEqual(a),t(a.x).toBeCloseTo(0),t(a.y).toBeCloseTo(0),t(a.z).toBeCloseTo(1),t(a.length()).toBeCloseTo(1)}),o("first-person camera pitch input tilts aim downward when positive",async()=>{const{camera:e}=f();e.autoActivate=!1,await e.onInit(),e.rotationInput.rotateX(.25),e.updateCameraTransform();const o=e.getAimDirection(new p);t(o.y).toBeLessThan(0),t(o.z).toBeGreaterThan(0),t(o.length()).toBeCloseTo(1)}),o("first-person hidden objects restore their previous visibility",()=>{const{camera:e}=f(),o=new s,n=new s;n.visible=!1,e.hideObjects(o,n),t(o.visible).toBe(!1),t(n.visible).toBe(!1),e.restoreHiddenObjects(),t(o.visible).toBe(!0),t(n.visible).toBe(!1)}),o("first-person activation is idempotent",async()=>{const{camera:e,element:o}=f();e.autoActivate=!1,await e.onInit(),w();const a=n.spyOn(o,"addEventListener"),i=n.spyOn(o,"removeEventListener"),s=n.spyOn(document,"addEventListener"),r=n.spyOn(document,"removeEventListener");e.activate(),e.activate(),t(B(a,"pointerdown")).toBe(1),t(B(a,"keydown")).toBe(1),t(B(s,"pointerlockchange")).toBe(1),e.deactivate(),e.deactivate(),t(B(i,"pointerdown")).toBe(1),t(B(i,"keydown")).toBe(1),t(B(r,"pointerlockchange")).toBe(1)}),o("third-person activation remains idempotent",async()=>{const e=new g,o=h(),a=new d;a.actor=e,a.autoActivate=!1,await a.onInit(),w();const i=n.spyOn(o,"addEventListener"),s=n.spyOn(o,"removeEventListener"),r=n.spyOn(document,"addEventListener"),c=n.spyOn(document,"removeEventListener");a.activate(),a.activate(),t(B(i,"pointerdown")).toBe(1),t(B(i,"keydown")).toBe(1),t(B(r,"pointerlockchange")).toBe(1),a.deactivate(),a.deactivate(),t(B(s,"pointerdown")).toBe(1),t(B(s,"keydown")).toBe(1),t(B(c,"pointerlockchange")).toBe(1)}),o("third-person camera does not treat sustained fast movement as a teleport",async()=>{const e=new g;h();const o=new d;o.actor=e,o.autoActivate=!0,o.collision=!1,o.fixedBehind=!1,o.smoothCamera=!0,o.smoothSpeed=10,await o.onInit(),o.onLateUpdate(1/60);for(let t=0;t<12;t++)e.position.x+=.2,o.onLateUpdate(1/60);t(e.position.x-o.camera.position.x).toBeGreaterThan(.7)}),o("third-person camera still snaps after a target teleport",async()=>{const e=new g;h();const o=new d;o.actor=e,o.autoActivate=!0,o.collision=!1,o.fixedBehind=!1,o.smoothCamera=!0,o.teleportSnapDistance=5,await o.onInit(),o.onLateUpdate(1/60),e.position.x=10,o.onLateUpdate(1/60),t(o.camera.position.x).toBeCloseTo(10)});class g extends m{constructor(){super(),this.__isInitialised=!0}}/*
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=net-character-movement.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{beforeAll as e,describe as t,expect as o,test as a}from"vitest";import{CharacterMovementMode as n}from"../gameplay/actors/builtin/components/character/modes.js";let r,c;function s(e={}){return{sequence:1,clientTime:.016,dt:.016,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0,...e}}function i(e={}){return{sequence:0,x:0,y:0,z:0,velocityX:0,velocityY:0,velocityZ:0,yaw:0,mode:n.walking,horizontalSpeed:0,...e}}function l(e){return{directionInput:{},jumpInput:{},sprintInput:{},rotationInput:{},setRootMotionAction(){},applyImpulse(){},...e}}e(async()=>{"undefined"!=typeof HTMLCanvasElement&&Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>({fillStyle:"",fillRect(){},clearRect(){},getImageData:()=>({data:new Uint8ClampedArray(4)}),putImageData(){},createImageData:()=>({data:new Uint8ClampedArray(4)}),setTransform(){},drawImage(){},save(){},restore(){},beginPath(){},moveTo(){},lineTo(){},closePath(){},stroke(){},translate(){},scale(){},rotate(){},arc(){},fill(){},measureText:()=>({width:0}),transform(){},rect(){},clip(){}})}),r=await import("../gameplay/actors/builtin/components/character/net-character-movement-protocol.js"),c=(await import("../gameplay/actors/builtin/components/character/character-movement-like.js")).getCharacterMovementLike}),t("net character movement packets",()=>{a("round trips batched saved moves",()=>{const e=[s({sequence:1,dt:.016,inputX:.25,inputY:1,yaw:.5,flags:3,clientX:1,clientY:2,clientZ:3}),s({sequence:2,dt:.033,inputX:-.5,inputY:.5,yaw:-1,flags:1,clientX:4,clientY:5,clientZ:6,rootMotionZ:.2})],t=r.decodeNetCharacterMoveBatch(r.encodeNetCharacterMoveBatch(e));o(t.ok).toBe(!0),t.ok&&(o(t.value.moves).toHaveLength(2),o(t.value.moves[0].sequence).toBe(1),o(t.value.moves[0].clientTime).toBeCloseTo(.016,3),o(t.value.moves[0].dt).toBeCloseTo(.016,3),o(t.value.moves[0].inputX).toBeCloseTo(.25,4),o(t.value.moves[1].rootMotionZ).toBeCloseTo(.2))}),a("round trips owner ack, correction, and simulated state packets",()=>{const e=i({sequence:42,x:10,y:2,z:-3,velocityX:1,yaw:1.25,mode:n.falling,horizontalSpeed:7}),t=r.decodeNetCharacterOwnerAck(r.encodeNetCharacterOwnerAck(42)),a=r.decodeNetCharacterOwnerCorrection(r.encodeNetCharacterOwnerCorrection(e)),c=r.decodeNetCharacterSimulatedState(r.encodeNetCharacterSimulatedState(e));o(t.ok&&t.value.sequence).toBe(42),o(a.ok&&a.value.snapshot.sequence).toBe(42),o(c.ok&&c.value.mode).toBe(n.falling),o(c.ok&&c.value.horizontalSpeed).toBeCloseTo(7)}),a("rejects malformed, unsupported version, and wrong-kind packets",()=>{const e=r.encodeNetCharacterMoveBatch([s({sequence:1})]),t=e.slice();t[0]=99;const a=e.slice();a[1]=99,o(r.decodeNetCharacterMoveBatch(new Uint8Array([1,1,0]))).toMatchObject({ok:!1,reason:"malformed"}),o(r.decodeNetCharacterMoveBatch(t)).toMatchObject({ok:!1,reason:"unsupported-version"}),o(r.decodeNetCharacterMoveBatch(a)).toMatchObject({ok:!1,reason:"wrong-kind"})})}),t("net character prediction helpers",()=>{a("combines adjacent equivalent saved moves by default",()=>{const e=new r.NetCharacterSavedMoveBuffer(4);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t).toHaveLength(1),o(t[0].sequence).toBe(2),o(t[0].dt).toBeCloseTo(.032),o(t[0].clientZ).toBeCloseTo(.3)}),a("keeps adjacent saved moves separate when combining is disabled",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,-1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("preserves a sprint start move for server simulation and replay",()=>{const e=new r.NetCharacterSavedMoveBuffer(4),t=r.MOVE_FLAG_SPRINT|r.MOVE_FLAG_SPRINT_START;e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,flags:t})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,flags:r.MOVE_FLAG_SPRINT}));const a=r.encodeNetCharacterMoveBatch(e.getMovesForSend(4)),n=r.decodeNetCharacterMoveBatch(a);o(n.ok).toBe(!0),n.ok&&(o(n.value.moves.map(e=>e.sequence)).toEqual([1,2]),o(n.value.moves[0].flags).toBe(t))}),a("respects saved move max combined delta time",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,.02);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1,2])}),a("does not combine a move after it has been selected for sending",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,.1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1]),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("selects the oldest unacked move and newest moves for send packets",()=>{const e=new r.NetCharacterSavedMoveBuffer(8);for(let t=1;t<=5;t++)e.push(s({sequence:t,clientTime:.02*t,dt:.02,inputX:t%2==0?.25:-.25}));o(e.getMovesForSend(3).map(e=>e.sequence)).toEqual([1,4,5])}),a("replays saved moves with a deterministic simulator",()=>{const e=r.replayNetCharacterSavedMoves({x:0},[s({sequence:1,dt:.1,inputY:1}),s({sequence:2,dt:.2,inputY:1})],(e,t)=>({x:e.x+t.inputY*t.dt*10}));o(e.x).toBeCloseTo(3)}),a("validates move sequence, dt, and normalized input",()=>{o(r.validateNetCharacterMove(s({sequence:2,dt:.016}),1)).toBe(!0),o(r.validateNetCharacterMove(s({sequence:1,dt:.016}),1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,clientTime:0}),1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,dt:.5}),1,.1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,inputX:1,inputY:1}),1)).toBe(!1)}),a("derives server move dt from client timestamps",()=>{const e=r.createNetCharacterServerMoveTimingState();r.accrueNetCharacterServerMoveTime(e,.05);const t=r.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.016}),{maxMoveDeltaTime:.1}),a=r.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.05}),{maxMoveDeltaTime:.1});o(t).toBeCloseTo(.016),o(a).toBeCloseTo(.034),o(e.lastClientTime).toBeCloseTo(.05)}),a("clamps server move dt and rejects clients too far ahead of server time",()=>{const e=r.createNetCharacterServerMoveTimingState();r.accrueNetCharacterServerMoveTime(e,.02);const t=r.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.5}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.1}),a=r.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.7}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.01});o(t).toBeCloseTo(.1),o(a).toBe(-1)})}),t("net character simulated proxy smoothing",()=>{a("interpolates delayed snapshots",()=>{const e=new r.NetCharacterProxySmoother(.1,20),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.6,t)).toBe(!0),o(t.x).toBeCloseTo(5)}),a("snaps instead of interpolating large gaps",()=>{const e=new r.NetCharacterProxySmoother(0,1),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.5,t)).toBe(!0),o(t.x).toBe(10)})}),t("character movement compatibility",()=>{a("prefers the default movement component when present",()=>{const e=l({horizontalSpeed:2}),t=l({horizontalSpeed:5,isDefaultCharacterMovementComponent:!0});o(c({oldMovement:e,defaultMovement:t,attachedComponents:[]})).toBe(t)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ import "reflect-metadata";
2
+ //# sourceMappingURL=net-property-snapshot.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{__decorate as e,__metadata as t}from"tslib";import"reflect-metadata";import{Subject as n}from"rxjs";import{Vector3 as r}from"three";import{expect as s,test as o,vi as c}from"vitest";c.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),c.mock("../gameplay/services/world.js",()=>({World:class{}}));import{Actor as a,BaseActor as i}from"../gameplay/actors/actor.js";import{ActorComponent as l}from"../gameplay/actors/component.js";import{$actorComponents as p}from"../gameplay/actors/internal/component-init.js";import{NetMode as d}from"../gameplay/net/net-session.js";import{NetRole as u}from"../gameplay/net/service/net-actor-role.js";import{NetSerializer as w}from"../gameplay/net/service/net-serializer.js";import{NetService as _}from"../gameplay/net/service/net-service.js";import{ReplOn as R}from"../gameplay/net/service/replication.js";import{getRpcMethodId as y,registerRpcMethod as g,RunsOn as v}from"../gameplay/net/service/rpc.js";import{RunOnServer as m}from"../gameplay/net/service/rpc-decorator.js";const f=2,h=3,B=4,b=1,S=21,V=25,A=7,C=8;let P=class extends i{constructor(){super(...arguments),this.allValue=0,this.ownerValue=0,this.notOwnerValue=0,this.changingValue=0,this.allOnRepSawOwnerValue=-1,this.clientRpcCalls=[],this.serverRpcCalls=[],this.clientBinaryRpcCalls=[],this.serverBinaryRpcCalls=[],this.largeBinaryRpcByteLength=0,this.largeBinaryRpcLastByte=0}onRep_allValue(){this.allOnRepSawOwnerValue=this.ownerValue}clientRpc(e){this.clientRpcCalls.push(e)}serverRpc(e){this.serverRpcCalls.push(e)}clientBinaryRpc(e){this.clientBinaryRpcCalls.push(Array.from(e))}serverBinaryRpc(e){this.serverBinaryRpcCalls.push(Array.from(e))}largeBinaryRpc(e){this.largeBinaryRpcByteLength=e.byteLength,this.largeBinaryRpcLastByte=e[e.byteLength-1]}};P=e([a({replicate:!0})],P);class O extends l{constructor(){super(...arguments),this.componentValue="",this.binaryRpcCalls=[]}componentBinaryRpc(e){this.binaryRpcCalls.push(Array.from(e))}}g(P.prototype,"clientRpc"),g(P.prototype,"serverRpc"),g(P.prototype,"clientBinaryRpc"),g(P.prototype,"serverBinaryRpc"),g(P.prototype,"largeBinaryRpc"),g(O.prototype,"componentBinaryRpc");class I extends i{constructor(){super(...arguments),this.baseRpcCalls=[]}baseRpc(e){this.baseRpcCalls.push(e)}}class M extends I{constructor(){super(...arguments),this.derivedRpcCalls=[]}derivedRpc(e){this.derivedRpcCalls.push(e)}}g(I.prototype,"baseRpc"),g(M.prototype,"derivedRpc");class k extends i{inheritedRpc(){}}e([m(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],k.prototype,"inheritedRpc",null);class q extends k{derivedRpc(){}}function U(e,t,n={}){const r={actors:t,removeActor(e){const n=t.indexOf(e);n>=0&&t.splice(n,1)}},s=Object.create(w.prototype);s.world=r;const o=j(e,n.clients??[],n.server,n.sent??[]),c=Object.create(_.prototype);return c.session=o,c.world=r,c.serializer=s,c.actorConnection=new Map,c.actorPreSpawnMessageBuffer=new Map,c.actorRefMissingMessageQueue=new Map,c.netStartupActorSpawnQueue=new Map,c.spawningActorNetIds=new Set,c.shouldReplicateMap=new Map,c.replicatedActors=new Map,c.replicatedProperties=new Map,c.replicatedPropertyRegistry=new Map,c.nextNetId=1,c}function j(e,t,r,s){return{id:"test-session",mode:e,clients:t,server:r,reconnect(){},disconnect(){},sendMessage(e,t,n){s.push({receiver:e,reliable:t,buffer:n})},hasMessage:()=>0,readMessage:()=>null,playerJoined:new n,playerLeft:new n}}function L(e){return{id:e}}function x(e){const t=new ArrayBuffer(5),n=new DataView(t);return n.setUint8(0,A),n.setUint32(1,e,!0),t}function E(e){const t=new ArrayBuffer(5),n=new DataView(t);return n.setUint8(0,B),n.setUint32(1,e,!0),t}function z(e){return new DataView(e).getUint8(0)}function D(e){return new DataView(e).getUint8(5)}function Q(e){const t=new DataView(e);let n=0;const r=t.getUint8(n);n+=1;const s=t.getUint32(n,!0);n+=4;n+=1+t.getUint8(n),n+=12,n+=12;const o=t.getUint8(n);n+=1;return{messageType:r,actorId:s,netRole:o,ownerId:t.getUint32(n)}}e([m(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],q.prototype,"derivedRpc",null),o("property snapshots are sent after actorReady and respect replication targets",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=10,r.allValue=11,r.ownerValue=22,r.notOwnerValue=33;const o=[],c=U(d.dedicatedServer,[r],{clients:[e,t],sent:o});c.setOwningConnection(r,e),c.registerReplicatedProperty(R.all,!0,r,"allValue",r),c.registerReplicatedProperty(R.owner,!0,r,"ownerValue",r),c.registerReplicatedProperty(R.notOwner,!0,r,"notOwnerValue",r),c.getPreSpawnMessageQueue(r,e),c.processActorReady(e,x(r.__netid));const a=new P;a.__netid=r.__netid;U(d.client,[a],{server:n}).processPropSnapshot(n,o[0].buffer),s(a.allValue).toBe(11),s(a.ownerValue).toBe(22),s(a.notOwnerValue).toBe(0),s(a.allOnRepSawOwnerValue).toBe(22),o.length=0,c.getPreSpawnMessageQueue(r,t),c.processActorReady(t,x(r.__netid));const i=new P;i.__netid=r.__netid;U(d.client,[i],{server:n}).processPropSnapshot(n,o[0].buffer),s(i.allValue).toBe(11),s(i.ownerValue).toBe(0),s(i.notOwnerValue).toBe(33)}),o("property updates queued during spawn are replaced by the latest snapshot value",()=>{const e=L(1),t=L("server"),n=new P;n.changingValue=1;const r=[],o=U(d.dedicatedServer,[n],{clients:[e],sent:r});o.setReplicate(n,!0),o.registerReplicatedProperty(R.all,!0,n,"changingValue",n),o.sendActorSpawn(n,[e]),r.length=0,n.changingValue=2,o.handlePropertySet(R.all,!0,n,"changingValue",n.changingValue),n.changingValue=3,o.handlePropertySet(R.all,!0,n,"changingValue",n.changingValue),s(r).toHaveLength(0),o.processActorReady(e,x(n.__netid)),s(r.map(e=>z(e.buffer))).toEqual([C]);const c=new P;c.__netid=n.__netid;U(d.client,[c],{server:t}).processPropSnapshot(t,r[0].buffer),s(c.changingValue).toBe(3)}),o("property snapshots include component properties",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=20;const r=new O;r.actor=n,r.__netid=1,r.componentValue="server component",n[p]=[r];const o=[],c=U(d.dedicatedServer,[n],{clients:[e],sent:o});c.registerReplicatedProperty(R.all,!0,n,"componentValue",r,r),c.getPreSpawnMessageQueue(n,e),c.processActorReady(e,x(n.__netid));const a=new P;a.__netid=n.__netid;const i=new O;i.actor=a,i.__netid=r.__netid,a[p]=[i];U(d.client,[a],{server:t}).processPropSnapshot(t,o[0].buffer),s(i.componentValue).toBe("server component")}),o("property snapshots queue atomically when they reference a missing actor",()=>{const e=c.spyOn(console,"warn").mockImplementation(()=>{}),t=c.spyOn(console,"debug").mockImplementation(()=>{}),n=c.spyOn(console,"log").mockImplementation(()=>{}),r=L(1),o=L("server"),a=new P;a.__netid=30,a.allValue=44;const i=new P;i.__netid=31,a.referencedActor=i;const l=[],p=U(d.dedicatedServer,[a,i],{clients:[r],sent:l});p.registerReplicatedProperty(R.all,!0,a,"allValue",a),p.registerReplicatedProperty(R.all,!0,a,"referencedActor",a),p.getPreSpawnMessageQueue(a,r),p.processActorReady(r,x(a.__netid));const u=new P;u.__netid=a.__netid;const w=U(d.client,[u],{server:o});w.processPropSnapshot(o,l[0].buffer),s(u.allValue).toBe(0),s(u.referencedActor).toBeUndefined();const _=new P;_.__netid=i.__netid,w.world.actors.push(_),w.processQueuedActorMessages(_.__netid),s(u.allValue).toBe(44),s(u.referencedActor).toBe(_),e.mockRestore(),t.mockRestore(),n.mockRestore()}),o("live property replication applies server updates and ignores non-server messages",()=>{const e=L("server"),t=L(1),n=new P;n.__netid=40;const r=[],o=U(d.dedicatedServer,[n],{clients:[t],sent:r});o.setReplicate(n,!0),o.sendActorSpawn(n,[t]),o.processActorReady(t,x(n.__netid)),r.length=0,o.handlePropertySet(R.all,!0,n,"allValue",55),s(r).toHaveLength(1),s(z(r[0].buffer)).toBe(f);const a=new P;a.__netid=n.__netid,a.ownerValue=66;const i=U(d.client,[a],{server:e});i.processProp(e,r[0].buffer),s(a.allValue).toBe(55),s(a.allOnRepSawOwnerValue).toBe(66);const l=c.spyOn(console,"warn").mockImplementation(()=>{}),p=r[0].buffer;a.allValue=12,i.processProp(L("rogue"),p),s(a.allValue).toBe(12),l.mockRestore()}),o("server RPCs from clients require ownership and owner RPCs run on the owning client",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=50;const o=U(d.dedicatedServer,[r],{clients:[e,t]});o.setReplicate(r,!0),o.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[e,t]}),o.setOwningConnection(r,e);const a=new P;a.__netid=r.__netid;const i=[],l=U(d.client,[a],{server:n,sent:i});l.setReplicate(a,!0),l.sendRpc(v.server,!0,a,"serverRpc",[77]),s(D(i[0].buffer)).toBe(y(a,"serverRpc")),o.processRpc(e,i[0].buffer),s(r.serverRpcCalls).toEqual([77]);const p=new P;p.__netid=r.__netid;const u=[],w=U(d.client,[p],{server:n,sent:u});w.setReplicate(p,!0);const _=c.spyOn(console,"warn").mockImplementation(()=>{});w.sendRpc(v.server,!0,p,"serverRpc",[88]),o.processRpc(t,u[0].buffer),s(r.serverRpcCalls).toEqual([77]),_.mockRestore();const R=[];o.session=j(d.dedicatedServer,[e,t],void 0,R),o.sendRpc(v.client,!0,r,"clientRpc",[99]),s(R.map(e=>e.receiver.id)).toEqual([e.id]),s(z(R[0].buffer)).toBe(b),s(D(R[0].buffer)).toBe(y(r,"clientRpc")),l.processRpc(n,R[0].buffer),s(a.clientRpcCalls).toEqual([99])}),o("RPC method ids include inherited methods before derived registrations",()=>{const e=L(1),t=L("server"),n=new M;n.__netid=60;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new M;o.__netid=n.__netid;const c=[],a=U(d.client,[o],{server:t,sent:c});a.setReplicate(o,!0),s(y(o,"baseRpc")).toBe(0),s(y(o,"derivedRpc")).toBe(1),a.sendRpc(v.server,!0,o,"baseRpc",[10]),a.sendRpc(v.server,!0,o,"derivedRpc",[20]),s(D(c[0].buffer)).toBe(0),s(D(c[1].buffer)).toBe(1),r.processRpc(e,c[0].buffer),r.processRpc(e,c[1].buffer),s(n.baseRpcCalls).toEqual([10]),s(n.derivedRpcCalls).toEqual([20])}),o("decorated inherited RPCs do not need to be redecorated on subclasses",()=>{const e=new q;s(y(e,"inheritedRpc")).toBe(0),s(y(e,"derivedRpc")).toBe(1)}),o("binary actor RPCs bypass NetSerializer from client to server",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=80;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new P;o.__netid=n.__netid;const a=[],i=U(d.client,[o],{server:t,sent:a});i.setReplicate(o,!0);const l=c.spyOn(i.serializer,"encode"),p=c.spyOn(r.serializer,"decode");i.sendRpc(v.server,!0,o,"serverBinaryRpc",[new Uint8Array([1,2,3])]),s(z(a[0].buffer)).toBe(S),s(l).not.toHaveBeenCalled(),r.processRpc(e,a[0].buffer),s(p).not.toHaveBeenCalled(),s(n.serverBinaryRpcCalls).toEqual([[1,2,3]]),l.mockRestore(),p.mockRestore()}),o("server to owner binary RPC preserves owner routing",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=81;const o=[],c=U(d.dedicatedServer,[r],{clients:[e,t],sent:o});c.setReplicate(r,!0),c.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[e,t]}),c.setOwningConnection(r,e);const a=new Uint8Array([9,8,7]);c.sendRpc(v.client,!0,r,"clientBinaryRpc",[new DataView(a.buffer)]),s(o.map(e=>e.receiver.id)).toEqual([e.id]),s(z(o[0].buffer)).toBe(S);const i=new P;i.__netid=r.__netid;U(d.client,[i],{server:n}).processRpc(n,o[0].buffer),s(i.clientBinaryRpcCalls).toEqual([[9,8,7]])}),o("component binary RPC resolves component id",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=82;const r=new O;r.actor=n,r.__netid=4,n[p]=[r];const o=[],c=U(d.dedicatedServer,[n],{clients:[e],sent:o});c.setReplicate(n,!0),c.replicatedActors.set(n,{spawnedAt:Date.now(),connections:[e]}),c.sendRpc(v.all,!0,n,"componentBinaryRpc",[new Uint8Array([4,5,6])],r),s(z(o[0].buffer)).toBe(V);const a=new P;a.__netid=n.__netid;const i=new O;i.actor=a,i.__netid=r.__netid,a[p]=[i];U(d.client,[a],{server:t}).processRpc(t,o[0].buffer),s(i.binaryRpcCalls).toEqual([[4,5,6]])}),o("binary RPC payload length supports more than uint16",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=83;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new P;o.__netid=n.__netid;const c=[],a=U(d.client,[o],{server:t,sent:c});a.setReplicate(o,!0);const i=new Uint8Array(7e4);i[i.length-1]=231,a.sendRpc(v.server,!0,o,"largeBinaryRpc",[i]),s(z(c[0].buffer)).toBe(S),s(function(e){const t=new DataView(e);let n=6;return t.getUint32(n,!0)}(c[0].buffer)).toBe(i.length),r.processRpc(e,c[0].buffer),s(n.largeBinaryRpcByteLength).toBe(i.length),s(n.largeBinaryRpcLastByte).toBe(231)}),o("binary RPCs queue when the target actor is missing",()=>{const e=c.spyOn(console,"debug").mockImplementation(()=>{}),t=L(1),n=L("server"),r=new P;r.__netid=84;const o=[],a=U(d.dedicatedServer,[r],{clients:[t],sent:o});a.setReplicate(r,!0),a.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[t]}),a.sendRpc(v.all,!0,r,"clientBinaryRpc",[new Uint8Array([8,4])]);const i=U(d.client,[],{server:n});i.processRpc(n,o[0].buffer);const l=new P;l.__netid=r.__netid,i.world.actors.push(l),i.processQueuedActorMessages(l.__netid),s(l.clientBinaryRpcCalls).toEqual([[8,4]]),e.mockRestore()}),o("actor spawn messages are personalized with owner role and owner actor id",()=>{const e=L(1),t=L(2),n=new P;n.__netid=90;const r=new P;r.owner=n;const o=[],c=U(d.dedicatedServer,[n,r],{clients:[e,t],sent:o});c.setOwningConnection(n,e),c.sendActorSpawn(r,[e,t]),s(o).toHaveLength(2);const a=Q(o[0].buffer),i=Q(o[1].buffer);s(a.messageType).toBe(h),s(a.actorId).toBe(r.__netid),s(a.netRole).toBe(u.autonomousProxy),s(a.ownerId).toBe(n.__netid),s(i.actorId).toBe(r.__netid),s(i.netRole).toBe(u.simulatedProxy),s(i.ownerId).toBe(n.__netid)}),o("startup actor spawn binds an existing materialized scene actor",async()=>{const e=c.spyOn(console,"log").mockImplementation(()=>{}),t=L(1),n=L("server"),r="level/chest",o=new P;o.__netSceneId=r;const a=[];U(d.dedicatedServer,[o],{clients:[t],sent:a}).sendActorSpawn(o,[t]);const i=new P;i.__netSceneId=r;const l=[],p=U(d.client,[i],{server:n,sent:l});await p.processSpawn(n,a[0].buffer),s(i.__netid).toBe(o.__netid),s(i.netRole).toBe(u.simulatedProxy),s(p.world.actors).toEqual([i]),s(l).toHaveLength(1),s(z(l[0].buffer)).toBe(A),s(new DataView(l[0].buffer).getUint32(1,!0)).toBe(o.__netid),e.mockRestore()}),o("startup actor spawn waits for the materialized scene actor",async()=>{const e=c.spyOn(console,"log").mockImplementation(()=>{}),t=L(1),n=L("server"),r="level/delayed-chest",o=new P;o.__netSceneId=r;const a=[];U(d.dedicatedServer,[o],{clients:[t],sent:a}).sendActorSpawn(o,[t]);const i=[],l=[],p=U(d.client,i,{server:n,sent:l});await p.processSpawn(n,a[0].buffer),s(l).toHaveLength(0),s(p.netStartupActorSpawnQueue.get(r)).toHaveLength(1);const w=new P;w.__netSceneId=r,i.push(w),p.processQueuedNetStartupActorSpawns(w),s(w.__netid).toBe(o.__netid),s(w.netRole).toBe(u.simulatedProxy),s(p.netStartupActorSpawnQueue.has(r)).toBe(!1),s(l).toHaveLength(1),s(z(l[0].buffer)).toBe(A),e.mockRestore()}),o("materialized scene actors get client-side initial roles",()=>{const e=new P;e.__netSceneId="level/replicated";const t=new I;t.__netSceneId="level/local-only";const n=U(d.client,[e,t]);n.configureMaterializedActorRole(e),n.configureMaterializedActorRole(t),s(e.netRole).toBe(u.simulatedProxy),s(t.netRole).toBe(u.none)}),o("remove actor messages only remove client actors when sent by the server",()=>{const e=L("server"),t=new P;t.__netid=60;const n=[t],r=U(d.client,n,{server:e}),o=c.spyOn(console,"warn").mockImplementation(()=>{});r.processRemoveActor(L("rogue"),E(t.__netid)),s(n).toContain(t),r.processRemoveActor(e,E(t.__netid)),s(n).not.toContain(t),o.mockRestore()}),o("net serializer preserves actor references and three vector values",()=>{const e=new P;e.__netid=70;const t={actors:[e]},n=Object.create(w.prototype);n.world=t;const o=n.encode({actor:e,position:new r(1,2,3),nested:{values:new Set([new r(4,5,6)])}}),c={error:!1,missingActorId:void 0,reset(){this.error=!1,this.missingActorId=void 0}},a=n.decode(o,c);s(a.actor).toBe(e),s(a.position).toBeInstanceOf(r),s(a.position.toArray()).toEqual([1,2,3]),s([...a.nested.values][0].toArray()).toEqual([4,5,6]),s(c.error).toBe(!1)});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=painted-scatter-manager.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as e,expect as t,it as n,vi as a}from"vitest";a.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&a.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{}})});import{BoxGeometry as s,Group as i,InstancedMesh as o,Mesh as r,MeshBasicMaterial as c,PerspectiveCamera as l,Raycaster as d,Scene as f}from"three";import{PaintedScatterManager as u}from"../scene/scatter/painted-scatter-manager.js";function p(e,t,n=new l){const s=new f,i=new Map(t.map(e=>[e.id,e]));let o=()=>{};const r={getObjects:()=>e,onCreate:()=>{},onUpdate:()=>{},onRemove:()=>{}},c=a.fn(async e=>({scene:m(),animations:[]}));return{scene:s,manager:new u(s,r,{camera:n,onLoop:a.fn(e=>{o=e}),removeOnLoop:a.fn(e=>{o===e&&(o=()=>{})})},{getMesh:c},{getAsset:a.fn(async e=>i.get(e)??null)}),getMesh:c,runLoop:()=>o()}}function h(e){return e.children.find(e=>"Painted Scatter"===e.name)}function m(){const e=new i;return e.add(new r(new s(1,1,1),new c)),e}function g(e){return{id:"scatter",name:"Scatter",type:"scatter",position:[0,0,0],rotation:[0,0,0],scale:[1,1,1],paintedScatter:{...e,brush:{radius:4,density:1,eraseMode:"enabled"}}}}function w(e,t,n={}){return{id:e,assetId:t,enabled:!0,weight:1,viewDistance:100,density:1,sparseNoiseThreshold:1,sparseNoiseScale:1,alignToNormal:!1,maxSlope:90,randomRotation:!0,normalsUp:!1,scaleMin:1,scaleMax:1,offsetMin:0,offsetMax:0,minSpacing:0,castShadow:!1,receiveShadow:!0,...n}}function y(e,t){return{key:e,revision:1,instances:t}}function b(e,t,n){return{id:e,paletteId:t,position:n,rotation:[0,0,0],scale:[1,1,1]}}function v(e){return{id:e,name:e,type:"mesh",fileKey:`${e}.glb`,fileFormat:"glb"}}async function M(e){for(let t=0;t<40;t++){if(e())return;await new Promise(e=>setTimeout(e,0))}t(e()).toBe(!0)}e("PaintedScatterManager",()=>{n("batches instances by cell and render asset with raycasting disabled",async()=>{const e=v("asset-a"),n=v("asset-b"),a=w("palette-a",e.id),s=w("palette-b",n.id),i=[g({palette:[a,s],cells:[y("0:0:0",[b("a-1",a.id,[0,0,0]),b("a-2",a.id,[1,0,0]),b("b-1",s.id,[2,0,0])]),y("1:0:0",[b("a-3",a.id,[35,0,0])])]})],{scene:r,manager:c}=p(i,[e,n]);c.queueRefresh(!0),await M(()=>3===h(r).children.length);const l=h(r).children;t(l.map(e=>e.count).sort()).toEqual([1,1,2]),t(l.every(e=>e instanceof o)).toBe(!0);const f=[];l[0].raycast(new d,f),t(f).toHaveLength(0)}),n("updates cell visibility from view distance",async()=>{const e=v("asset-a"),n=w("palette-a",e.id,{viewDistance:5}),a=[g({palette:[n],cells:[y("0:0:0",[b("a-1",n.id,[0,0,0])])]})],s=new l;s.position.set(100,0,0);const{scene:i,manager:o,runLoop:r}=p(a,[e],s);o.queueRefresh(!0),await M(()=>1===h(i).children.length);const c=h(i).children[0];t(c.visible).toBe(!1),s.position.set(0,0,0),r(),t(c.visible).toBe(!0)}),n("rebuilds referenced assets on forced refresh and removes deleted cells",async()=>{const e=v("asset-a"),n=w("palette-a",e.id),a=g({palette:[n],cells:[y("0:0:0",[b("a-1",n.id,[0,0,0])])]}),{scene:s,manager:i,getMesh:o}=p([a],[e]);i.queueRefresh(!0),await M(()=>1===h(s).children.length);const r=h(s).children[0];t(i.usesScatterAsset(e.id)).toBe(!0),t(o).toHaveBeenCalledTimes(1),i.queueRefresh(!0),await M(()=>2===o.mock.calls.length),await M(()=>1===h(s).children.length&&h(s).children[0]!==r),a.paintedScatter.cells=[],i.queueRefresh(!1),await M(()=>0===h(s).children.length)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,4 +1,4 @@
1
- import{expect as e,test as t,vi as a}from"vitest";import{Vector3 as r}from"three";import{rgb as o,UniformSampler2d as p}from"three-shader-graph";a.mock("three/examples/jsm/Addons.js",()=>({BufferGeometryUtils:{},ConvexHull:class{},SkeletonUtils:{clone:e=>e}}));import{convertConfiguredParamsToRuntimeTypes as n,convertConfiguredParamValueToRuntimeType as s,inferRuntimeSerializedParamTypeHint as i}from"../scene/custom-param-runtime-types.js";import{SerializedParamType as c}from"../scene/model.js";class m{constructor(){this.tint=o("white"),this.direction=new r(1,1,1),this.map=new p("map")}}t("runtime parameter type inference uses class instance default values",()=>{e(i((new m).tint)?.type).toBe(c.RgbNode),e(i((new m).direction)?.type).toBe(c.Vector3),e(i((new m).map)?.type).toBe(c.Sampler2DNode)}),t("configured params can be retargeted to inferred runtime types",()=>{const t=new m,{params:a,skipped:r}=n({tint:{type:c.Color,value:"#336699"},direction:{type:c.Number,value:.5}},{parameterTarget:t});e(r).toEqual([]),e(a.tint).toMatchObject({type:c.RgbNode,value:"#336699"}),e(a.direction).toMatchObject({type:c.Vector3,value:[.5,.5,.5]})}),t("stored texture params can be retargeted to sampler node params",()=>{const{params:t,skipped:a}=n({map:{type:c.Texture,value:"texture-asset-id"}},{parameterTarget:new m});e(a).toEqual([]),e(t.map).toMatchObject({type:c.Sampler2DNode,value:"texture-asset-id"})}),t("configured params are normalized when stored type changed but value shape did not",()=>{const t=s({type:c.Vector3,value:.5},{type:c.Vector3});e(t).toMatchObject({type:c.Vector3,value:[.5,.5,.5]})}),t("configured params that cannot be safely coerced are skipped",()=>{const t=new m,a=s({type:c.Texture,value:"texture-asset-id"},i(t.direction));e(a).toBeNull()}),t("runtime type conversion caches extracted parameter metadata",()=>{const t=a.fn(()=>[{name:"direction",type:r,options:{}}]),o=a.fn(()=>c.Vector3),p={direction:{type:c.Number,value:.5}};n(p,{parameterType:m,extractPropertyParameters:t,toSerializedParamType:o}),n(p,{parameterType:m,extractPropertyParameters:t,toSerializedParamType:o}),e(t).toHaveBeenCalledTimes(1),e(o).toHaveBeenCalledTimes(1)}),t("runtime type conversion can be disabled",()=>{const t={direction:{type:c.Number,value:.5}},{params:a,skipped:r}=n(t,{parameterTarget:new m,applyRuntimeParamTypeInference:!1});e(a).toBe(t),e(a.direction).toMatchObject({type:c.Number,value:.5}),e(r).toEqual([])});/*
1
+ import{expect as e,test as t,vi as a}from"vitest";import{Vector3 as r}from"three";import{rgb as o,UniformSampler2d as p}from"three-shader-graph";a.mock("three/examples/jsm/Addons.js",()=>({BufferGeometryUtils:{},ConvexHull:class{},SkeletonUtils:{clone:e=>e}}));import{convertConfiguredParamsToRuntimeTypes as n,convertConfiguredParamValueToRuntimeType as s,inferRuntimeSerializedParamTypeHint as i}from"../scene/custom-param-runtime-types.js";import{SerializedParamType as c}from"../scene/model.js";class m{constructor(){this.tint=o("white"),this.direction=new r(1,1,1),this.map=new p("map")}}t("runtime parameter type inference uses class instance default values",()=>{e(i((new m).tint)?.type).toBe(c.RgbNode),e(i((new m).direction)?.type).toBe(c.Vector3),e(i((new m).map)?.type).toBe(c.Sampler2DNode)}),t("configured params can be retargeted to inferred runtime types",()=>{const t=new m,{params:a,skipped:r}=n({tint:{type:c.Color,value:"#336699"},direction:{type:c.Number,value:.5}},{parameterTarget:t});e(r).toEqual([]),e(a.tint).toMatchObject({type:c.RgbNode,value:"#336699"}),e(a.direction).toMatchObject({type:c.Vector3,value:[.5,.5,.5]})}),t("stored texture params can be retargeted to sampler node params",()=>{const{params:t,skipped:a}=n({map:{type:c.Texture,value:"texture-asset-id"}},{parameterTarget:new m});e(a).toEqual([]),e(t.map).toMatchObject({type:c.Sampler2DNode,value:"texture-asset-id"})}),t("configured params are normalized when stored type changed but value shape did not",()=>{const t=s({type:c.Vector3,value:.5},{type:c.Vector3});e(t).toMatchObject({type:c.Vector3,value:[.5,.5,.5]})}),t("float node params preserve serialized particle curve values",()=>{const t={time:"particle",easing:"Linear",a:.25,b:1},a=s({type:c.FloatNode,value:t},{type:c.FloatNode});e(a).toMatchObject({type:c.FloatNode,value:t})}),t("configured params that cannot be safely coerced are skipped",()=>{const t=new m,a=s({type:c.Texture,value:"texture-asset-id"},i(t.direction));e(a).toBeNull()}),t("runtime type conversion caches extracted parameter metadata",()=>{const t=a.fn(()=>[{name:"direction",type:r,options:{}}]),o=a.fn(()=>c.Vector3),p={direction:{type:c.Number,value:.5}};n(p,{parameterType:m,extractPropertyParameters:t,toSerializedParamType:o}),n(p,{parameterType:m,extractPropertyParameters:t,toSerializedParamType:o}),e(t).toHaveBeenCalledTimes(1),e(o).toHaveBeenCalledTimes(1)}),t("runtime type conversion can be disabled",()=>{const t={direction:{type:c.Number,value:.5}},{params:a,skipped:r}=n(t,{parameterTarget:new m,applyRuntimeParamTypeInference:!1});e(a).toBe(t),e(a.direction).toMatchObject({type:c.Number,value:.5}),e(r).toEqual([])});/*
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{expect as t,test as o}from"vitest";import*as e from"three";import{BaseActor as i}from"../gameplay/actors/actor.js";import{CharacterAnimationComponent as n,CharacterMovementComponent as s}from"../gameplay/actors/index.js";import{RootMotionClip as r}from"../gameplay/animation/root-motion.js";import{createAnimationClipInstance as a,createAnimationSubTrack as c,evaluateAnimationClipOutputProgress as l,evaluateAnimationClipSourceProgress as m,normalizeAnimationClipRetiming as p,SequencePlayer as u,buildRetimedAnimationClip as d}from"../effects/sequence/index.js";function g(t){return Array.from(t).map(t=>Math.round(1e3*t)/1e3)}o("uniform retiming mapping stays linear",()=>{t(m(void 0,.25)).toBeCloseTo(.25),t(l(void 0,.75)).toBeCloseTo(.75),t(m({mode:"uniform",points:[]},.6)).toBeCloseTo(.6)}),o("impact retiming redistributes time around the impact marker",()=>{const o={mode:"impact",points:[{id:"impact",clipProgress:.35,sourceProgress:.55}]};t(m(o,.35)).toBeCloseTo(.55),t(l(o,.55)).toBeCloseTo(.35),t(m(o,.7)).toBeGreaterThan(.7)}),o("advanced retiming sanitization stays monotonic and bounded",()=>{const o=p({mode:"advanced",points:[{id:"a",clipProgress:.9,sourceProgress:.2},{id:"b",clipProgress:-4,sourceProgress:.8},{id:"c",clipProgress:.9,sourceProgress:2},{id:"d",clipProgress:.4,sourceProgress:.1}]});t(o.points).toHaveLength(3);for(let e=1;e<o.points.length;e++)t(o.points[e].clipProgress).toBeGreaterThan(o.points[e-1].clipProgress),t(o.points[e].sourceProgress).toBeGreaterThan(o.points[e-1].sourceProgress);t(o.points[0].clipProgress).toBeGreaterThan(0),t(o.points[o.points.length-1].sourceProgress).toBeLessThan(1)}),o("retimed clip baking preserves trim boundary poses and remaps track times to clip duration",()=>{const o=new e.AnimationClip("trim-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,10,20])]),i=a("anim",0,1);i.clipStartOffset=.5,i.clipEndOffset=.25,i.retiming={mode:"advanced",points:[]};const n=d(o,i),s=n.tracks[0];t(n.duration).toBeCloseTo(1),t(g(s.times)).toEqual([0,.4,1]),t(g(s.values)).toEqual([5,10,17.5])}),o("root motion track baking stays aligned with the retimed visual clip",()=>{const o=new e.AnimationClip("root-motion-test",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0])]),i=a("anim",0,1);i.retiming={mode:"impact",points:[{id:"impact",clipProgress:.25,sourceProgress:.5}]};const n=d(o,i),s=n.tracks[0],c=r.fromClip(n,!1);t(g(s.times)).toEqual([0,.25,1]),t(g(c.motionTrack.times)).toEqual([0,.25,1]),t(g(c.motionTrack.values)).toEqual([0,0,0,1,0,0,2,0,0])}),o("sequence player keeps uniform clips on the existing playback path",()=>{const o=new u,i=c(),n=a("anim",0,1);n.playbackRate=1.75,n.clipStartOffset=.25,i.clips.push(n);const s=new e.AnimationClip("uniform-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const r=new e.Object3D,l={target:r,mixer:new e.AnimationMixer(r),activeActions:new Map};o.evaluateAnimationSubTrack(i,l,.1);const m=l.activeActions.get(`${i.id}-${n.id}`);t(m).toBeDefined(),t(m.getClip()).toBe(s),t(m.timeScale).toBeCloseTo(1.75),t(m.time).toBeCloseTo(.25)}),o("sequence player uses baked retimed clips and still wires root motion through the action",()=>{const o=new u,i=c(),n=a("anim",0,1.5);n.rootMotion=!0,n.playbackRate=2,n.retiming={mode:"impact",points:[{id:"impact",clipProgress:.3,sourceProgress:.55}]},i.clips.push(n);const s=new e.AnimationClip("retimed-root",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0]),new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const l=new h,m=new f(l.object),p=new A;l.charAnim=m,l.movement=p;const d={target:l,activeActions:new Map};o.evaluateAnimationSubTrack(i,d,.1);const g=d.activeActions.get(`${i.id}-${n.id}`);t(g).toBeDefined(),t(g.timeScale).toBeCloseTo(1),t(g.getClip()).toBeInstanceOf(r),t(g.getClip().duration).toBeCloseTo(1.5),t(p.lastRootMotionAction).toBe(g),t(o.retimedAnimationClips.size).toBe(1)});class h extends i{constructor(){super(...arguments),this.charAnim=null,this.movement=null}getComponent(t){return t===n?this.charAnim:t===s?this.movement:void 0}}class f{constructor(t){this.fullBodyAction=null,this.mixer=new e.AnimationMixer(t)}play(t,o={}){this.fullBodyAction=this.mixer.clipAction(t),this.fullBodyAction.setLoop(e.LoopOnce,1),this.fullBodyAction.clampWhenFinished=!0,this.fullBodyAction.timeScale=o.timeScale??1,null!=o.offset&&(this.fullBodyAction.time=o.offset),this.fullBodyAction.play()}beginExternalAnimationControl(t,o={}){return this.play(t,o),this.getFullBodyAction()}getFullBodyAction(){if(null==this.fullBodyAction)throw new Error("No full body action is active");return this.fullBodyAction}getMixer(){return this.mixer}beginExternalControl(){}endExternalControl(){}stopSequenceAnimation(){}}class A{constructor(){this.lastRootMotionAction=null}setRootMotionAction(t){this.lastRootMotionAction=t}}/*
1
+ import{expect as t,test as o}from"vitest";import*as e from"three";import{BaseActor as i}from"../gameplay/actors/actor.js";import{CharacterAnimationComponent as n,OldCharacterMovementComponent as s}from"../gameplay/actors/index.js";import{RootMotionClip as r}from"../gameplay/animation/root-motion.js";import{createAnimationClipInstance as a,createAnimationSubTrack as c,evaluateAnimationClipOutputProgress as l,evaluateAnimationClipSourceProgress as m,normalizeAnimationClipRetiming as p,SequencePlayer as u,buildRetimedAnimationClip as d}from"../effects/sequence/index.js";function g(t){return Array.from(t).map(t=>Math.round(1e3*t)/1e3)}o("uniform retiming mapping stays linear",()=>{t(m(void 0,.25)).toBeCloseTo(.25),t(l(void 0,.75)).toBeCloseTo(.75),t(m({mode:"uniform",points:[]},.6)).toBeCloseTo(.6)}),o("impact retiming redistributes time around the impact marker",()=>{const o={mode:"impact",points:[{id:"impact",clipProgress:.35,sourceProgress:.55}]};t(m(o,.35)).toBeCloseTo(.55),t(l(o,.55)).toBeCloseTo(.35),t(m(o,.7)).toBeGreaterThan(.7)}),o("advanced retiming sanitization stays monotonic and bounded",()=>{const o=p({mode:"advanced",points:[{id:"a",clipProgress:.9,sourceProgress:.2},{id:"b",clipProgress:-4,sourceProgress:.8},{id:"c",clipProgress:.9,sourceProgress:2},{id:"d",clipProgress:.4,sourceProgress:.1}]});t(o.points).toHaveLength(3);for(let e=1;e<o.points.length;e++)t(o.points[e].clipProgress).toBeGreaterThan(o.points[e-1].clipProgress),t(o.points[e].sourceProgress).toBeGreaterThan(o.points[e-1].sourceProgress);t(o.points[0].clipProgress).toBeGreaterThan(0),t(o.points[o.points.length-1].sourceProgress).toBeLessThan(1)}),o("retimed clip baking preserves trim boundary poses and remaps track times to clip duration",()=>{const o=new e.AnimationClip("trim-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,10,20])]),i=a("anim",0,1);i.clipStartOffset=.5,i.clipEndOffset=.25,i.retiming={mode:"advanced",points:[]};const n=d(o,i),s=n.tracks[0];t(n.duration).toBeCloseTo(1),t(g(s.times)).toEqual([0,.4,1]),t(g(s.values)).toEqual([5,10,17.5])}),o("root motion track baking stays aligned with the retimed visual clip",()=>{const o=new e.AnimationClip("root-motion-test",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0])]),i=a("anim",0,1);i.retiming={mode:"impact",points:[{id:"impact",clipProgress:.25,sourceProgress:.5}]};const n=d(o,i),s=n.tracks[0],c=r.fromClip(n,!1);t(g(s.times)).toEqual([0,.25,1]),t(g(c.motionTrack.times)).toEqual([0,.25,1]),t(g(c.motionTrack.values)).toEqual([0,0,0,1,0,0,2,0,0])}),o("sequence player keeps uniform clips on the existing playback path",()=>{const o=new u,i=c(),n=a("anim",0,1);n.playbackRate=1.75,n.clipStartOffset=.25,i.clips.push(n);const s=new e.AnimationClip("uniform-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const r=new e.Object3D,l={target:r,mixer:new e.AnimationMixer(r),activeActions:new Map};o.evaluateAnimationSubTrack(i,l,.1);const m=l.activeActions.get(`${i.id}-${n.id}`);t(m).toBeDefined(),t(m.getClip()).toBe(s),t(m.timeScale).toBeCloseTo(1.75),t(m.time).toBeCloseTo(.25)}),o("sequence player uses baked retimed clips and still wires root motion through the action",()=>{const o=new u,i=c(),n=a("anim",0,1.5);n.rootMotion=!0,n.playbackRate=2,n.retiming={mode:"impact",points:[{id:"impact",clipProgress:.3,sourceProgress:.55}]},i.clips.push(n);const s=new e.AnimationClip("retimed-root",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0]),new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const l=new h,m=new f(l.object),p=new A;l.charAnim=m,l.movement=p;const d={target:l,activeActions:new Map};o.evaluateAnimationSubTrack(i,d,.1);const g=d.activeActions.get(`${i.id}-${n.id}`);t(g).toBeDefined(),t(g.timeScale).toBeCloseTo(1),t(g.getClip()).toBeInstanceOf(r),t(g.getClip().duration).toBeCloseTo(1.5),t(p.lastRootMotionAction).toBe(g),t(o.retimedAnimationClips.size).toBe(1)});class h extends i{constructor(){super(...arguments),this.charAnim=null,this.movement=null}getComponent(t){return t===n?this.charAnim:t===s?this.movement:void 0}}class f{constructor(t){this.fullBodyAction=null,this.mixer=new e.AnimationMixer(t)}play(t,o={}){this.fullBodyAction=this.mixer.clipAction(t),this.fullBodyAction.setLoop(e.LoopOnce,1),this.fullBodyAction.clampWhenFinished=!0,this.fullBodyAction.timeScale=o.timeScale??1,null!=o.offset&&(this.fullBodyAction.time=o.offset),this.fullBodyAction.play()}beginExternalAnimationControl(t,o={}){return this.play(t,o),this.getFullBodyAction()}getFullBodyAction(){if(null==this.fullBodyAction)throw new Error("No full body action is active");return this.fullBodyAction}getMixer(){return this.mixer}beginExternalControl(){}endExternalControl(){}stopSequenceAnimation(){}}class A{constructor(){this.lastRootMotionAction=null}setRootMotionAction(t){this.lastRootMotionAction=t}}/*
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 e,expect as s,it as a,vi as t}from"vitest";import*as r from"three";import{createMaterialSubTrack as o,createPostProcessClipInstance as n,createPostProcessTrack as i,createPropertyKeyframe as c,createSequenceData as l}from"../effects/sequence/sequence-data.js";import{createDefaultPostProcessShaderGraphDocument as p,shaderGraphPostProcessWeightUniformName as d}from"../shader/graph/index.js";import{shaderParameterUniformName as u}from"../shader/parameter.js";import{UniformFloatNode as f}from"../shader-nodes/index.js";import{SerializedParamType as m}from"../scene/model.js";async function h(e,s){const{SequencePlayer:a}=await import("../effects/sequence/sequence-player.js"),t=new a;return t.setWorld({scene:new r.Scene}),t.setViewController(e),t.setAssetLoader(s),t}function y(e,s,a){const t=l();t.duration=Math.max(2,s+a+.25);const r=i(),o=n(s,a);return o.shaderGraphAssetId=e,r.clips.push(o),t.tracks.push(r),{sequence:t,track:r,clip:o}}function v(e,s={}){return{clearCacheById:t.fn(),getShaderGraphByAssetId:t.fn().mockResolvedValue(e),prepareShaderGraphParameters:t.fn().mockResolvedValue(s)}}function w(){const e=[],s=t.fn((s,a={})=>{const r={material:s,enabled:a.enabled??!0,priority:a.priority??0,stage:a.stage??"beforeOutput",dispose:t.fn()};return e.push(r),r});return{handles:e,addPostProcessEffect:s,clearCameraShakeContributionsForOwner:t.fn(),releaseCameraOverridesForOwner:t.fn(),pushCameraOverride:t.fn(),setCameraShakeContribution:t.fn()}}function C(e){return e.material.uniforms[d].value}async function g(e=6){for(let s=0;s<e;s++)await Promise.resolve()}t.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),t.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),t.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),t.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),t.mock("../effects/vfx/vfx-service.js",()=>({VfxService:class{}})),t.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,s)=>(s in e||(e[s]=("string"!=typeof s||!s.startsWith("is"))&&t.fn()),e[s]),set:(e,s,a)=>(e[s]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),e("sequence post-process tracks",()=>{a("registers active post-process clips with the view controller",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{clip:r,sequence:o}=y("graph-asset",0,1);t.load(o),t.play(),await g(),s(a.addPostProcessEffect).toHaveBeenCalledTimes(1),s(a.addPostProcessEffect).toHaveBeenCalledWith(s.anything(),s.objectContaining({stage:"beforeOutput",priority:0,enabled:!0})),s(a.handles[0].material.uniforms[d]).toBeTruthy(),s(r.shaderGraphAssetId).toBe("graph-asset")}),a("updates post-process blend weight from clip fades",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{clip:r,sequence:o}=y("graph-asset",0,2);r.fadeInDuration=.5,r.fadeOutDuration=.5,t.load(o),t.play(),await g(),t.seek(.25),s(C(a.handles[0])).toBeCloseTo(.5),t.seek(1),s(C(a.handles[0])).toBeCloseTo(1),t.seek(1.75),s(C(a.handles[0])).toBeCloseTo(.5)}),a("animates shader graph parameters on active post-process clips",async()=>{const e=(a="intensity",{...p(),parameters:[{id:a,name:a,type:"float",defaultValue:0}],nodes:[{id:a,kind:"input.parameter",label:"Parameter",params:{parameter:a}}],edges:[],outputs:{color:{input:{nodeId:a,port:"value"}}}});var a;const t=u("intensity"),r=w(),n=await h(r,v(e,{intensity:new f(t,0,void 0,!1)})),{sequence:i,track:l}=y("graph-asset",0,1),d=o({materialMatchMode:"parameterName",materialAssetId:null,parameterName:"intensity",uniformName:t,uniformType:m.FloatNode,valueType:m.Number});d.keyframes.push(c(0,{type:m.Number,value:0}),c(1,{type:m.Number,value:1})),l.subTracks.push(d),n.load(i),n.play(),await g(),n.seek(.5),s(r.handles[0].material.uniforms[t].value).toBeCloseTo(.5)}),a("disposes post-process handles when clips exit and when stopped",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{sequence:r}=y("graph-asset",0,1);t.load(r),t.play(),await g();const o=a.handles[0];t.seek(1.1),s(o.dispose).toHaveBeenCalledTimes(1),t.seek(.1),await g();const n=a.handles[1];t.stop(),s(n.dispose).toHaveBeenCalledTimes(1)}),a("does not register stale async post-process loads after stop",async()=>{const e=function(){let e;return{promise:new Promise(s=>{e=s}),resolve:e}}(),a=w(),t=await h(a,v(e.promise)),{sequence:r}=y("graph-asset",0,1);t.load(r),t.play(),t.stop(),e.resolve(p()),await g(),s(a.addPostProcessEffect).not.toHaveBeenCalled()})});/*
1
+ import{describe as e,expect as s,it as a,vi as t}from"vitest";import*as r from"three";import{createMaterialSubTrack as o,createPostProcessClipInstance as n,createPostProcessTrack as i,createPropertyKeyframe as c,createSequenceData as l}from"../effects/sequence/sequence-data.js";import{createDefaultPostProcessShaderGraphDocument as p,shaderGraphPostProcessWeightUniformName as d}from"../shader/graph/index.js";import{shaderParameterUniformName as u}from"../shader/parameter.js";import{UniformFloatNode as f}from"../shader-nodes/index.js";import{SerializedParamType as m}from"../scene/model.js";async function h(e,s){const{SequencePlayer:a}=await import("../effects/sequence/sequence-player.js"),t=new a;return t.setWorld({scene:new r.Scene}),t.setViewController(e),t.setAssetLoader(s),t}function y(e,s,a){const t=l();t.duration=Math.max(2,s+a+.25);const r=i(),o=n(s,a);return o.shaderGraphAssetId=e,r.clips.push(o),t.tracks.push(r),{sequence:t,track:r,clip:o}}function v(e,s={}){return{clearCacheById:t.fn(),getShaderGraphByAssetId:t.fn().mockResolvedValue(e),prepareShaderGraphParameters:t.fn().mockResolvedValue(s)}}function w(){const e=[],s=t.fn((s,a={})=>{const r={material:s,enabled:a.enabled??!0,priority:a.priority??0,stage:a.stage??"beforeOutput",dispose:t.fn()};return e.push(r),r});return{handles:e,addPostProcessEffect:s,clearCameraShakeContributionsForOwner:t.fn(),releaseCameraOverridesForOwner:t.fn(),pushCameraOverride:t.fn(),setCameraShakeContribution:t.fn()}}function C(e){return e.material.uniforms[d].value}async function g(e=6){for(let s=0;s<e;s++)await Promise.resolve()}t.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),t.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),t.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),t.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),t.mock("../effects/vfx/vfx-service.js",()=>({VfxService:class{}})),t.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,s)=>(s in e||(e[s]=("string"!=typeof s||!s.startsWith("is"))&&t.fn()),e[s]),set:(e,s,a)=>(e[s]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),e("sequence post-process tracks",()=>{a("registers active post-process clips with the view controller",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{clip:r,sequence:o}=y("graph-asset",0,1);t.load(o),t.play(),await g(),s(a.addPostProcessEffect).toHaveBeenCalledTimes(1),s(a.addPostProcessEffect).toHaveBeenCalledWith(s.anything(),s.objectContaining({stage:"beforeOutput",priority:0,enabled:!0})),s(a.handles[0].material.uniforms[d]).toBeTruthy(),s(r.shaderGraphAssetId).toBe("graph-asset")}),a("updates post-process blend weight from clip fades",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{clip:r,sequence:o}=y("graph-asset",0,2);r.fadeInDuration=.5,r.fadeOutDuration=.5,t.load(o),t.play(),await g(),t.seek(.25),s(C(a.handles[0])).toBeCloseTo(.5),t.seek(1),s(C(a.handles[0])).toBeCloseTo(1),t.seek(1.75),s(C(a.handles[0])).toBeCloseTo(.5)}),a("animates shader graph parameters on active post-process clips",async()=>{const e=(a="intensity",{...p(),parameters:[{id:a,name:a,type:"float",defaultValue:0}],nodes:[{id:a,kind:"input.parameter",params:{parameter:a}}],edges:[],outputs:{color:{input:{nodeId:a,port:"value"}}}});var a;const t=u("intensity"),r=w(),n=await h(r,v(e,{intensity:new f(t,0,void 0,!1)})),{sequence:i,track:l}=y("graph-asset",0,1),d=o({materialMatchMode:"parameterName",materialAssetId:null,parameterName:"intensity",uniformName:t,uniformType:m.FloatNode,valueType:m.Number});d.keyframes.push(c(0,{type:m.Number,value:0}),c(1,{type:m.Number,value:1})),l.subTracks.push(d),n.load(i),n.play(),await g(),n.seek(.5),s(r.handles[0].material.uniforms[t].value).toBeCloseTo(.5)}),a("disposes post-process handles when clips exit and when stopped",async()=>{const e=p(),a=w(),t=await h(a,v(e)),{sequence:r}=y("graph-asset",0,1);t.load(r),t.play(),await g();const o=a.handles[0];t.seek(1.1),s(o.dispose).toHaveBeenCalledTimes(1),t.seek(.1),await g();const n=a.handles[1];t.stop(),s(n.dispose).toHaveBeenCalledTimes(1)}),a("does not register stale async post-process loads after stop",async()=>{const e=function(){let e;return{promise:new Promise(s=>{e=s}),resolve:e}}(),a=w(),t=await h(a,v(e.promise)),{sequence:r}=y("graph-asset",0,1);t.load(r),t.play(),t.stop(),e.resolve(p()),await g(),s(a.addPostProcessEffect).not.toHaveBeenCalled()})});/*
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{expect as e,test as t,vi as o}from"vitest";import{DoubleSide as a,FrontSide as r,Texture as n}from"three";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&o.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("three/examples/jsm/Addons.js",async e=>({...await e(),ConvexHull:class{},SkeletonUtils:{clone:e=>e},RectAreaLightHelper:class{}})),o.mock("three-shader-graph",async e=>{const t=await e();return{...t,rgb:(e,o,a)=>null==o&&null==a&&!0===e?.isColor&&"function"==typeof e.getHexString?t.rgb("#"+e.getHexString()):t.rgb(e,o,a)}}),o.mock("../rendering.js",()=>({RenderingView:class{constructor(){this._id="test",this.csm={setupMaterial:o.fn()}}getEnvTexture(e){return e}},setRenderingPaused:o.fn()}));import{materialFromAsset as s,prepareCustomParamsFromShaderGraph as d,prepareShaderGraphParameters as p}from"../scene/materializer.js";import{SerializedParamType as u}from"../scene/model.js";import{AssetsProvider as l}from"../scene/assets-provider.js";import{AssetResourceLoader as i}from"../scene/asset-resource-loader.js";import{buildShaderGraphMaterial as m,buildShaderGraphPostProcessMaterial as c,compileShaderGraph as v,compileShaderGraphPreview as f,createDefaultPostProcessShaderGraphDocument as h,createDefaultShaderGraphDocument as y,createDefaultVfxShaderGraphDocument as g,shaderGraphPostProcessWeightUniformName as I,shaderGraphParametersToPropertyParameters as b}from"../shader/graph/index.js";import{NodeShaderMaterial as x,textureSampler2d as B,UniformFloatNode as k,UniformSampler2dArraySlice as T}from"../shader-nodes/index.js";import{StandardShader as w}from"../shader/builtin/standard-shader.js";import{sceneMapUniformName as j}from"../shader-nodes/scene-sample.js";function M(e,t,o){return{version:1,target:"surface",parameters:[],nodes:e,edges:t,outputs:o}}function C(e,t){return{id:e,name:e,type:"material",material:{type:"shaderGraph",side:r,params:{},shaderParams:{},shaderGraph:t}}}t("graph validation rejects missing nodes, invalid ports, and incompatible edges",()=>{const t=y();t.outputs.color={input:{nodeId:"missing",port:"value"}},e(()=>v(t)).toThrow(/Missing shader graph node/);const o=y();o.outputs.color={input:{nodeId:"base-color",port:"missing"}},e(()=>v(o)).toThrow(/does not have output/);const a=M([{id:"a",kind:"constant.vec2",params:{value:[1,1]}},{id:"b",kind:"constant.vec3",params:{value:[1,1,1]}},{id:"add",kind:"math.add"}],[{from:{nodeId:"a",port:"value"},to:{nodeId:"add",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"add",port:"b"}}],{color:{input:{nodeId:"add",port:"value"}}});e(()=>v(a)).toThrow(/Cannot add vec2 and vec3/)}),t("surface graph compiles to NodeShaderOutput",()=>{const t=v(y("surface"));e(t.output.color).toBeTruthy(),e(t.output.transparent).toBe(!1)}),t("inline literal inputs compile without separate constant nodes",()=>{const t=M([{id:"uv",kind:"builtin.uv"},{id:"scale-uv",kind:"math.multiply",inputs:{b:{value:2,valueType:"float"}}},{id:"rotate",kind:"uv.rotate",inputs:{angle:{value:.5,valueType:"float"},center:{value:[.25,.75],valueType:"vec2"}}},{id:"translate",kind:"transform.translate",inputs:{offset:{value:[0,1,0],valueType:"vec3"}}}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"scale-uv",port:"a"}},{from:{nodeId:"scale-uv",port:"value"},to:{nodeId:"rotate",port:"uv"}}],{color:{input:{nodeId:"rotate",port:"value"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.nodes.get("scale-uv")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("rotate")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("translate")?.outputs.value.type).toBe("mat4"),e(o.output.color).toBeTruthy(),e(o.output.transform).toBeTruthy()}),t("sprite graph compiles with sprite-compatible material output",()=>{const t=y("sprite"),o=v(t);e(o.output.color).toBeTruthy(),e(o.output.transparent).toBe(!0)}),t("default VFX shader graphs compile mapped targets and build non-sprite materials",()=>{for(const t of["sprite","surface","decal","trail"]){const o=g(t);if(e(v(o).output.color).toBeTruthy(),"sprite"===t)continue;const a=m(o,{trailBillboard:"trail"===t});e(a.isMaterial).toBe(!0)}}),t("graph material options apply to supported material targets",()=>{const t=y("surface");t.materialOptions={side:"double",transparent:!0};const o=m(t);e(o.side).toBe(a),e(o.transparent).toBe(!0);const r=y("decal");r.materialOptions={transparent:!0};const n=m(r);e(n.transparent).toBe(!0)}),t("standard graph compiles extended material outputs and vertex transform",()=>{const t=M([{id:"metalness",kind:"constant.float",params:{value:.8}},{id:"ao",kind:"constant.float",params:{value:.5}},{id:"sheen",kind:"constant.vec3",params:{value:[.1,.2,.3]}},{id:"offset",kind:"constant.vec3",params:{value:[0,1,0]}},{id:"translate",kind:"transform.translate"}],[{from:{nodeId:"offset",port:"value"},to:{nodeId:"translate",port:"offset"}}],{metalness:{input:{nodeId:"metalness",port:"value"}},ambientOcclusion:{input:{nodeId:"ao",port:"value"}},sheenColor:{input:{nodeId:"sheen",port:"value"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.output.metalness).toBeTruthy(),e(o.output.ambientOcclusion).toBeTruthy(),e(o.output.sheenColor).toBeTruthy(),e(o.output.transform).toBeTruthy();const a=m(t);e(a.outputMetalness).toBeTruthy(),e(a.outputAmbientOcclusion).toBeTruthy(),e(a.outputTransform).toBeTruthy()}),t("math multiply composes transform matrices",()=>{const t=M([{id:"translate",kind:"transform.translate",inputs:{offset:{value:[0,1,0],valueType:"vec3"}}},{id:"scale",kind:"transform.scale",inputs:{scale:{value:[2,2,2],valueType:"vec3"}}},{id:"compose",kind:"math.multiply"}],[{from:{nodeId:"translate",port:"value"},to:{nodeId:"compose",port:"a"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"compose",port:"b"}}],{transform:{input:{nodeId:"compose",port:"value"}}}),o=v(t);e(o.nodes.get("compose")?.outputs.value.type).toBe("mat4"),e(o.output.transform).toBeTruthy(),e(m(t).outputTransform).toBeTruthy()}),t("matrix input nodes compile as mat4 uniforms",()=>{for(const t of["builtin.viewMatrix","builtin.projectionMatrix","builtin.modelViewMatrix"]){const o=M([{id:"matrix",kind:t}],[],{transform:{input:{nodeId:"matrix",port:"value"}}}),a=v(o);e(a.nodes.get("matrix")?.outputs.value.type).toBe("mat4"),e(a.output.transform).toBeTruthy()}}),t("compose vec4 node compiles from float components",()=>{const t=M([{id:"compose",kind:"compose.vec4",inputs:{x:{value:.1},y:{value:.2},z:{value:.3},w:{value:1}}}],[],{color:{input:{nodeId:"compose",port:"value"}}}),o=v(t);e(o.nodes.get("compose")?.outputs.value.type).toBe("vec4"),e(o.output.color).toBeTruthy()}),t("atan2 node compiles from y and x inputs",()=>{const t=M([{id:"atan2",kind:"math.atan2",inputs:{y:{value:1},x:{value:0}}}],[],{color:{input:{nodeId:"atan2",port:"value"}}}),o=v(t);e(o.nodes.get("atan2")?.outputs.value.type).toBe("float"),e(o.output.color).toBeTruthy()}),t("built-in attribute nodes compile for fragment and transform outputs",()=>{const t=M([{id:"color",kind:"builtin.color"},{id:"normal",kind:"builtin.normal"},{id:"position",kind:"builtin.position"},{id:"worldPosition",kind:"builtin.worldPosition"},{id:"objectPosition",kind:"builtin.objectPosition"},{id:"translate",kind:"transform.translate"}],[{from:{nodeId:"objectPosition",port:"value"},to:{nodeId:"translate",port:"offset"}}],{color:{input:{nodeId:"color",port:"value"}},normal:{input:{nodeId:"normal",port:"value"}},roughness:{input:{nodeId:"position",port:"x"}},metalness:{input:{nodeId:"worldPosition",port:"x"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.nodes.get("color")?.outputs.value.type).toBe("rgba"),e(o.nodes.get("normal")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("position")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("worldPosition")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("objectPosition")?.outputs.value.type).toBe("vec3"),e(o.output.color).toBeTruthy(),e(o.output.normal).toBeTruthy(),e(o.output.transform).toBeTruthy(),e(m(t).outputTransform).toBeTruthy()}),t("painted layer mix node compiles color layers and builds a material",()=>{const t=M([{id:"base",kind:"constant.rgba",params:{value:"#224422",alpha:1}},{id:"grass",kind:"constant.rgba",params:{value:"#55aa33",alpha:1}},{id:"stone",kind:"constant.rgba",params:{value:"#777777",alpha:1}},{id:"mix",kind:"layer.mixPainted",params:{mode:"soft",enableNoise:!0,softness:.25,noiseScale:.2,noiseAmount:.4}}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"grass",port:"value"},to:{nodeId:"mix",port:"layer1"}},{from:{nodeId:"stone",port:"value"},to:{nodeId:"mix",port:"layer3"}}],{color:{input:{nodeId:"mix",port:"value"}}}),o=v(t);e(o.nodes.get("mix")?.outputs.value.type).toBe("rgba"),e(o.output.color).toBeTruthy(),e(m(t).isMaterial).toBe(!0)}),t("painted layer mix node compiles sparse float layers in hard and soft modes",()=>{for(const t of["hard","soft"]){const o=M([{id:"base",kind:"constant.float",params:{value:.8}},{id:"mud",kind:"constant.float",params:{value:1}},{id:"wet",kind:"constant.float",params:{value:.25}},{id:"mix",kind:"layer.mixPainted",params:{mode:t,enableNoise:!1,softness:.3,noiseScale:.1,noiseAmount:.5}}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"mud",port:"value"},to:{nodeId:"mix",port:"layer2"}},{from:{nodeId:"wet",port:"value"},to:{nodeId:"mix",port:"layer8"}}],{roughness:{input:{nodeId:"mix",port:"value"}}}),a=v(o);e(a.nodes.get("mix")?.outputs.value.type).toBe("float"),e(a.output.roughness).toBeTruthy()}}),t("painted layer mix node rejects incompatible layer types",()=>{const t=M([{id:"base",kind:"constant.vec2",params:{value:[0,1]}},{id:"layer",kind:"constant.vec3",params:{value:[1,0,0]}},{id:"mix",kind:"layer.mixPainted"}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"layer",port:"value"},to:{nodeId:"mix",port:"layer1"}}],{color:{input:{nodeId:"mix",port:"value"}}});e(()=>v(t)).toThrow(/Cannot mix vec2 and vec3/)}),t("additional math, uv, shape, noise, and effect nodes compile",()=>{const t=M([{id:"uv",kind:"builtin.uv"},{id:"angle",kind:"constant.float",params:{value:.5}},{id:"scale",kind:"constant.float",params:{value:4}},{id:"half",kind:"constant.float",params:{value:.5}},{id:"axis",kind:"constant.vec3",params:{value:[0,1,0]}},{id:"rotatedUv",kind:"uv.rotate"},{id:"twirl",kind:"uv.twirl"},{id:"bulge",kind:"uv.bulge"},{id:"radial",kind:"uv.radial"},{id:"flipbook",kind:"uv.flipbook"},{id:"shape",kind:"shape.roundedRectangle"},{id:"noise",kind:"noise.simplex"},{id:"voronoi",kind:"noise.voronoi2d"},{id:"fresnel",kind:"effect.fresnel"},{id:"sin",kind:"math.sin"},{id:"pow",kind:"math.pow"},{id:"mix",kind:"math.mix"},{id:"rotateAxis",kind:"transform.rotateAxis"}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"rotatedUv",port:"uv"}},{from:{nodeId:"angle",port:"value"},to:{nodeId:"rotatedUv",port:"angle"}},{from:{nodeId:"rotatedUv",port:"value"},to:{nodeId:"twirl",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"twirl",port:"strength"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"bulge",port:"uv"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"bulge",port:"power"}},{from:{nodeId:"bulge",port:"value"},to:{nodeId:"radial",port:"uv"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"flipbook",port:"uv"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"shape",port:"uv"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"width"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"height"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"radius"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"noise",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"noise",port:"scale"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"voronoi",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"voronoi",port:"scale"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"fresnel",port:"power"}},{from:{nodeId:"radial",port:"angle"},to:{nodeId:"sin",port:"value"}},{from:{nodeId:"noise",port:"value"},to:{nodeId:"pow",port:"a"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"pow",port:"b"}},{from:{nodeId:"shape",port:"value"},to:{nodeId:"mix",port:"a"}},{from:{nodeId:"voronoi",port:"value"},to:{nodeId:"mix",port:"b"}},{from:{nodeId:"fresnel",port:"value"},to:{nodeId:"mix",port:"t"}},{from:{nodeId:"axis",port:"value"},to:{nodeId:"rotateAxis",port:"axis"}},{from:{nodeId:"angle",port:"value"},to:{nodeId:"rotateAxis",port:"angle"}}],{color:{input:{nodeId:"mix",port:"value"}},opacity:{input:{nodeId:"sin",port:"value"}},anisotropyDirection:{input:{nodeId:"flipbook",port:"value"}},transform:{input:{nodeId:"rotateAxis",port:"value"}}}),o=v(t);e(o.nodes.get("mix")?.outputs.value.type).toBe("float"),e(o.nodes.get("twirl")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("bulge")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("radial")?.outputs.coords.type).toBe("vec2"),e(o.nodes.get("flipbook")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("rotateAxis")?.outputs.value.type).toBe("mat4"),e(o.output.color).toBeTruthy(),e(o.output.opacity).toBeTruthy(),e(o.output.transform).toBeTruthy()}),t("slider input node outputs a clamped float value",()=>{const t=M([{id:"slider",kind:"input.slider",params:{value:1.25}}],[],{roughness:{input:{nodeId:"slider",port:"value"}}}),o=v(t);e(o.nodes.get("slider")?.outputs.value.type).toBe("float"),e(o.output.roughness).toBeTruthy()}),t("comparison and boolean graph nodes compile to boolean outputs",()=>{const t=M([{id:"a",kind:"constant.float",params:{value:.75}},{id:"b",kind:"constant.float",params:{value:.5}},{id:"greater",kind:"compare.greaterThan"},{id:"lessEqual",kind:"compare.lessThanOrEqual"},{id:"not",kind:"boolean.not"},{id:"and",kind:"boolean.and"},{id:"or",kind:"boolean.or"}],[{from:{nodeId:"a",port:"value"},to:{nodeId:"greater",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"greater",port:"b"}},{from:{nodeId:"a",port:"value"},to:{nodeId:"lessEqual",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"lessEqual",port:"b"}},{from:{nodeId:"lessEqual",port:"value"},to:{nodeId:"not",port:"value"}},{from:{nodeId:"greater",port:"value"},to:{nodeId:"and",port:"a"}},{from:{nodeId:"not",port:"value"},to:{nodeId:"and",port:"b"}},{from:{nodeId:"and",port:"value"},to:{nodeId:"or",port:"a"}},{from:{nodeId:"lessEqual",port:"value"},to:{nodeId:"or",port:"b"}}],{discard:{input:{nodeId:"or",port:"value"}}}),o=v(t);e(o.nodes.get("greater")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("lessEqual")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("not")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("and")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("or")?.outputs.value.type).toBe("boolean"),e(o.output.discard).toBeTruthy()}),t("split component node exposes vector and color channels as separate outputs",()=>{const t=M([{id:"vec",kind:"constant.vec3",params:{value:[.2,.4,.6]}},{id:"color",kind:"constant.rgba",params:{value:"#336699",alpha:.75}},{id:"splitVec",kind:"decompose.split"},{id:"splitColor",kind:"decompose.split"},{id:"sum",kind:"math.add"}],[{from:{nodeId:"vec",port:"value"},to:{nodeId:"splitVec",port:"value"}},{from:{nodeId:"color",port:"value"},to:{nodeId:"splitColor",port:"value"}},{from:{nodeId:"splitVec",port:"z"},to:{nodeId:"sum",port:"a"}},{from:{nodeId:"splitColor",port:"a"},to:{nodeId:"sum",port:"b"}}],{roughness:{input:{nodeId:"sum",port:"value"}}}),o=v(t);e(Object.keys(o.nodes.get("splitVec")?.outputs??{})).toEqual(["x","y","z"]),e(Object.keys(o.nodes.get("splitColor")?.outputs??{})).toEqual(["r","g","b","a","x","y","z","w"]),e(o.nodes.get("splitVec")?.outputs.z.type).toBe("float"),e(o.nodes.get("splitColor")?.outputs.a.type).toBe("float"),e(o.output.roughness).toBeTruthy()}),t("color gradient node compiles editable stops to a sampled color",()=>{const t=M([{id:"threshold",kind:"constant.float",params:{value:.75}},{id:"gradient",kind:"color.gradient",params:{stops:[{position:0,color:"#000000",alpha:1},{position:.5,color:"#ff0000",alpha:1},{position:1,color:"#ffffff",alpha:1}]}}],[{from:{nodeId:"threshold",port:"value"},to:{nodeId:"gradient",port:"t"}}],{color:{input:{nodeId:"gradient",port:"value"}}}),o=v(t);e(o.nodes.get("gradient")?.outputs.value.type).toBe("rgba"),e(o.output.color).toBeTruthy(),e(f(t,"gradient").color).toBeTruthy()}),t("curve node compiles editable curve to a sampled float",()=>{const t=M([{id:"t",kind:"constant.float",params:{value:.75}},{id:"curve",kind:"utility.curve",params:{curve:"InOutQuad"}}],[{from:{nodeId:"t",port:"value"},to:{nodeId:"curve",port:"t"}}],{roughness:{input:{nodeId:"curve",port:"value"}}}),o=v(t);e(o.nodes.get("curve")?.outputs.value.type).toBe("float"),e(o.output.roughness).toBeTruthy(),e(f(t,"curve").color).toBeTruthy()}),t("graph parameters convert to property params and custom defaults",()=>{const t=y();t.parameters.push({name:"opacity",type:"float",defaultValue:.5});const o=b(t);e(o.map(e=>e.name)).toEqual(["baseColor","opacity"]),e(o.every(e=>!0===e.options.optional)).toBe(!0);const a=d(t);e(a.baseColor).toMatchObject({type:u.RgbNode,value:"#ffffff",override:!1}),e(a.opacity).toMatchObject({type:u.FloatNode,value:.5,override:!1})}),t("existing shader graph param values keep overriding unless explicitly disabled",()=>{const t=y(),o=d(t,{baseColor:{type:u.RgbNode,value:"#ff0000"}});e(o.baseColor).toMatchObject({type:u.RgbNode,value:"#ff0000",override:!0});const a=d(t,{baseColor:{type:u.RgbNode,value:"#00ff00",override:!1}});e(a.baseColor).toMatchObject({type:u.RgbNode,value:"#ffffff",override:!1})}),t("texture graph parameter defaults serialize as sampler parameters",()=>{const t=M([],[],{});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=d(t);e(o.map).toMatchObject({type:u.Sampler2DNode,value:"texture-asset-id"})}),t("shader graph params keep graph-declared serialized types over previous string params",()=>{const t=y();t.parameters.push({name:"map",type:"texture",defaultValue:null});const o=d(t,{baseColor:{type:u.String,value:"#ff0000"},map:{type:u.String,value:"texture-asset-id"}});e(o.baseColor).toMatchObject({type:u.RgbNode,value:"#ff0000"}),e(o.map).toMatchObject({type:u.Sampler2DNode,value:"texture-asset-id"})}),t("shader graph texture params resolve asset ids before compile",async()=>{const t=M([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=await p({},t,new A([{id:"texture-asset-id",name:"Map",type:"texture",fileKey:"map.png",fileFormat:"png"}]),new O);e(o.map).toBeTruthy(),e(typeof o.map).not.toBe("string"),e(v(t,{parameters:o}).output.color).toBeTruthy()}),t("shader graph texture array params expose named slice uniforms",async()=>{const t=M([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-array-layer"});const o=await p({},t,new A([{id:"texture-array-layer",name:"Map Layer",type:"texture",fileKey:"map.png",fileFormat:"png",texture:{textureArrayFileKey:"textures/packed.ktx2",textureArrayLayer:5}}]),new P(new n,5)),a=m(t,{parameters:o});e(a.uniforms.pu_map_i?.value).toBe(5),e(a.fragmentShader).toContain("uniform float pu_map_i")}),t("standard shader height maps support texture array slices",()=>{const t=new w;t.heightMap=new T("heightMap",new n,new k("heightMap_i",3,void 0,!1)),t.map=B(new n),t.heightScale=.05;const o=t.build();e(o.fragmentShader).toContain("uniform sampler2DArray heightMap"),e(o.fragmentShader).toContain("uniform float heightMap_i"),e(o.fragmentShader).toContain("texture(heightMap, vec3(uv + vCurrOffset, heightMapLayer)).r")}),t("material graph asset reference and local embedded graph both build materials",async()=>{const t={id:"graph-asset",name:"Reusable Graph",type:"shaderGraph",shaderGraph:y()},o=C("referenced",{source:"asset",assetId:t.id}),a=C("local",{source:"local",graph:y()}),r=new A([t,o,a]),n=new i;e((await s(o,null,r,n,[],!1)).isMaterial).toBe(!0),e((await s(a,null,r,n,[],!1)).isMaterial).toBe(!0)}),t("selected-node preview compiles scalar, vector, color, rgba, and boolean outputs",()=>{const t=[{id:"float",kind:"constant.float",params:{value:.25}},{id:"slider",kind:"input.slider",params:{value:.5}},{id:"vec2",kind:"constant.vec2",params:{value:[1,0]}},{id:"rgb",kind:"constant.vec3",params:{value:[1,.5,0]}},{id:"rgba",kind:"constant.rgba",params:{value:"#ff8800",alpha:.75}},{id:"boolean",kind:"constant.boolean",params:{value:!0}}];for(const o of t){const t=M([o],[],{color:{input:{nodeId:o.id,port:"value"}}});e(f(t,o.id).color).toBeTruthy()}}),t("post-process shader graph builds a fullscreen-safe material",()=>{const t=h(),o=c(t);e(o).toBeInstanceOf(x),e(o.depthWrite).toBe(!1),e(o.depthTest).toBe(!1),e(o.toneMapped).toBe(!1),e(o.transparent).toBe(!1),e(o.userData.isPostProcessShaderGraph).toBe(!0),e(o.uniforms[j]).toBeTruthy(),e(o.uniforms[I]).toBeTruthy()}),t("empty post-process shader graph defaults to identity scene color",()=>{const t=h();t.nodes=[],t.edges=[],t.outputs={};const o=v(t),a=c(t);e(o.output.color).toBeTruthy(),e(o.output.transparent).toBe(!1),e(a.uniforms[j]).toBeTruthy(),e(a.uniforms[I]).toBeTruthy()}),t("post-process color and opacity outputs expose blend weight uniform",()=>{const t=M([{id:"color",kind:"constant.rgba",params:{value:"#ff0044",alpha:1}},{id:"opacity",kind:"constant.float",params:{value:.35}}],[],{color:{input:{nodeId:"color",port:"value"}},opacity:{input:{nodeId:"opacity",port:"value"}},roughness:{value:.8}});t.target="postProcess";const o=v(t),a=c(t);e(o.output.color).toBeTruthy(),e(o.output.opacity).toBeTruthy(),e(o.output.roughness).toBeUndefined(),e(a.uniforms[I]).toBeTruthy()}),t("selected-node preview builds through post-process fullscreen path",()=>{const t=M([{id:"float",kind:"constant.float",params:{value:.25}}],[],{color:{input:{nodeId:"float",port:"value"}}});t.target="postProcess";const o=c(t,{previewNodeId:"float"});e(o).toBeInstanceOf(x),e(o.userData.isPostProcessShaderGraph).toBe(!0),e(o.uniforms[j]).toBeTruthy(),e(o.uniforms[I]).toBeTruthy()}),t("texture parameter nodes expose sampled value and channel outputs",()=>{const t=M([{id:"texture",kind:"input.parameter",params:{parameter:"albedo"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"albedo",type:"texture",defaultValue:null});const o=v(t,{parameters:{albedo:new n}}),a=o.nodes.get("texture")?.outputs??{};e(Object.keys(a)).toEqual(["value","rgb","r","g","b","a","sampler"]),e(a.value.type).toBe("rgba"),e(a.rgb.type).toBe("rgb"),e(a.r.type).toBe("float"),e(a.g.type).toBe("float"),e(a.b.type).toBe("float"),e(a.a.type).toBe("float"),e(a.sampler.type).toBe("sampler2d"),e(o.output.color).toBeTruthy()}),t("texture parameter nodes accept resolved sampler values",()=>{const t=M([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=v(t,{parameters:{map:B(new n)}});e(o.output.color).toBeTruthy()}),t("scene depth graph nodes expose sampled and fragment linear eye depth",()=>{const t=M([{id:"uv",kind:"builtin.screenUv"},{id:"sceneDepth",kind:"scene.sampleDepth"},{id:"fragmentDepth",kind:"scene.fragmentDepth"},{id:"depthFade",kind:"math.subtract"}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"sceneDepth",port:"uv"}},{from:{nodeId:"sceneDepth",port:"linearEyeDepth"},to:{nodeId:"depthFade",port:"a"}},{from:{nodeId:"fragmentDepth",port:"linearEyeDepth"},to:{nodeId:"depthFade",port:"b"}}],{opacity:{input:{nodeId:"depthFade",port:"value"}}}),o=v(t);e(o.nodes.get("sceneDepth")?.outputs.depth.type).toBe("float"),e(o.nodes.get("sceneDepth")?.outputs.linearEyeDepth.type).toBe("float"),e(o.nodes.get("fragmentDepth")?.outputs.linearEyeDepth.type).toBe("float"),e(o.output.opacity).toBeTruthy()}),t("unpack normal node compiles with various input combinations",()=>{const t=M([{id:"sample",kind:"constant.vec3",params:{value:[.5,.5,1]}},{id:"scale",kind:"constant.float",params:{value:1}},{id:"normal",kind:"builtin.normal"},{id:"toNormal",kind:"color.unpackNormal"}],[{from:{nodeId:"sample",port:"value"},to:{nodeId:"toNormal",port:"color"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"toNormal",port:"scale"}},{from:{nodeId:"normal",port:"value"},to:{nodeId:"toNormal",port:"normal"}}],{normal:{input:{nodeId:"toNormal",port:"value"}}}),o=v(t);e(o.nodes.get("toNormal")?.outputs.value.type).toBe("vec3"),e(o.output.normal).toBeTruthy()});class A extends l{constructor(e){super(),this.onCreate=null,this.onDelete=null,this.onUpdate=null,this.assets=new Map(e.map(e=>[e.id,e]))}async getAsset(e){return this.assets.get(e)}async getAssets(){return Array.from(this.assets.values())}}class O extends i{async getTexture(){return new n}async getTextureArray(){return{texture:null,layerIndex:null}}}class P extends O{constructor(e,t){super(),this.textureArray=e,this.layerIndex=t}async getTextureArray(){return{texture:this.textureArray,layerIndex:this.layerIndex}}}/*
1
+ import{expect as e,test as t,vi as o}from"vitest";import{DoubleSide as a,FrontSide as r,Texture as n}from"three";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&o.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("three/examples/jsm/Addons.js",async e=>({...await e(),ConvexHull:class{},SkeletonUtils:{clone:e=>e},RectAreaLightHelper:class{}})),o.mock("three-shader-graph",async e=>{const t=await e();return{...t,rgb:(e,o,a)=>null==o&&null==a&&!0===e?.isColor&&"function"==typeof e.getHexString?t.rgb("#"+e.getHexString()):t.rgb(e,o,a)}}),o.mock("../rendering.js",()=>({RenderingView:class{constructor(){this._id="test",this.csm={setupMaterial:o.fn()}}getEnvTexture(e){return e}},setRenderingPaused:o.fn()}));import{materialFromAsset as s,prepareCustomParamsFromShaderGraph as d,prepareShaderGraphParameters as p}from"../scene/materializer.js";import{SerializedParamType as u}from"../scene/model.js";import{AssetsProvider as l}from"../scene/assets-provider.js";import{AssetResourceLoader as i}from"../scene/asset-resource-loader.js";import{buildShaderGraphMaterial as m,buildShaderGraphPostProcessMaterial as c,compileShaderGraph as v,compileShaderGraphPreview as f,createDefaultPostProcessShaderGraphDocument as h,createDefaultShaderGraphDocument as y,createDefaultVfxShaderGraphDocument as g,shaderGraphPostProcessWeightUniformName as I,shaderGraphParametersToPropertyParameters as b}from"../shader/graph/index.js";import{NodeShaderMaterial as x,textureSampler2d as B,UniformFloatNode as k,UniformSampler2dArraySlice as T}from"../shader-nodes/index.js";import{StandardShader as w}from"../shader/builtin/standard-shader.js";import{sceneMapUniformName as C}from"../shader-nodes/scene-sample.js";function j(e,t,o){return{version:1,target:"surface",parameters:[],nodes:e,edges:t,outputs:o}}function M(e,t){return{id:e,name:e,type:"material",material:{type:"shaderGraph",side:r,params:{},shaderParams:{},shaderGraph:t}}}t("graph validation rejects missing nodes, invalid ports, and incompatible edges",()=>{const t=y();t.outputs.color={input:{nodeId:"missing",port:"value"}},e(()=>v(t)).toThrow(/Missing shader graph node/);const o=y();o.outputs.color={input:{nodeId:"base-color",port:"missing"}},e(()=>v(o)).toThrow(/does not have output/);const a=j([{id:"a",kind:"constant.vec2",params:{value:[1,1]}},{id:"b",kind:"constant.vec3",params:{value:[1,1,1]}},{id:"add",kind:"math.add"}],[{from:{nodeId:"a",port:"value"},to:{nodeId:"add",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"add",port:"b"}}],{color:{input:{nodeId:"add",port:"value"}}});e(()=>v(a)).toThrow(/Cannot add vec2 and vec3/)}),t("surface graph compiles to NodeShaderOutput",()=>{const t=v(y("surface"));e(t.output.color).toBeTruthy(),e(t.output.transparent).toBe(!1)}),t("inline literal inputs compile without separate constant nodes",()=>{const t=j([{id:"uv",kind:"builtin.uv"},{id:"scale-uv",kind:"math.multiply",inputs:{b:{value:2,valueType:"float"}}},{id:"rotate",kind:"uv.rotate",inputs:{angle:{value:.5,valueType:"float"},center:{value:[.25,.75],valueType:"vec2"}}},{id:"translate",kind:"transform.translate",inputs:{offset:{value:[0,1,0],valueType:"vec3"}}}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"scale-uv",port:"a"}},{from:{nodeId:"scale-uv",port:"value"},to:{nodeId:"rotate",port:"uv"}}],{color:{input:{nodeId:"rotate",port:"value"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.nodes.get("scale-uv")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("rotate")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("translate")?.outputs.value.type).toBe("mat4"),e(o.output.color).toBeTruthy(),e(o.output.transform).toBeTruthy()}),t("sprite graph compiles with sprite-compatible material output",()=>{const t=y("sprite"),o=v(t);e(o.output.color).toBeTruthy(),e(o.output.transparent).toBe(!0)}),t("default VFX shader graphs compile mapped targets and build non-sprite materials",()=>{for(const t of["sprite","surface","decal","trail"]){const o=g(t);if(e(v(o).output.color).toBeTruthy(),"sprite"===t)continue;const a=m(o,{trailBillboard:"trail"===t});e(a.isMaterial).toBe(!0)}}),t("graph material options apply to supported material targets",()=>{const t=y("surface");t.materialOptions={side:"double",transparent:!0};const o=m(t);e(o.side).toBe(a),e(o.transparent).toBe(!0);const r=y("decal");r.materialOptions={transparent:!0};const n=m(r);e(n.transparent).toBe(!0)}),t("standard graph compiles extended material outputs and vertex transform",()=>{const t=j([{id:"metalness",kind:"constant.float",params:{value:.8}},{id:"ao",kind:"constant.float",params:{value:.5}},{id:"sheen",kind:"constant.vec3",params:{value:[.1,.2,.3]}},{id:"offset",kind:"constant.vec3",params:{value:[0,1,0]}},{id:"translate",kind:"transform.translate"}],[{from:{nodeId:"offset",port:"value"},to:{nodeId:"translate",port:"offset"}}],{metalness:{input:{nodeId:"metalness",port:"value"}},ambientOcclusion:{input:{nodeId:"ao",port:"value"}},sheenColor:{input:{nodeId:"sheen",port:"value"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.output.metalness).toBeTruthy(),e(o.output.ambientOcclusion).toBeTruthy(),e(o.output.sheenColor).toBeTruthy(),e(o.output.transform).toBeTruthy();const a=m(t);e(a.outputMetalness).toBeTruthy(),e(a.outputAmbientOcclusion).toBeTruthy(),e(a.outputTransform).toBeTruthy()}),t("math multiply composes transform matrices",()=>{const t=j([{id:"translate",kind:"transform.translate",inputs:{offset:{value:[0,1,0],valueType:"vec3"}}},{id:"scale",kind:"transform.scale",inputs:{scale:{value:[2,2,2],valueType:"vec3"}}},{id:"compose",kind:"math.multiply"}],[{from:{nodeId:"translate",port:"value"},to:{nodeId:"compose",port:"a"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"compose",port:"b"}}],{transform:{input:{nodeId:"compose",port:"value"}}}),o=v(t);e(o.nodes.get("compose")?.outputs.value.type).toBe("mat4"),e(o.output.transform).toBeTruthy(),e(m(t).outputTransform).toBeTruthy()}),t("matrix input nodes compile as mat4 uniforms",()=>{for(const t of["builtin.viewMatrix","builtin.projectionMatrix","builtin.modelViewMatrix"]){const o=j([{id:"matrix",kind:t}],[],{transform:{input:{nodeId:"matrix",port:"value"}}}),a=v(o);e(a.nodes.get("matrix")?.outputs.value.type).toBe("mat4"),e(a.output.transform).toBeTruthy()}}),t("compose vec4 node compiles from float components",()=>{const t=j([{id:"compose",kind:"compose.vec4",inputs:{x:{value:.1},y:{value:.2},z:{value:.3},w:{value:1}}}],[],{color:{input:{nodeId:"compose",port:"value"}}}),o=v(t);e(o.nodes.get("compose")?.outputs.value.type).toBe("vec4"),e(o.output.color).toBeTruthy()}),t("atan2 node compiles from y and x inputs",()=>{const t=j([{id:"atan2",kind:"math.atan2",inputs:{y:{value:1},x:{value:0}}}],[],{color:{input:{nodeId:"atan2",port:"value"}}}),o=v(t);e(o.nodes.get("atan2")?.outputs.value.type).toBe("float"),e(o.output.color).toBeTruthy()}),t("built-in attribute nodes compile for fragment and transform outputs",()=>{const t=j([{id:"color",kind:"builtin.color"},{id:"normal",kind:"builtin.normal"},{id:"position",kind:"builtin.position"},{id:"worldPosition",kind:"builtin.worldPosition"},{id:"objectPosition",kind:"builtin.objectPosition"},{id:"translate",kind:"transform.translate"}],[{from:{nodeId:"objectPosition",port:"value"},to:{nodeId:"translate",port:"offset"}}],{color:{input:{nodeId:"color",port:"value"}},normal:{input:{nodeId:"normal",port:"value"}},roughness:{input:{nodeId:"position",port:"x"}},metalness:{input:{nodeId:"worldPosition",port:"x"}},transform:{input:{nodeId:"translate",port:"value"}}}),o=v(t);e(o.nodes.get("color")?.outputs.value.type).toBe("rgba"),e(o.nodes.get("normal")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("position")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("worldPosition")?.outputs.value.type).toBe("vec3"),e(o.nodes.get("objectPosition")?.outputs.value.type).toBe("vec3"),e(o.output.color).toBeTruthy(),e(o.output.normal).toBeTruthy(),e(o.output.transform).toBeTruthy(),e(m(t).outputTransform).toBeTruthy()}),t("painted layer mix node compiles color layers and builds a material",()=>{const t=j([{id:"base",kind:"constant.rgba",params:{value:"#224422",alpha:1}},{id:"grass",kind:"constant.rgba",params:{value:"#55aa33",alpha:1}},{id:"stone",kind:"constant.rgba",params:{value:"#777777",alpha:1}},{id:"mix",kind:"layer.mixPainted",params:{mode:"soft",enableNoise:!0,softness:.25,noiseScale:.2,noiseAmount:.4}}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"grass",port:"value"},to:{nodeId:"mix",port:"layer1"}},{from:{nodeId:"stone",port:"value"},to:{nodeId:"mix",port:"layer3"}}],{color:{input:{nodeId:"mix",port:"value"}}}),o=v(t);e(o.nodes.get("mix")?.outputs.value.type).toBe("rgba"),e(o.output.color).toBeTruthy(),e(m(t).isMaterial).toBe(!0)}),t("painted layer mix node compiles sparse float layers in hard and soft modes",()=>{for(const t of["hard","soft"]){const o=j([{id:"base",kind:"constant.float",params:{value:.8}},{id:"mud",kind:"constant.float",params:{value:1}},{id:"wet",kind:"constant.float",params:{value:.25}},{id:"mix",kind:"layer.mixPainted",params:{mode:t,enableNoise:!1,softness:.3,noiseScale:.1,noiseAmount:.5}}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"mud",port:"value"},to:{nodeId:"mix",port:"layer2"}},{from:{nodeId:"wet",port:"value"},to:{nodeId:"mix",port:"layer8"}}],{roughness:{input:{nodeId:"mix",port:"value"}}}),a=v(o);e(a.nodes.get("mix")?.outputs.value.type).toBe("float"),e(a.output.roughness).toBeTruthy()}}),t("painted layer mix node rejects incompatible layer types",()=>{const t=j([{id:"base",kind:"constant.vec2",params:{value:[0,1]}},{id:"layer",kind:"constant.vec3",params:{value:[1,0,0]}},{id:"mix",kind:"layer.mixPainted"}],[{from:{nodeId:"base",port:"value"},to:{nodeId:"mix",port:"base"}},{from:{nodeId:"layer",port:"value"},to:{nodeId:"mix",port:"layer1"}}],{color:{input:{nodeId:"mix",port:"value"}}});e(()=>v(t)).toThrow(/Cannot mix vec2 and vec3/)}),t("additional math, uv, shape, noise, and effect nodes compile",()=>{const t=j([{id:"uv",kind:"builtin.uv"},{id:"angle",kind:"constant.float",params:{value:.5}},{id:"scale",kind:"constant.float",params:{value:4}},{id:"half",kind:"constant.float",params:{value:.5}},{id:"axis",kind:"constant.vec3",params:{value:[0,1,0]}},{id:"rotatedUv",kind:"uv.rotate"},{id:"twirl",kind:"uv.twirl"},{id:"bulge",kind:"uv.bulge"},{id:"radial",kind:"uv.radial"},{id:"flipbook",kind:"uv.flipbook"},{id:"shape",kind:"shape.roundedRectangle"},{id:"noise",kind:"noise.simplex"},{id:"voronoi",kind:"noise.voronoi2d"},{id:"fresnel",kind:"effect.fresnel"},{id:"sin",kind:"math.sin"},{id:"pow",kind:"math.pow"},{id:"mix",kind:"math.mix"},{id:"rotateAxis",kind:"transform.rotateAxis"}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"rotatedUv",port:"uv"}},{from:{nodeId:"angle",port:"value"},to:{nodeId:"rotatedUv",port:"angle"}},{from:{nodeId:"rotatedUv",port:"value"},to:{nodeId:"twirl",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"twirl",port:"strength"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"bulge",port:"uv"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"bulge",port:"power"}},{from:{nodeId:"bulge",port:"value"},to:{nodeId:"radial",port:"uv"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"flipbook",port:"uv"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"shape",port:"uv"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"width"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"height"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"shape",port:"radius"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"noise",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"noise",port:"scale"}},{from:{nodeId:"twirl",port:"value"},to:{nodeId:"voronoi",port:"uv"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"voronoi",port:"scale"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"fresnel",port:"power"}},{from:{nodeId:"radial",port:"angle"},to:{nodeId:"sin",port:"value"}},{from:{nodeId:"noise",port:"value"},to:{nodeId:"pow",port:"a"}},{from:{nodeId:"half",port:"value"},to:{nodeId:"pow",port:"b"}},{from:{nodeId:"shape",port:"value"},to:{nodeId:"mix",port:"a"}},{from:{nodeId:"voronoi",port:"value"},to:{nodeId:"mix",port:"b"}},{from:{nodeId:"fresnel",port:"value"},to:{nodeId:"mix",port:"t"}},{from:{nodeId:"axis",port:"value"},to:{nodeId:"rotateAxis",port:"axis"}},{from:{nodeId:"angle",port:"value"},to:{nodeId:"rotateAxis",port:"angle"}}],{color:{input:{nodeId:"mix",port:"value"}},opacity:{input:{nodeId:"sin",port:"value"}},anisotropyDirection:{input:{nodeId:"flipbook",port:"value"}},transform:{input:{nodeId:"rotateAxis",port:"value"}}}),o=v(t);e(o.nodes.get("mix")?.outputs.value.type).toBe("float"),e(o.nodes.get("twirl")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("bulge")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("radial")?.outputs.coords.type).toBe("vec2"),e(o.nodes.get("flipbook")?.outputs.value.type).toBe("vec2"),e(o.nodes.get("rotateAxis")?.outputs.value.type).toBe("mat4"),e(o.output.color).toBeTruthy(),e(o.output.opacity).toBeTruthy(),e(o.output.transform).toBeTruthy()}),t("slider input node outputs a clamped float value",()=>{const t=j([{id:"slider",kind:"input.slider",params:{value:1.25}}],[],{roughness:{input:{nodeId:"slider",port:"value"}}}),o=v(t);e(o.nodes.get("slider")?.outputs.value.type).toBe("float"),e(o.output.roughness).toBeTruthy()}),t("comparison and boolean graph nodes compile to boolean outputs",()=>{const t=j([{id:"a",kind:"constant.float",params:{value:.75}},{id:"b",kind:"constant.float",params:{value:.5}},{id:"greater",kind:"compare.greaterThan"},{id:"lessEqual",kind:"compare.lessThanOrEqual"},{id:"not",kind:"boolean.not"},{id:"and",kind:"boolean.and"},{id:"or",kind:"boolean.or"}],[{from:{nodeId:"a",port:"value"},to:{nodeId:"greater",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"greater",port:"b"}},{from:{nodeId:"a",port:"value"},to:{nodeId:"lessEqual",port:"a"}},{from:{nodeId:"b",port:"value"},to:{nodeId:"lessEqual",port:"b"}},{from:{nodeId:"lessEqual",port:"value"},to:{nodeId:"not",port:"value"}},{from:{nodeId:"greater",port:"value"},to:{nodeId:"and",port:"a"}},{from:{nodeId:"not",port:"value"},to:{nodeId:"and",port:"b"}},{from:{nodeId:"and",port:"value"},to:{nodeId:"or",port:"a"}},{from:{nodeId:"lessEqual",port:"value"},to:{nodeId:"or",port:"b"}}],{discard:{input:{nodeId:"or",port:"value"}}}),o=v(t);e(o.nodes.get("greater")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("lessEqual")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("not")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("and")?.outputs.value.type).toBe("boolean"),e(o.nodes.get("or")?.outputs.value.type).toBe("boolean"),e(o.output.discard).toBeTruthy()}),t("split component node exposes vector and color channels as separate outputs",()=>{const t=j([{id:"vec",kind:"constant.vec3",params:{value:[.2,.4,.6]}},{id:"color",kind:"constant.rgba",params:{value:"#336699",alpha:.75}},{id:"splitVec",kind:"decompose.split"},{id:"splitColor",kind:"decompose.split"},{id:"sum",kind:"math.add"}],[{from:{nodeId:"vec",port:"value"},to:{nodeId:"splitVec",port:"value"}},{from:{nodeId:"color",port:"value"},to:{nodeId:"splitColor",port:"value"}},{from:{nodeId:"splitVec",port:"z"},to:{nodeId:"sum",port:"a"}},{from:{nodeId:"splitColor",port:"a"},to:{nodeId:"sum",port:"b"}}],{roughness:{input:{nodeId:"sum",port:"value"}}}),o=v(t);e(Object.keys(o.nodes.get("splitVec")?.outputs??{})).toEqual(["x","y","z"]),e(Object.keys(o.nodes.get("splitColor")?.outputs??{})).toEqual(["r","g","b","a","x","y","z","w"]),e(o.nodes.get("splitVec")?.outputs.z.type).toBe("float"),e(o.nodes.get("splitColor")?.outputs.a.type).toBe("float"),e(o.output.roughness).toBeTruthy()}),t("color gradient node compiles editable stops to a sampled color",()=>{const t=j([{id:"threshold",kind:"constant.float",params:{value:.75}},{id:"gradient",kind:"color.gradient",params:{stops:[{position:0,color:"#000000",alpha:1},{position:.5,color:"#ff0000",alpha:1},{position:1,color:"#ffffff",alpha:1}]}}],[{from:{nodeId:"threshold",port:"value"},to:{nodeId:"gradient",port:"t"}}],{color:{input:{nodeId:"gradient",port:"value"}}}),o=v(t);e(o.nodes.get("gradient")?.outputs.value.type).toBe("rgba"),e(o.output.color).toBeTruthy(),e(f(t,"gradient").color).toBeTruthy()}),t("curve node compiles editable curve to a sampled float",()=>{const t=j([{id:"t",kind:"constant.float",params:{value:.75}},{id:"curve",kind:"utility.curve",params:{curve:"InOutQuad"}}],[{from:{nodeId:"t",port:"value"},to:{nodeId:"curve",port:"t"}}],{roughness:{input:{nodeId:"curve",port:"value"}}}),o=v(t);e(o.nodes.get("curve")?.outputs.value.type).toBe("float"),e(o.output.roughness).toBeTruthy(),e(f(t,"curve").color).toBeTruthy()}),t("graph parameters convert to property params and custom defaults",()=>{const t=y();t.parameters.push({name:"opacity",type:"float",group:"Surface",defaultValue:.5});const o=b(t);e(o.map(e=>e.name)).toEqual(["baseColor","opacity"]),e(o.every(e=>!0===e.options.optional)).toBe(!0),e(o.map(e=>e.options.group??null)).toEqual([null,"Surface"]);const a=d(t);e(a.baseColor).toMatchObject({type:u.RgbNode,value:"#ffffff",override:!1}),e(a.opacity).toMatchObject({type:u.FloatNode,value:.5,override:!1})}),t("existing shader graph param values keep overriding unless explicitly disabled",()=>{const t=y(),o=d(t,{baseColor:{type:u.RgbNode,value:"#ff0000"}});e(o.baseColor).toMatchObject({type:u.RgbNode,value:"#ff0000",override:!0});const a=d(t,{baseColor:{type:u.RgbNode,value:"#00ff00",override:!1}});e(a.baseColor).toMatchObject({type:u.RgbNode,value:"#ffffff",override:!1})}),t("texture graph parameter defaults serialize as sampler parameters",()=>{const t=j([],[],{});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=d(t);e(o.map).toMatchObject({type:u.Sampler2DNode,value:"texture-asset-id"})}),t("shader graph params keep graph-declared serialized types over previous string params",()=>{const t=y();t.parameters.push({name:"map",type:"texture",defaultValue:null});const o=d(t,{baseColor:{type:u.String,value:"#ff0000"},map:{type:u.String,value:"texture-asset-id"}});e(o.baseColor).toMatchObject({type:u.RgbNode,value:"#ff0000"}),e(o.map).toMatchObject({type:u.Sampler2DNode,value:"texture-asset-id"})}),t("shader graph texture params resolve asset ids before compile",async()=>{const t=j([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=await p({},t,new A([{id:"texture-asset-id",name:"Map",type:"texture",fileKey:"map.png",fileFormat:"png"}]),new O);e(o.map).toBeTruthy(),e(typeof o.map).not.toBe("string"),e(v(t,{parameters:o}).output.color).toBeTruthy()}),t("shader graph texture array params expose named slice uniforms",async()=>{const t=j([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-array-layer"});const o=await p({},t,new A([{id:"texture-array-layer",name:"Map Layer",type:"texture",fileKey:"map.png",fileFormat:"png",texture:{textureArrayFileKey:"textures/packed.ktx2",textureArrayLayer:5}}]),new P(new n,5)),a=m(t,{parameters:o});e(a.uniforms.pu_map_i?.value).toBe(5),e(a.fragmentShader).toContain("uniform float pu_map_i")}),t("standard shader height maps support texture array slices",()=>{const t=new w;t.heightMap=new T("heightMap",new n,new k("heightMap_i",3,void 0,!1)),t.map=B(new n),t.heightScale=.05;const o=t.build();e(o.fragmentShader).toContain("uniform sampler2DArray heightMap"),e(o.fragmentShader).toContain("uniform float heightMap_i"),e(o.fragmentShader).toContain("texture(heightMap, vec3(uv + vCurrOffset, heightMapLayer)).r")}),t("standard shader supports particle opacity for instanced VFX materials",()=>{const t=(new w).build();e(t.fragmentShader).toContain("IS_PARTICLE"),e(t.fragmentShader).toContain("particleData")}),t("material graph asset reference and local embedded graph both build materials",async()=>{const t={id:"graph-asset",name:"Reusable Graph",type:"shaderGraph",shaderGraph:y()},o=M("referenced",{source:"asset",assetId:t.id}),a=M("local",{source:"local",graph:y()}),r=new A([t,o,a]),n=new i;e((await s(o,null,r,n,[],!1)).isMaterial).toBe(!0),e((await s(a,null,r,n,[],!1)).isMaterial).toBe(!0)}),t("selected-node preview compiles scalar, vector, color, rgba, and boolean outputs",()=>{const t=[{id:"float",kind:"constant.float",params:{value:.25}},{id:"slider",kind:"input.slider",params:{value:.5}},{id:"vec2",kind:"constant.vec2",params:{value:[1,0]}},{id:"rgb",kind:"constant.vec3",params:{value:[1,.5,0]}},{id:"rgba",kind:"constant.rgba",params:{value:"#ff8800",alpha:.75}},{id:"boolean",kind:"constant.boolean",params:{value:!0}}];for(const o of t){const t=j([o],[],{color:{input:{nodeId:o.id,port:"value"}}});e(f(t,o.id).color).toBeTruthy()}}),t("post-process shader graph builds a fullscreen-safe material",()=>{const t=h(),o=c(t);e(o).toBeInstanceOf(x),e(o.depthWrite).toBe(!1),e(o.depthTest).toBe(!1),e(o.toneMapped).toBe(!1),e(o.transparent).toBe(!1),e(o.userData.isPostProcessShaderGraph).toBe(!0),e(o.uniforms[C]).toBeTruthy(),e(o.uniforms[I]).toBeTruthy()}),t("empty post-process shader graph defaults to identity scene color",()=>{const t=h();t.nodes=[],t.edges=[],t.outputs={};const o=v(t),a=c(t);e(o.output.color).toBeTruthy(),e(o.output.transparent).toBe(!1),e(a.uniforms[C]).toBeTruthy(),e(a.uniforms[I]).toBeTruthy()}),t("post-process color and opacity outputs expose blend weight uniform",()=>{const t=j([{id:"color",kind:"constant.rgba",params:{value:"#ff0044",alpha:1}},{id:"opacity",kind:"constant.float",params:{value:.35}}],[],{color:{input:{nodeId:"color",port:"value"}},opacity:{input:{nodeId:"opacity",port:"value"}},roughness:{value:.8}});t.target="postProcess";const o=v(t),a=c(t);e(o.output.color).toBeTruthy(),e(o.output.opacity).toBeTruthy(),e(o.output.roughness).toBeUndefined(),e(a.uniforms[I]).toBeTruthy()}),t("selected-node preview builds through post-process fullscreen path",()=>{const t=j([{id:"float",kind:"constant.float",params:{value:.25}}],[],{color:{input:{nodeId:"float",port:"value"}}});t.target="postProcess";const o=c(t,{previewNodeId:"float"});e(o).toBeInstanceOf(x),e(o.userData.isPostProcessShaderGraph).toBe(!0),e(o.uniforms[C]).toBeTruthy(),e(o.uniforms[I]).toBeTruthy()}),t("texture parameter nodes expose sampled value and channel outputs",()=>{const t=j([{id:"texture",kind:"input.parameter",params:{parameter:"albedo"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"albedo",type:"texture",defaultValue:null});const o=v(t,{parameters:{albedo:new n}}),a=o.nodes.get("texture")?.outputs??{};e(Object.keys(a)).toEqual(["value","rgb","r","g","b","a","sampler"]),e(a.value.type).toBe("rgba"),e(a.rgb.type).toBe("rgb"),e(a.r.type).toBe("float"),e(a.g.type).toBe("float"),e(a.b.type).toBe("float"),e(a.a.type).toBe("float"),e(a.sampler.type).toBe("sampler2d"),e(o.output.color).toBeTruthy()}),t("texture parameter nodes accept resolved sampler values",()=>{const t=j([{id:"texture",kind:"input.parameter",params:{parameter:"map"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"map",type:"texture",defaultValue:"texture-asset-id"});const o=v(t,{parameters:{map:B(new n)}});e(o.output.color).toBeTruthy()}),t("missing texture parameter values warn and use fallback sampler",()=>{const t=j([{id:"texture",kind:"input.parameter",params:{parameter:"missingTexture"}}],[],{color:{input:{nodeId:"texture",port:"value"}}});t.parameters.push({name:"missingTexture",type:"texture",defaultValue:null});const a=o.spyOn(console,"warn").mockImplementation(()=>{});try{const o=v(t),r=o.nodes.get("texture")?.outputs??{};e(r.sampler.type).toBe("sampler2d"),e(o.output.color).toBeTruthy(),e(a).toHaveBeenCalledWith(e.stringContaining("missingTexture"))}finally{a.mockRestore()}}),t("scene depth graph nodes expose sampled and fragment linear eye depth",()=>{const t=j([{id:"uv",kind:"builtin.screenUv"},{id:"sceneDepth",kind:"scene.sampleDepth"},{id:"fragmentDepth",kind:"scene.fragmentDepth"},{id:"depthFade",kind:"math.subtract"}],[{from:{nodeId:"uv",port:"value"},to:{nodeId:"sceneDepth",port:"uv"}},{from:{nodeId:"sceneDepth",port:"linearEyeDepth"},to:{nodeId:"depthFade",port:"a"}},{from:{nodeId:"fragmentDepth",port:"linearEyeDepth"},to:{nodeId:"depthFade",port:"b"}}],{opacity:{input:{nodeId:"depthFade",port:"value"}}}),o=v(t);e(o.nodes.get("sceneDepth")?.outputs.depth.type).toBe("float"),e(o.nodes.get("sceneDepth")?.outputs.linearEyeDepth.type).toBe("float"),e(o.nodes.get("fragmentDepth")?.outputs.linearEyeDepth.type).toBe("float"),e(o.output.opacity).toBeTruthy()}),t("unpack normal node compiles with various input combinations",()=>{const t=j([{id:"sample",kind:"constant.vec3",params:{value:[.5,.5,1]}},{id:"scale",kind:"constant.float",params:{value:1}},{id:"normal",kind:"builtin.normal"},{id:"toNormal",kind:"color.unpackNormal"}],[{from:{nodeId:"sample",port:"value"},to:{nodeId:"toNormal",port:"color"}},{from:{nodeId:"scale",port:"value"},to:{nodeId:"toNormal",port:"scale"}},{from:{nodeId:"normal",port:"value"},to:{nodeId:"toNormal",port:"normal"}}],{normal:{input:{nodeId:"toNormal",port:"value"}}}),o=v(t);e(o.nodes.get("toNormal")?.outputs.value.type).toBe("vec3"),e(o.output.normal).toBeTruthy()});class A extends l{constructor(e){super(),this.onCreate=null,this.onDelete=null,this.onUpdate=null,this.assets=new Map(e.map(e=>[e.id,e]))}async getAsset(e){return this.assets.get(e)}async getAssets(){return Array.from(this.assets.values())}}class O extends i{async getTexture(){return new n}async getTextureArray(){return{texture:null,layerIndex:null}}}class P extends O{constructor(e,t){super(),this.textureArray=e,this.layerIndex=t}async getTextureArray(){return{texture:this.textureArray,layerIndex:this.layerIndex}}}/*
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=vfx-random-color-initializer.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{expect as o,test as e,vi as r}from"vitest";import{Color as t}from"three";import{RandomColor as l}from"../effects/vfx/initializsers.js";e("random color initializer assigns particle color and enables renderer color use",()=>{const e=r.spyOn(Math,"random").mockReturnValue(.25);try{const e=new l(new t("#000000"),new t("#ffffff")),r={color:{r:0,g:0,b:0},useColor:!1};e.initialize(r),o(r.useColor).toBe(!0),o(r.color.r).toBeCloseTo(.25),o(r.color.g).toBeCloseTo(.25),o(r.color.b).toBeCloseTo(.25)}finally{e.mockRestore()}});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=world-prefab-spawn.test.d.ts.map