@relu-interactives/spatial-ecs 0.3.18 → 0.3.19

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 (40) hide show
  1. package/dist/{XRHandPose-B8xxFb9A.d.cts → XRHandPose-CLl4MiHx.d.cts} +12 -2
  2. package/dist/{XRHandPose-ubMe5Igc.d.ts → XRHandPose-CrzI57JA.d.ts} +12 -2
  3. package/dist/{chunk-PFE2SDXR.js → chunk-D6KXWNYT.js} +3 -3
  4. package/dist/{chunk-PFE2SDXR.js.map → chunk-D6KXWNYT.js.map} +1 -1
  5. package/dist/{chunk-J3EUNW57.js → chunk-H6DQ7JRE.js} +48 -18
  6. package/dist/chunk-H6DQ7JRE.js.map +1 -0
  7. package/dist/{chunk-7SSOZWXM.js → chunk-JYKFLQB5.js} +46 -5
  8. package/dist/chunk-JYKFLQB5.js.map +1 -0
  9. package/dist/{chunk-KRWBNC6Y.js → chunk-P42OE3RU.js} +4 -4
  10. package/dist/{chunk-KRWBNC6Y.js.map → chunk-P42OE3RU.js.map} +1 -1
  11. package/dist/components/index.cjs +33 -10
  12. package/dist/components/index.cjs.map +1 -1
  13. package/dist/components/index.d.cts +3 -3
  14. package/dist/components/index.d.ts +3 -3
  15. package/dist/components/index.js +1 -1
  16. package/dist/docs/guide/vr-and-hand-poses.md +67 -0
  17. package/dist/factories/index.cjs +8 -0
  18. package/dist/factories/index.cjs.map +1 -1
  19. package/dist/factories/index.d.cts +2 -2
  20. package/dist/factories/index.d.ts +2 -2
  21. package/dist/factories/index.js +3 -3
  22. package/dist/{handPose-dFxqbFFY.d.cts → handPose-DlcTtqgx.d.cts} +26 -1
  23. package/dist/{handPose-dFxqbFFY.d.ts → handPose-DlcTtqgx.d.ts} +26 -1
  24. package/dist/{index-7CFf45fo.d.cts → index-3pNbH0iP.d.cts} +1 -1
  25. package/dist/{index-D7aSZ-e9.d.ts → index-C_589fc4.d.ts} +1 -1
  26. package/dist/{index-DhWfxUgL.d.ts → index-JRcoel9F.d.ts} +3 -2
  27. package/dist/{index-CA9fqh4Z.d.cts → index-g777AmFv.d.cts} +3 -2
  28. package/dist/index.cjs +87 -15
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +11 -8
  31. package/dist/index.d.ts +11 -8
  32. package/dist/index.js +8 -8
  33. package/dist/systems/index.cjs +78 -15
  34. package/dist/systems/index.cjs.map +1 -1
  35. package/dist/systems/index.d.cts +2 -2
  36. package/dist/systems/index.d.ts +2 -2
  37. package/dist/systems/index.js +3 -3
  38. package/package.json +1 -1
  39. package/dist/chunk-7SSOZWXM.js.map +0 -1
  40. package/dist/chunk-J3EUNW57.js.map +0 -1
@@ -1,6 +1,6 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, o as EntityType, I as ImageComponent, p as ImageTarget, q as ImageTargetAnchor, r as ImageTargetAnchorOptions, s as ImageTargetReference, t as InstanceSlotComponent, u as InstanceTransform, v as InstancedMeshComponent, w as InstancedMeshKind, L as LightComponent, x as LightValueState, M as MaterialComponent, y as MaterialTypeValue, z as MaterialValueState, B as MeshComponent, F as MeshGeometryComponent, G as MeshGeometryParameters, H as MeshGeometryParametersByKind, J as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, K as PhysicsState, Q as PlaneMeshParameters, R as RadiusHeightMeshParameters, S as RadiusMeshParameters, T as RigidbodyState, U as RigidbodyType, V as ScriptComponent, W as ScriptEntry, X as ScriptObjectComponent, Y as ScriptObjectComponentInput, Z as ScriptObjectRuntimeState, _ as Selectable, $ as SelectionTransformState, a0 as SpriteComponent, a1 as TextureFilteringValue, a2 as TransformModeValue, a3 as TransformSpaceValue, a4 as VRTeleportAreaComponent, a5 as VRTeleportAreaOptions, a6 as VideoComponent, a7 as VrTeleportShape, a8 as XRHandPoseComponent, a9 as XRHandPoseOptions, aa as XRInteractableComponent, ab as XRInteractableOptions, ac as XRInteractorComponent, ad as XRInteractorOptions, ae as XRInteractorSideComponent, af as XRRigComponent, ag as XRRigOptions, ah as XrAttachPoint, ai as XrDistanceGrabBehavior, aj as XrGrabGesture, ak as XrGrabHands, al as XrGrabReach, am as XrHandPoseEntry, an as XrHandPreset, ao as XrInteractorSide, ap as XrInteractorType, aq as XrLocomotionMode, ar as XrTrackingOrigin, as as XrTwoHandRotation, at as createAttachPoint, au as createHandPoseEntry, av as defaultMaterialValue, aw as handPoseDefinitionFor, ax as normalizePhysicsState, ay as setPhysicsLiveRef } from '../XRHandPose-B8xxFb9A.cjs';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as VRTeleportAreaComponent, a6 as VRTeleportAreaOptions, a7 as VideoComponent, a8 as VrTeleportShape, a9 as XRHandPoseComponent, aa as XRHandPoseOptions, ab as XRInteractableComponent, ac as XRInteractableOptions, ad as XRInteractorComponent, ae as XRInteractorOptions, af as XRInteractorSideComponent, ag as XRRigComponent, ah as XRRigOptions, ai as XrAttachPoint, aj as XrDistanceGrabBehavior, ak as XrGrabGesture, al as XrGrabHands, am as XrGrabReach, an as XrHandPoseEntry, ao as XrHandPreset, ap as XrInteractorSide, aq as XrInteractorType, ar as XrLocomotionMode, as as XrTrackingOrigin, at as XrTwoHandRotation, au as createAttachPoint, av as createHandPoseEntry, aw as defaultMaterialValue, ax as handPoseDefinitionFor, ay as normalizePhysicsState, az as setPhysicsLiveRef } from '../XRHandPose-CLl4MiHx.cjs';
2
2
  export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-BhnLw0fZ.cjs';
3
- export { P as PrefabInstance, a as PrefabNode } from '../index-CA9fqh4Z.cjs';
3
+ export { P as PrefabInstance, a as PrefabNode } from '../index-g777AmFv.cjs';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
6
- import '../handPose-dFxqbFFY.cjs';
6
+ import '../handPose-DlcTtqgx.cjs';
@@ -1,6 +1,6 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, o as EntityType, I as ImageComponent, p as ImageTarget, q as ImageTargetAnchor, r as ImageTargetAnchorOptions, s as ImageTargetReference, t as InstanceSlotComponent, u as InstanceTransform, v as InstancedMeshComponent, w as InstancedMeshKind, L as LightComponent, x as LightValueState, M as MaterialComponent, y as MaterialTypeValue, z as MaterialValueState, B as MeshComponent, F as MeshGeometryComponent, G as MeshGeometryParameters, H as MeshGeometryParametersByKind, J as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, K as PhysicsState, Q as PlaneMeshParameters, R as RadiusHeightMeshParameters, S as RadiusMeshParameters, T as RigidbodyState, U as RigidbodyType, V as ScriptComponent, W as ScriptEntry, X as ScriptObjectComponent, Y as ScriptObjectComponentInput, Z as ScriptObjectRuntimeState, _ as Selectable, $ as SelectionTransformState, a0 as SpriteComponent, a1 as TextureFilteringValue, a2 as TransformModeValue, a3 as TransformSpaceValue, a4 as VRTeleportAreaComponent, a5 as VRTeleportAreaOptions, a6 as VideoComponent, a7 as VrTeleportShape, a8 as XRHandPoseComponent, a9 as XRHandPoseOptions, aa as XRInteractableComponent, ab as XRInteractableOptions, ac as XRInteractorComponent, ad as XRInteractorOptions, ae as XRInteractorSideComponent, af as XRRigComponent, ag as XRRigOptions, ah as XrAttachPoint, ai as XrDistanceGrabBehavior, aj as XrGrabGesture, ak as XrGrabHands, al as XrGrabReach, am as XrHandPoseEntry, an as XrHandPreset, ao as XrInteractorSide, ap as XrInteractorType, aq as XrLocomotionMode, ar as XrTrackingOrigin, as as XrTwoHandRotation, at as createAttachPoint, au as createHandPoseEntry, av as defaultMaterialValue, aw as handPoseDefinitionFor, ax as normalizePhysicsState, ay as setPhysicsLiveRef } from '../XRHandPose-ubMe5Igc.js';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as VRTeleportAreaComponent, a6 as VRTeleportAreaOptions, a7 as VideoComponent, a8 as VrTeleportShape, a9 as XRHandPoseComponent, aa as XRHandPoseOptions, ab as XRInteractableComponent, ac as XRInteractableOptions, ad as XRInteractorComponent, ae as XRInteractorOptions, af as XRInteractorSideComponent, ag as XRRigComponent, ah as XRRigOptions, ai as XrAttachPoint, aj as XrDistanceGrabBehavior, ak as XrGrabGesture, al as XrGrabHands, am as XrGrabReach, an as XrHandPoseEntry, ao as XrHandPreset, ap as XrInteractorSide, aq as XrInteractorType, ar as XrLocomotionMode, as as XrTrackingOrigin, at as XrTwoHandRotation, au as createAttachPoint, av as createHandPoseEntry, aw as defaultMaterialValue, ax as handPoseDefinitionFor, ay as normalizePhysicsState, az as setPhysicsLiveRef } from '../XRHandPose-CrzI57JA.js';
2
2
  export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-BhnLw0fZ.js';
3
- export { P as PrefabInstance, a as PrefabNode } from '../index-DhWfxUgL.js';
3
+ export { P as PrefabInstance, a as PrefabNode } from '../index-JRcoel9F.js';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
6
- import '../handPose-dFxqbFFY.js';
6
+ import '../handPose-DlcTtqgx.js';
@@ -1,4 +1,4 @@
1
- export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSE_CURLS, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VRTeleportAreaComponent, VideoComponent, XRHandPoseComponent, XRInteractableComponent, XRInteractorComponent, XRInteractorSideComponent, XRRigComponent, createAttachPoint, createHandPoseEntry, defaultMaterialValue, handPoseDefinitionFor, normalizePhysicsState, setPhysicsLiveRef } from '../chunk-J3EUNW57.js';
1
+ export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSE_CURLS, DEFAULT_POSE_DIRECTIONS, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VRTeleportAreaComponent, VideoComponent, XRHandPoseComponent, XRInteractableComponent, XRInteractorComponent, XRInteractorSideComponent, XRRigComponent, createAttachPoint, createHandPoseEntry, defaultMaterialValue, handPoseDefinitionFor, normalizePhysicsState, setPhysicsLiveRef } from '../chunk-H6DQ7JRE.js';
2
2
  import '../chunk-OY7ZODZP.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -164,6 +164,14 @@ A custom pose is five curl values, `0` (finger straight) to `1` (fully closed),
164
164
 
165
165
  Optionally add a **direction** requirement: which way the palm, fingers, or thumb must point (`up`, `down`, `forward`, `back`, `left`, `right`, `toward-head`, `away-from-head`) within an angle tolerance. This is what separates thumbs-up from thumbs-down, which are the same hand shape.
166
166
 
167
+ ### Per-finger direction
168
+
169
+ Curl says how *bent* a finger is; direction says where it *points*. "Index extended, pointing up" and "index extended, pointing at the user" are different gestures with identical curls, so direction is what tells them apart.
170
+
171
+ In the editor, click a finger on the hand and pick the directions it may point: `Up`, `Down`, `Forward`, `Back`, `Left`, `Right`, `At user`, `Away`. Picking **several means any one of them counts** — that is how "thumb up or diagonally up" stays a single pose. Pick none to leave the finger free.
172
+
173
+ **Angle Tolerance** is how far a finger may point from an accepted direction. 45° by default: direction is a coarse signal, and a tight cone makes a pose that looks right to the user fail to register.
174
+
167
175
  ### Tuning
168
176
 
169
177
  - **Tolerance** — raise it if a pose is hard to trigger, lower it if it fires by accident.
@@ -171,6 +179,65 @@ Optionally add a **direction** requirement: which way the palm, fingers, or thum
171
179
 
172
180
  A pose does not re-fire while held, and survives a brief hand-tracking dropout rather than flickering.
173
181
 
182
+ ### Driving game logic
183
+
184
+ The hooks fire on the entity that declares the pose, so put the component on the object that should react. A few patterns worth knowing:
185
+
186
+ **Fire once per gesture.** `onHandPoseStart` fires when the pose begins, not every frame, so it is safe to spawn or play something directly.
187
+
188
+ ```ts
189
+ import { Behaviour, type XrHandPoseEventDetail } from "@relu-interactives/spatial-ecs";
190
+
191
+ export default class Spellcaster extends Behaviour {
192
+ onHandPoseStart(event: XrHandPoseEventDetail) {
193
+ if (event.poseId !== "cast") return;
194
+ this.spawnPrefab("assets/fireball.prefab", {
195
+ position: this.transform.position,
196
+ });
197
+ }
198
+ }
199
+ ```
200
+
201
+ **Hold a state while the pose lasts.** Use both hooks when the pose should gate something continuous.
202
+
203
+ ```ts
204
+ export default class Shield extends Behaviour {
205
+ private raised = false;
206
+
207
+ onHandPoseStart(event: XrHandPoseEventDetail) {
208
+ if (event.poseId === "open-palm") this.raised = true;
209
+ }
210
+
211
+ onHandPoseEnd(event: XrHandPoseEventDetail) {
212
+ if (event.poseId === "open-palm") this.raised = false;
213
+ }
214
+
215
+ onUpdate() {
216
+ if (this.raised) { /* absorb damage, drift the shield toward the hand… */ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ **React per hand.** `event.handedness` is `"left"` or `"right"`, so one pose can drive two behaviours.
222
+
223
+ ```ts
224
+ onHandPoseStart(event: XrHandPoseEventDetail) {
225
+ if (event.poseId !== "point") return;
226
+ if (event.handedness === "right") this.aim();
227
+ else this.selectMenu();
228
+ }
229
+ ```
230
+
231
+ **Require a confident match.** `event.confidence` is 0–1 at the moment the pose fired. Gate on it when a gesture is destructive.
232
+
233
+ ```ts
234
+ onHandPoseStart(event: XrHandPoseEventDetail) {
235
+ if (event.poseId === "delete-all" && event.confidence > 0.95) this.clearScene();
236
+ }
237
+ ```
238
+
239
+ Two things to design around: **match on `poseId`, not the pose name** — the id is the contract, the name is a label — and give a destructive gesture a longer **Hold Time** so it cannot fire in passing.
240
+
174
241
  ## Locomotion
175
242
 
176
243
  Movement is a property of the **XR Rig**, not of scripts: tracking origin (floor or eye level), locomotion mode, move speed, snap turn, and — when locomotion is teleport — the arc's **Color**, **Blocked Color** and **Thickness**. The arc starts a short way out from the head so it never draws through the user's own view, which matters on Vision Pro where the aim ray originates between the eyes. Teleport destinations are **Teleport Area** entities. Scripts should move the rig, never the camera — while a session is presenting, the headset owns the camera and writes its pose every frame.
@@ -2304,6 +2304,14 @@ function createHandPoseEntry(partial) {
2304
2304
  preset: partial.preset ?? null,
2305
2305
  curls: { ...DEFAULT_POSE_CURLS, ...partial.curls },
2306
2306
  tolerance: partial.tolerance ?? 0.25,
2307
+ directions: {
2308
+ thumb: [...partial.directions?.thumb ?? []],
2309
+ index: [...partial.directions?.index ?? []],
2310
+ middle: [...partial.directions?.middle ?? []],
2311
+ ring: [...partial.directions?.ring ?? []],
2312
+ pinky: [...partial.directions?.pinky ?? []]
2313
+ },
2314
+ directionTolerance: partial.directionTolerance ?? 45,
2307
2315
  hand: partial.hand ?? null,
2308
2316
  orientation: partial.orientation ?? null,
2309
2317
  minHoldSeconds: partial.minHoldSeconds ?? 0.15,