@realitycollective/webxr-interactions 0.1.1-preview.2 → 0.1.1

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 (54) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/README.md +17 -2
  3. package/dist/behaviours/behaviour.d.ts +17 -1
  4. package/dist/behaviours/behaviour.js +12 -2
  5. package/dist/behaviours/behaviour.js.map +1 -1
  6. package/dist/behaviours/grab.d.ts +1 -0
  7. package/dist/behaviours/grab.js +10 -4
  8. package/dist/behaviours/grab.js.map +1 -1
  9. package/dist/contract-cases.js +84 -0
  10. package/dist/contract-cases.js.map +1 -1
  11. package/dist/descriptor.d.ts +7 -1
  12. package/dist/descriptor.js.map +1 -1
  13. package/dist/gaze.d.ts +50 -0
  14. package/dist/gaze.js +162 -0
  15. package/dist/gaze.js.map +1 -1
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.js +9 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/math.d.ts +8 -0
  20. package/dist/math.js +17 -0
  21. package/dist/math.js.map +1 -1
  22. package/dist/memory-physics.d.ts +41 -0
  23. package/dist/memory-physics.js +168 -0
  24. package/dist/memory-physics.js.map +1 -0
  25. package/dist/memory-ports.d.ts +55 -0
  26. package/dist/memory-ports.js +128 -0
  27. package/dist/memory-ports.js.map +1 -0
  28. package/dist/near-pointer-contract-cases.d.ts +16 -0
  29. package/dist/near-pointer-contract-cases.js +283 -0
  30. package/dist/near-pointer-contract-cases.js.map +1 -0
  31. package/dist/near-pointer.d.ts +88 -0
  32. package/dist/near-pointer.js +114 -0
  33. package/dist/near-pointer.js.map +1 -0
  34. package/dist/physics-contract-cases.d.ts +11 -0
  35. package/dist/physics-contract-cases.js +229 -0
  36. package/dist/physics-contract-cases.js.map +1 -0
  37. package/dist/physics.d.ts +148 -0
  38. package/dist/physics.js +35 -0
  39. package/dist/physics.js.map +1 -0
  40. package/dist/ports.d.ts +15 -0
  41. package/dist/ports.js.map +1 -1
  42. package/dist/release-velocity.d.ts +58 -0
  43. package/dist/release-velocity.js +89 -0
  44. package/dist/release-velocity.js.map +1 -0
  45. package/dist/runtime.d.ts +129 -4
  46. package/dist/runtime.js +362 -40
  47. package/dist/runtime.js.map +1 -1
  48. package/dist/trace-synthetic.d.ts +12 -0
  49. package/dist/trace-synthetic.js +194 -0
  50. package/dist/trace-synthetic.js.map +1 -0
  51. package/dist/trace.d.ts +215 -0
  52. package/dist/trace.js +350 -0
  53. package/dist/trace.js.map +1 -0
  54. package/package.json +8 -4
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The shared near-pointer conformance suite: the touch press, the grip grab,
3
+ * pointer priority with its selection lock, and the ray and cursor visuals
4
+ * (`near-pointer.ts`), run through a binding's own `HitTester` so every
5
+ * platform proves the same rule over its own scene queries.
6
+ *
7
+ * Runner-free, like `hitTesterContractCases()`: an adapter's test file is a
8
+ * loop over these cases with the same subject factory it already uses for
9
+ * the hit-tester suite. The suite drives an `InteractionRuntime` itself, with
10
+ * a scripted provider, so the only platform code under test is the hit
11
+ * tester and the targets the driver places.
12
+ *
13
+ * Every distance below is metres to the target's SURFACE, the quantity the
14
+ * touch and grab pointers compare (IWSDK's sphere intersector reports the
15
+ * distance from the fingertip to the point on the mesh).
16
+ */
17
+ import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
18
+ import { NEAR_POINTER_DEFAULTS } from "./near-pointer.js";
19
+ import { InteractionRuntime } from "./runtime.js";
20
+ /**
21
+ * The shared near-pointer conformance suite. An adapter's test file:
22
+ *
23
+ * ```ts
24
+ * for (const contractCase of nearPointerContractCases()) {
25
+ * it(contractCase.name, () => contractCase.run(makeSubject()));
26
+ * }
27
+ * ```
28
+ */
29
+ export function nearPointerContractCases() {
30
+ return NEAR_POINTER_CASES;
31
+ }
32
+ /** A provider whose sources a case sets frame by frame. */
33
+ class ScriptedProvider {
34
+ sources = [];
35
+ capabilities = {
36
+ ...NO_CAPABILITIES,
37
+ rays: true,
38
+ pokes: true,
39
+ grabs: "poseOnly",
40
+ handJoints: true,
41
+ pinch: true,
42
+ headPose: false,
43
+ gaze: false,
44
+ };
45
+ getCapabilities() {
46
+ return this.capabilities;
47
+ }
48
+ onCapabilitiesChanged() {
49
+ return () => undefined;
50
+ }
51
+ onSourcesChanged() {
52
+ return () => undefined;
53
+ }
54
+ sample() {
55
+ return this.sources;
56
+ }
57
+ }
58
+ const CENTRE = [0, 1, -1];
59
+ const RADIUS = 0.1;
60
+ /** A ray from the viewer straight through the target. */
61
+ const RAY_AT = { origin: [0, 1, 0], direction: [0, 0, -1] };
62
+ /** A ray pointing away from everything. */
63
+ const RAY_AWAY = { origin: [0, 1, 0], direction: [0, 0, 1] };
64
+ const FAR = [0, 1, 1];
65
+ /** A point `distance` metres outside the target's surface, on its near side. */
66
+ function outside(distance) {
67
+ return [CENTRE[0], CENTRE[1], CENTRE[2] + RADIUS + distance];
68
+ }
69
+ function hand(frame) {
70
+ const snapshot = {
71
+ id: "right",
72
+ kind: frame.kind ?? "hand",
73
+ handedness: "right",
74
+ select: frame.select ?? 0,
75
+ squeeze: frame.squeeze ?? 0,
76
+ };
77
+ const ray = frame.ray ?? "away";
78
+ if (ray !== "none")
79
+ snapshot.ray = ray === "at" ? { ...RAY_AT } : { ...RAY_AWAY };
80
+ snapshot.indexTip = frame.tip ?? FAR;
81
+ snapshot.gripPose = { position: frame.grip ?? FAR, quaternion: [0, 0, 0, 1] };
82
+ return snapshot;
83
+ }
84
+ function rig(subject, behaviours = [{ kind: "press" }]) {
85
+ subject.driver.place("target", CENTRE, RADIUS);
86
+ const provider = new ScriptedProvider();
87
+ const runtime = new InteractionRuntime({ provider, hitTester: subject.hitTester, velocity: false });
88
+ const events = [];
89
+ const visuals = [];
90
+ runtime.onEvent((event) => events.push(event));
91
+ runtime.onPointerVisuals((list) => {
92
+ visuals.length = 0;
93
+ visuals.push(...list);
94
+ });
95
+ runtime.registerInteractable({ id: "target", behaviours });
96
+ return {
97
+ runtime,
98
+ provider,
99
+ events,
100
+ visuals,
101
+ frame(frame) {
102
+ const before = events.length;
103
+ provider.sources = [hand(frame)];
104
+ runtime.update(1 / 60);
105
+ return events.slice(before).map((event) => event.type);
106
+ },
107
+ };
108
+ }
109
+ function assert(condition, message) {
110
+ if (!condition)
111
+ throw new Error(message);
112
+ }
113
+ function expectActive(r, kind, when) {
114
+ const active = r.runtime.getActivePointer("right");
115
+ assert(active === kind, `${when}: the active pointer must be ${String(kind)}, got ${String(active)}`);
116
+ }
117
+ const D = NEAR_POINTER_DEFAULTS;
118
+ const NEAR_POINTER_CASES = [
119
+ {
120
+ name: "a fingertip approaching from outside hovers at 0.15 m, presses at 0.02 m, releases on the way out and stops hovering beyond 0.2 m",
121
+ run(subject) {
122
+ const r = rig(subject);
123
+ r.frame({ tip: outside(0.3) });
124
+ expectActive(r, null, "at 0.3 m");
125
+ let types = r.frame({ tip: outside(D.touchHoverEnter - 0.01) });
126
+ expectActive(r, "touch", "inside the hover enter distance");
127
+ assert(types.includes("hoverEnter"), "entering the hover band must raise hoverEnter");
128
+ types = r.frame({ tip: outside(0.05) });
129
+ assert(!types.includes("pressStart"), "5 cm out must not press");
130
+ types = r.frame({ tip: outside(D.touchDown) });
131
+ assert(types.includes("pressStart"), "reaching the press distance must raise pressStart");
132
+ assert(r.runtime.getState("target")?.pressed === true, "the target must be pressed");
133
+ types = r.frame({ tip: outside(0.005) });
134
+ assert(!types.includes("pressEnd") && !types.includes("pressStart"), "going deeper must change nothing");
135
+ types = r.frame({ tip: outside(D.touchDown + 0.01) });
136
+ assert(types.includes("pressEnd"), "coming back out past the press distance must release");
137
+ r.frame({ tip: outside(D.touchHoverEnter + 0.02) });
138
+ expectActive(r, "touch", "between enter and exit while hovering (hysteresis)");
139
+ types = r.frame({ tip: outside(D.touchHoverExit + 0.01) });
140
+ expectActive(r, null, "beyond the exit distance");
141
+ assert(types.includes("hoverExit"), "leaving the exit distance must raise hoverExit");
142
+ },
143
+ },
144
+ {
145
+ name: "a fingertip first seen inside the press band never presses until it has been outside it",
146
+ run(subject) {
147
+ const r = rig(subject);
148
+ let types = r.frame({ tip: outside(0.01) });
149
+ assert(types.includes("hoverEnter") && !types.includes("pressStart"), "a fingertip that appears inside the band hovers but must not press");
150
+ types = r.frame({ tip: outside(0.0) });
151
+ assert(!types.includes("pressStart"), "staying inside must not press");
152
+ r.frame({ tip: outside(0.05) });
153
+ types = r.frame({ tip: outside(0.015) });
154
+ assert(types.includes("pressStart"), "coming back in from outside must press");
155
+ },
156
+ },
157
+ {
158
+ name: "a grip within 0.07 m of the surface makes grab the active pointer and a squeeze grabs that target",
159
+ run(subject) {
160
+ const r = rig(subject, [{ kind: "grab" }]);
161
+ r.frame({ grip: outside(D.grabRadius - 0.02), ray: "at" });
162
+ expectActive(r, "grab", "grip inside the grab radius, ray also hitting");
163
+ let types = r.frame({ grip: outside(D.grabRadius - 0.02), ray: "at", squeeze: 1 });
164
+ assert(types.includes("grabStart"), "a squeeze while grab owns the hand must grab");
165
+ types = r.frame({ grip: outside(D.grabRadius - 0.02), ray: "at", squeeze: 0 });
166
+ assert(types.includes("grabEnd"), "releasing the squeeze must end the grab");
167
+ r.frame({ grip: outside(D.grabRadius + 0.01), ray: "away" });
168
+ expectActive(r, null, "grip outside the grab radius with no ray hit");
169
+ },
170
+ },
171
+ {
172
+ name: "touch beats grab beats ray",
173
+ run(subject) {
174
+ const r = rig(subject);
175
+ r.frame({ tip: outside(0.1), grip: outside(0.05), ray: "at" });
176
+ expectActive(r, "touch", "fingertip, grip and ray all on the target");
177
+ r.frame({ grip: outside(0.05), ray: "at" });
178
+ expectActive(r, "grab", "grip and ray on the target");
179
+ r.frame({ ray: "at" });
180
+ expectActive(r, "ray", "only the ray on the target");
181
+ r.frame({ ray: "away" });
182
+ expectActive(r, null, "nothing on the target");
183
+ },
184
+ },
185
+ {
186
+ name: "the ray and its cursor show only while the ray owns the source, and the cursor sits at the active pointer's hit",
187
+ run(subject) {
188
+ const r = rig(subject);
189
+ r.frame({ ray: "at" });
190
+ let v = r.visuals[0];
191
+ assert(v.ray && v.cursor && v.cursorPoint !== null, "with the ray active, the ray and its cursor show");
192
+ assert(Math.abs(v.cursorPoint[2] - (CENTRE[2] + RADIUS)) <= RADIUS + 0.05, "the cursor sits near the ray's hit on the target");
193
+ r.frame({ tip: outside(0.05), ray: "at" });
194
+ v = r.visuals[0];
195
+ assert(!v.ray, "with touch active, the ray is hidden");
196
+ assert(v.cursor && v.cursorPoint !== null, "with touch active, the cursor shows at the touch point");
197
+ assert(Math.abs(v.cursorPoint[2] - (CENTRE[2] + RADIUS)) <= RADIUS + 0.05, "the cursor sits on the surface under the fingertip");
198
+ r.frame({ grip: outside(0.05), ray: "at" });
199
+ v = r.visuals[0];
200
+ assert(!v.ray && v.cursor, "with grab active, the ray is hidden and the cursor shows");
201
+ r.frame({ ray: "away" });
202
+ v = r.visuals[0];
203
+ assert(v.ray && !v.cursor && v.cursorPoint === null, "with nothing active, the ray shows and the cursor does not");
204
+ r.frame({ ray: "none" });
205
+ v = r.visuals[0];
206
+ assert(!v.ray && !v.cursor, "a source with no ray draws no ray");
207
+ },
208
+ },
209
+ {
210
+ name: "a pointer that is pressing keeps the source until it releases",
211
+ run(subject) {
212
+ const r = rig(subject);
213
+ r.frame({ ray: "at" });
214
+ let types = r.frame({ ray: "at", select: 1 });
215
+ assert(types.includes("pressStart"), "a select on the ray target presses it");
216
+ r.frame({ tip: outside(0.05), ray: "at", select: 1 });
217
+ expectActive(r, "ray", "a fingertip entering the band while the ray presses");
218
+ types = r.frame({ tip: outside(0.05), ray: "at", select: 0 });
219
+ assert(types.includes("pressEnd"), "releasing select ends the ray press");
220
+ r.frame({ tip: outside(0.05), ray: "at" });
221
+ expectActive(r, "touch", "once released, touch takes the source");
222
+ },
223
+ },
224
+ {
225
+ name: "a panel nearer than the object takes the ray, the object is not hovered, and the visuals name the panel",
226
+ run(subject) {
227
+ // IWSDK's MultiPointer runs over every pointer-event object, panels
228
+ // included; the UI Extensions hosts offer their panels to the same
229
+ // arbiter. Here a stand-in panel set offers a ray hit nearer than the
230
+ // target 1 m out.
231
+ const r = rig(subject);
232
+ const panels = r.runtime.getPointerArbiter().registerSet("panels", "panel");
233
+ panels.offer("right", "ray", { targetId: "info", point: [0, 1, -0.5], distance: 0.5 });
234
+ const types = r.frame({ ray: "at" });
235
+ expectActive(r, "ray", "a ray through a panel and an object behind it");
236
+ assert(!types.includes("hoverEnter"), "the object behind the panel must not be hovered");
237
+ const v = r.visuals[0];
238
+ assert(v.ray && v.cursor && v.targetKind === "panel" && v.targetId === "info", `the visuals must name the panel, got ${JSON.stringify(v)}`);
239
+ assert(Math.abs(v.hitDistance - 0.5) < 1e-9, "the visuals carry the panel hit's distance");
240
+ panels.offer("right", "ray", null);
241
+ const after = r.frame({ ray: "at" });
242
+ assert(after.includes("hoverEnter"), "with the panel gone the object behind it is hovered");
243
+ assert(r.visuals[0].targetKind === "object", "the visuals name the object again");
244
+ panels.dispose();
245
+ },
246
+ },
247
+ {
248
+ name: "a fingertip on a panel retires the ray over an object, and a fingertip on an object retires the ray over a panel",
249
+ run(subject) {
250
+ const r = rig(subject);
251
+ const panels = r.runtime.getPointerArbiter().registerSet("panels", "panel");
252
+ r.frame({ ray: "at" });
253
+ expectActive(r, "ray", "the ray on the object");
254
+ panels.offer("right", "touch", { targetId: "info", point: [0.3, 1, -0.5], distance: 0.01 });
255
+ let types = r.frame({ ray: "at" });
256
+ expectActive(r, "touch", "a fingertip touching a panel while the ray is on an object");
257
+ assert(types.includes("hoverExit"), "the object under the ray loses its hover when the panel's touch takes the hand");
258
+ let v = r.visuals[0];
259
+ assert(!v.ray && v.cursor && v.targetKind === "panel", `the ray hides and the cursor sits on the panel, got ${JSON.stringify(v)}`);
260
+ panels.offer("right", "touch", null);
261
+ panels.offer("right", "ray", { targetId: "info", point: [0, 1, -0.5], distance: 0.5 });
262
+ r.frame({ ray: "at" });
263
+ expectActive(r, "ray", "the ray on the panel");
264
+ types = r.frame({ tip: outside(0.05), ray: "at" });
265
+ expectActive(r, "touch", "a fingertip touching the object while the ray is on a panel");
266
+ v = r.visuals[0];
267
+ assert(!v.ray && v.cursor && v.targetKind === "object" && v.targetId === "target", `the ray over the panel hides and the cursor sits on the object, got ${JSON.stringify(v)}`);
268
+ assert(types.includes("hoverEnter"), "the touched object is hovered");
269
+ panels.dispose();
270
+ },
271
+ },
272
+ {
273
+ name: "a controller's ray origin presses by touch exactly as a fingertip does",
274
+ run(subject) {
275
+ const r = rig(subject);
276
+ r.frame({ kind: "controller", tip: outside(0.1) });
277
+ expectActive(r, "touch", "a controller nose inside the hover band");
278
+ const types = r.frame({ kind: "controller", tip: outside(0.01) });
279
+ assert(types.includes("pressStart"), "a controller nose reaching the press distance presses");
280
+ },
281
+ },
282
+ ];
283
+ //# sourceMappingURL=near-pointer-contract-cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-pointer-contract-cases.js","sourceRoot":"","sources":["../src/near-pointer-contract-cases.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared near-pointer conformance suite: the touch press, the grip grab,\n * pointer priority with its selection lock, and the ray and cursor visuals\n * (`near-pointer.ts`), run through a binding's own `HitTester` so every\n * platform proves the same rule over its own scene queries.\n *\n * Runner-free, like `hitTesterContractCases()`: an adapter's test file is a\n * loop over these cases with the same subject factory it already uses for\n * the hit-tester suite. The suite drives an `InteractionRuntime` itself, with\n * a scripted provider, so the only platform code under test is the hit\n * tester and the targets the driver places.\n *\n * Every distance below is metres to the target's SURFACE, the quantity the\n * touch and grab pointers compare (IWSDK's sphere intersector reports the\n * distance from the fingertip to the point on the mesh).\n */\nimport {\n NO_CAPABILITIES,\n type InputCapabilities,\n type InputProvider,\n type InputSourceSnapshot,\n type Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport type { HitTesterContractSubject } from \"./contract-cases.js\";\nimport type { InteractionEvent } from \"./events.js\";\nimport { NEAR_POINTER_DEFAULTS, type PointerVisuals } from \"./near-pointer.js\";\nimport { InteractionRuntime } from \"./runtime.js\";\n\n/** One check a binding's hit tester must pass under the near-pointer rule. */\nexport interface NearPointerContractCase {\n name: string;\n run(subject: HitTesterContractSubject): void;\n}\n\n/**\n * The shared near-pointer conformance suite. An adapter's test file:\n *\n * ```ts\n * for (const contractCase of nearPointerContractCases()) {\n * it(contractCase.name, () => contractCase.run(makeSubject()));\n * }\n * ```\n */\nexport function nearPointerContractCases(): readonly NearPointerContractCase[] {\n return NEAR_POINTER_CASES;\n}\n\n/** A provider whose sources a case sets frame by frame. */\nclass ScriptedProvider implements InputProvider {\n sources: InputSourceSnapshot[] = [];\n private readonly capabilities: InputCapabilities = {\n ...NO_CAPABILITIES,\n rays: true,\n pokes: true,\n grabs: \"poseOnly\",\n handJoints: true,\n pinch: true,\n headPose: false,\n gaze: false,\n };\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(): () => void {\n return () => undefined;\n }\n\n onSourcesChanged(): () => void {\n return () => undefined;\n }\n\n sample(): readonly InputSourceSnapshot[] {\n return this.sources;\n }\n}\n\nconst CENTRE: Vec3Tuple = [0, 1, -1];\nconst RADIUS = 0.1;\n/** A ray from the viewer straight through the target. */\nconst RAY_AT = { origin: [0, 1, 0] as Vec3Tuple, direction: [0, 0, -1] as Vec3Tuple };\n/** A ray pointing away from everything. */\nconst RAY_AWAY = { origin: [0, 1, 0] as Vec3Tuple, direction: [0, 0, 1] as Vec3Tuple };\nconst FAR: Vec3Tuple = [0, 1, 1];\n\n/** A point `distance` metres outside the target's surface, on its near side. */\nfunction outside(distance: number): Vec3Tuple {\n return [CENTRE[0], CENTRE[1], CENTRE[2] + RADIUS + distance];\n}\n\ninterface HandFrame {\n tip?: Vec3Tuple;\n grip?: Vec3Tuple;\n ray?: \"at\" | \"away\" | \"none\";\n select?: number;\n squeeze?: number;\n kind?: \"hand\" | \"controller\";\n}\n\nfunction hand(frame: HandFrame): InputSourceSnapshot {\n const snapshot: InputSourceSnapshot = {\n id: \"right\",\n kind: frame.kind ?? \"hand\",\n handedness: \"right\",\n select: frame.select ?? 0,\n squeeze: frame.squeeze ?? 0,\n };\n const ray = frame.ray ?? \"away\";\n if (ray !== \"none\") snapshot.ray = ray === \"at\" ? { ...RAY_AT } : { ...RAY_AWAY };\n snapshot.indexTip = frame.tip ?? FAR;\n snapshot.gripPose = { position: frame.grip ?? FAR, quaternion: [0, 0, 0, 1] };\n return snapshot;\n}\n\ninterface Rig {\n runtime: InteractionRuntime;\n provider: ScriptedProvider;\n events: InteractionEvent[];\n visuals: PointerVisuals[];\n /** Feed one frame and return the event types it produced. */\n frame(frame: HandFrame): string[];\n}\n\nfunction rig(subject: HitTesterContractSubject, behaviours: Array<{ kind: \"press\" } | { kind: \"grab\" }> = [{ kind: \"press\" }]): Rig {\n subject.driver.place(\"target\", CENTRE, RADIUS);\n const provider = new ScriptedProvider();\n const runtime = new InteractionRuntime({ provider, hitTester: subject.hitTester, velocity: false });\n const events: InteractionEvent[] = [];\n const visuals: PointerVisuals[] = [];\n runtime.onEvent((event) => events.push(event));\n runtime.onPointerVisuals((list) => {\n visuals.length = 0;\n visuals.push(...list);\n });\n runtime.registerInteractable({ id: \"target\", behaviours });\n return {\n runtime,\n provider,\n events,\n visuals,\n frame(frame) {\n const before = events.length;\n provider.sources = [hand(frame)];\n runtime.update(1 / 60);\n return events.slice(before).map((event) => event.type);\n },\n };\n}\n\nfunction assert(condition: boolean, message: string): asserts condition {\n if (!condition) throw new Error(message);\n}\n\nfunction expectActive(r: Rig, kind: string | null, when: string): void {\n const active = r.runtime.getActivePointer(\"right\");\n assert(active === kind, `${when}: the active pointer must be ${String(kind)}, got ${String(active)}`);\n}\n\nconst D = NEAR_POINTER_DEFAULTS;\n\nconst NEAR_POINTER_CASES: readonly NearPointerContractCase[] = [\n {\n name: \"a fingertip approaching from outside hovers at 0.15 m, presses at 0.02 m, releases on the way out and stops hovering beyond 0.2 m\",\n run(subject) {\n const r = rig(subject);\n r.frame({ tip: outside(0.3) });\n expectActive(r, null, \"at 0.3 m\");\n let types = r.frame({ tip: outside(D.touchHoverEnter - 0.01) });\n expectActive(r, \"touch\", \"inside the hover enter distance\");\n assert(types.includes(\"hoverEnter\"), \"entering the hover band must raise hoverEnter\");\n types = r.frame({ tip: outside(0.05) });\n assert(!types.includes(\"pressStart\"), \"5 cm out must not press\");\n types = r.frame({ tip: outside(D.touchDown) });\n assert(types.includes(\"pressStart\"), \"reaching the press distance must raise pressStart\");\n assert(r.runtime.getState(\"target\")?.pressed === true, \"the target must be pressed\");\n types = r.frame({ tip: outside(0.005) });\n assert(!types.includes(\"pressEnd\") && !types.includes(\"pressStart\"), \"going deeper must change nothing\");\n types = r.frame({ tip: outside(D.touchDown + 0.01) });\n assert(types.includes(\"pressEnd\"), \"coming back out past the press distance must release\");\n r.frame({ tip: outside(D.touchHoverEnter + 0.02) });\n expectActive(r, \"touch\", \"between enter and exit while hovering (hysteresis)\");\n types = r.frame({ tip: outside(D.touchHoverExit + 0.01) });\n expectActive(r, null, \"beyond the exit distance\");\n assert(types.includes(\"hoverExit\"), \"leaving the exit distance must raise hoverExit\");\n },\n },\n {\n name: \"a fingertip first seen inside the press band never presses until it has been outside it\",\n run(subject) {\n const r = rig(subject);\n let types = r.frame({ tip: outside(0.01) });\n assert(types.includes(\"hoverEnter\") && !types.includes(\"pressStart\"), \"a fingertip that appears inside the band hovers but must not press\");\n types = r.frame({ tip: outside(0.0) });\n assert(!types.includes(\"pressStart\"), \"staying inside must not press\");\n r.frame({ tip: outside(0.05) });\n types = r.frame({ tip: outside(0.015) });\n assert(types.includes(\"pressStart\"), \"coming back in from outside must press\");\n },\n },\n {\n name: \"a grip within 0.07 m of the surface makes grab the active pointer and a squeeze grabs that target\",\n run(subject) {\n const r = rig(subject, [{ kind: \"grab\" }]);\n r.frame({ grip: outside(D.grabRadius - 0.02), ray: \"at\" });\n expectActive(r, \"grab\", \"grip inside the grab radius, ray also hitting\");\n let types = r.frame({ grip: outside(D.grabRadius - 0.02), ray: \"at\", squeeze: 1 });\n assert(types.includes(\"grabStart\"), \"a squeeze while grab owns the hand must grab\");\n types = r.frame({ grip: outside(D.grabRadius - 0.02), ray: \"at\", squeeze: 0 });\n assert(types.includes(\"grabEnd\"), \"releasing the squeeze must end the grab\");\n r.frame({ grip: outside(D.grabRadius + 0.01), ray: \"away\" });\n expectActive(r, null, \"grip outside the grab radius with no ray hit\");\n },\n },\n {\n name: \"touch beats grab beats ray\",\n run(subject) {\n const r = rig(subject);\n r.frame({ tip: outside(0.1), grip: outside(0.05), ray: \"at\" });\n expectActive(r, \"touch\", \"fingertip, grip and ray all on the target\");\n r.frame({ grip: outside(0.05), ray: \"at\" });\n expectActive(r, \"grab\", \"grip and ray on the target\");\n r.frame({ ray: \"at\" });\n expectActive(r, \"ray\", \"only the ray on the target\");\n r.frame({ ray: \"away\" });\n expectActive(r, null, \"nothing on the target\");\n },\n },\n {\n name: \"the ray and its cursor show only while the ray owns the source, and the cursor sits at the active pointer's hit\",\n run(subject) {\n const r = rig(subject);\n r.frame({ ray: \"at\" });\n let v = r.visuals[0]!;\n assert(v.ray && v.cursor && v.cursorPoint !== null, \"with the ray active, the ray and its cursor show\");\n assert(Math.abs(v.cursorPoint![2] - (CENTRE[2] + RADIUS)) <= RADIUS + 0.05, \"the cursor sits near the ray's hit on the target\");\n r.frame({ tip: outside(0.05), ray: \"at\" });\n v = r.visuals[0]!;\n assert(!v.ray, \"with touch active, the ray is hidden\");\n assert(v.cursor && v.cursorPoint !== null, \"with touch active, the cursor shows at the touch point\");\n assert(Math.abs(v.cursorPoint![2] - (CENTRE[2] + RADIUS)) <= RADIUS + 0.05, \"the cursor sits on the surface under the fingertip\");\n r.frame({ grip: outside(0.05), ray: \"at\" });\n v = r.visuals[0]!;\n assert(!v.ray && v.cursor, \"with grab active, the ray is hidden and the cursor shows\");\n r.frame({ ray: \"away\" });\n v = r.visuals[0]!;\n assert(v.ray && !v.cursor && v.cursorPoint === null, \"with nothing active, the ray shows and the cursor does not\");\n r.frame({ ray: \"none\" });\n v = r.visuals[0]!;\n assert(!v.ray && !v.cursor, \"a source with no ray draws no ray\");\n },\n },\n {\n name: \"a pointer that is pressing keeps the source until it releases\",\n run(subject) {\n const r = rig(subject);\n r.frame({ ray: \"at\" });\n let types = r.frame({ ray: \"at\", select: 1 });\n assert(types.includes(\"pressStart\"), \"a select on the ray target presses it\");\n r.frame({ tip: outside(0.05), ray: \"at\", select: 1 });\n expectActive(r, \"ray\", \"a fingertip entering the band while the ray presses\");\n types = r.frame({ tip: outside(0.05), ray: \"at\", select: 0 });\n assert(types.includes(\"pressEnd\"), \"releasing select ends the ray press\");\n r.frame({ tip: outside(0.05), ray: \"at\" });\n expectActive(r, \"touch\", \"once released, touch takes the source\");\n },\n },\n {\n name: \"a panel nearer than the object takes the ray, the object is not hovered, and the visuals name the panel\",\n run(subject) {\n // IWSDK's MultiPointer runs over every pointer-event object, panels\n // included; the UI Extensions hosts offer their panels to the same\n // arbiter. Here a stand-in panel set offers a ray hit nearer than the\n // target 1 m out.\n const r = rig(subject);\n const panels = r.runtime.getPointerArbiter().registerSet(\"panels\", \"panel\");\n panels.offer(\"right\", \"ray\", { targetId: \"info\", point: [0, 1, -0.5], distance: 0.5 });\n const types = r.frame({ ray: \"at\" });\n expectActive(r, \"ray\", \"a ray through a panel and an object behind it\");\n assert(!types.includes(\"hoverEnter\"), \"the object behind the panel must not be hovered\");\n const v = r.visuals[0]!;\n assert(v.ray && v.cursor && v.targetKind === \"panel\" && v.targetId === \"info\", `the visuals must name the panel, got ${JSON.stringify(v)}`);\n assert(Math.abs(v.hitDistance! - 0.5) < 1e-9, \"the visuals carry the panel hit's distance\");\n panels.offer(\"right\", \"ray\", null);\n const after = r.frame({ ray: \"at\" });\n assert(after.includes(\"hoverEnter\"), \"with the panel gone the object behind it is hovered\");\n assert(r.visuals[0]!.targetKind === \"object\", \"the visuals name the object again\");\n panels.dispose();\n },\n },\n {\n name: \"a fingertip on a panel retires the ray over an object, and a fingertip on an object retires the ray over a panel\",\n run(subject) {\n const r = rig(subject);\n const panels = r.runtime.getPointerArbiter().registerSet(\"panels\", \"panel\");\n r.frame({ ray: \"at\" });\n expectActive(r, \"ray\", \"the ray on the object\");\n panels.offer(\"right\", \"touch\", { targetId: \"info\", point: [0.3, 1, -0.5], distance: 0.01 });\n let types = r.frame({ ray: \"at\" });\n expectActive(r, \"touch\", \"a fingertip touching a panel while the ray is on an object\");\n assert(types.includes(\"hoverExit\"), \"the object under the ray loses its hover when the panel's touch takes the hand\");\n let v = r.visuals[0]!;\n assert(!v.ray && v.cursor && v.targetKind === \"panel\", `the ray hides and the cursor sits on the panel, got ${JSON.stringify(v)}`);\n panels.offer(\"right\", \"touch\", null);\n panels.offer(\"right\", \"ray\", { targetId: \"info\", point: [0, 1, -0.5], distance: 0.5 });\n r.frame({ ray: \"at\" });\n expectActive(r, \"ray\", \"the ray on the panel\");\n types = r.frame({ tip: outside(0.05), ray: \"at\" });\n expectActive(r, \"touch\", \"a fingertip touching the object while the ray is on a panel\");\n v = r.visuals[0]!;\n assert(!v.ray && v.cursor && v.targetKind === \"object\" && v.targetId === \"target\", `the ray over the panel hides and the cursor sits on the object, got ${JSON.stringify(v)}`);\n assert(types.includes(\"hoverEnter\"), \"the touched object is hovered\");\n panels.dispose();\n },\n },\n {\n name: \"a controller's ray origin presses by touch exactly as a fingertip does\",\n run(subject) {\n const r = rig(subject);\n r.frame({ kind: \"controller\", tip: outside(0.1) });\n expectActive(r, \"touch\", \"a controller nose inside the hover band\");\n const types = r.frame({ kind: \"controller\", tip: outside(0.01) });\n assert(types.includes(\"pressStart\"), \"a controller nose reaching the press distance presses\");\n },\n },\n];\n"]}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Near pointers - the touch press, the grip grab, which pointer owns a hand
3
+ * and what that means for the ray and cursor. Pure logic, no engine.
4
+ *
5
+ * The template is IWSDK 1.0.0's pointer stack (`@iwsdk/xr-input`
6
+ * `multi-pointer.js`, `touch-pointer.js`, `grab-pointer.js`, over
7
+ * `@pmndrs/pointer-events`), restated here so every binding runs the same
8
+ * rule from the facts it already reports (`indexTip`, `gripPose`, `ray`):
9
+ *
10
+ * - **Touch.** A fingertip (a controller's ray origin stands in for one)
11
+ * hovers a target once its distance to the target's SURFACE is within
12
+ * `touchHoverEnter` (0.15 m) and keeps hovering until it is beyond
13
+ * `touchHoverExit` (0.2 m): `TOUCH_HYSTERESIS`. It presses while that
14
+ * distance is at or under `touchDown` (0.02 m): `TOUCH_DEFAULTS.downRadius`,
15
+ * which `@pmndrs/pointer-events` compares every frame, so the release is
16
+ * the way out past the same distance.
17
+ * - **Grab.** The grip is a sphere of `grabRadius` (0.07 m,
18
+ * `createGrabPointer`'s default) and a target whose surface it reaches is
19
+ * the grab candidate. The squeeze (or a hand's pinch on a grab-only
20
+ * target) then grabs it.
21
+ * - **Priority.** Per source, the first of touch, grab, ray with a candidate
22
+ * is the active pointer (`PRIORITY_ORDER`), and once it is pressing or
23
+ * grabbing it stays active until the release (IWSDK's selection lock).
24
+ * Since 29 September 2026 that decision is made ONCE per source across
25
+ * interactables and UI panels by the `PointerArbiter` in
26
+ * `@realitycollective/webxr-input` (`pointer-arbiter.ts`), which this
27
+ * runtime feeds and reads; `POINTER_PRIORITY`, `pickActivePointer`,
28
+ * `PointerCandidate` and `PointerVisuals` live there and are re-exported
29
+ * here unchanged.
30
+ * - **Visuals.** The ray and its cursor show only while the ray is the active
31
+ * pointer or nothing is (`shouldHideRay`); a near pointer owning the hand
32
+ * hides them, and the shared cursor sits at the active pointer's hit. What
33
+ * the app then shows of them is its `PointerDisplay` configuration.
34
+ *
35
+ * One rule is deliberately stricter than the template, as the Pale Signal
36
+ * work order of 28 September 2026 required: a touch never presses from inside
37
+ * or from behind. A fingertip first seen already within the press band, a
38
+ * hand that pops into tracking inside the target, does not press until it has
39
+ * been seen outside the band and comes back in. IWSDK's touch pointer would
40
+ * press on that first frame; `touch-reference.test.ts` records the one frame
41
+ * on which the two differ, and that they agree everywhere else.
42
+ */
43
+ export { POINTER_PRIORITY, pickActivePointer, pointerVisualsFor, type ActivePointerKind, type PointerCandidate, type PointerVisuals, } from "@realitycollective/webxr-input";
44
+ export interface NearPointerOptions {
45
+ /** Surface distance, metres, within which a fingertip starts hovering a target. IWSDK `enterHoverDistance` 0.15. */
46
+ touchHoverEnter: number;
47
+ /** Surface distance, metres, beyond which a hovering fingertip stops hovering. IWSDK `exitHoverDistance` 0.2. Never less than `touchHoverEnter`. */
48
+ touchHoverExit: number;
49
+ /** Surface distance, metres, at or under which a hovering fingertip presses, and above which it releases. IWSDK `downRadius` 0.02. */
50
+ touchDown: number;
51
+ /** Radius, metres, of the grip sphere that finds a grab candidate. `createGrabPointer` default 0.07. */
52
+ grabRadius: number;
53
+ }
54
+ /** IWSDK 1.0.0's constants: `TOUCH_HYSTERESIS`, `TOUCH_DEFAULTS.downRadius`, `createGrabPointer`'s radius. */
55
+ export declare const NEAR_POINTER_DEFAULTS: Readonly<NearPointerOptions>;
56
+ export declare function resolveNearPointerOptions(options?: Partial<NearPointerOptions>): NearPointerOptions;
57
+ /** What the touch machine reports after one frame. */
58
+ export interface TouchPointerUpdate {
59
+ /** The fingertip is hovering a target this frame (the touch pointer has a candidate). */
60
+ hovering: boolean;
61
+ /** The touch is pressing this frame. */
62
+ pressed: boolean;
63
+ /** True on the exact frame a press started. */
64
+ pressStarted: boolean;
65
+ /** True on the exact frame a press ended. */
66
+ pressEnded: boolean;
67
+ }
68
+ /**
69
+ * One source's touch pointer: hover with hysteresis, press at `touchDown`
70
+ * armed only from outside the band. Feed it the fingertip's surface distance
71
+ * to the nearest target each frame, or `null` when no target is in reach.
72
+ */
73
+ export declare class TouchPointerState {
74
+ private hovering;
75
+ private pressed;
76
+ /** The fingertip has been seen outside the press band since it last lost contact. */
77
+ private armed;
78
+ get isHovering(): boolean;
79
+ get isPressed(): boolean;
80
+ /**
81
+ * Advance one frame. `enter`/`exit` are the hover band for the target in
82
+ * question (an interactable may widen the default), `down` the press
83
+ * distance.
84
+ */
85
+ update(distance: number | null, enter: number, exit: number, down: number): TouchPointerUpdate;
86
+ /** Drop everything without reporting (the source went away). */
87
+ reset(): void;
88
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Near pointers - the touch press, the grip grab, which pointer owns a hand
3
+ * and what that means for the ray and cursor. Pure logic, no engine.
4
+ *
5
+ * The template is IWSDK 1.0.0's pointer stack (`@iwsdk/xr-input`
6
+ * `multi-pointer.js`, `touch-pointer.js`, `grab-pointer.js`, over
7
+ * `@pmndrs/pointer-events`), restated here so every binding runs the same
8
+ * rule from the facts it already reports (`indexTip`, `gripPose`, `ray`):
9
+ *
10
+ * - **Touch.** A fingertip (a controller's ray origin stands in for one)
11
+ * hovers a target once its distance to the target's SURFACE is within
12
+ * `touchHoverEnter` (0.15 m) and keeps hovering until it is beyond
13
+ * `touchHoverExit` (0.2 m): `TOUCH_HYSTERESIS`. It presses while that
14
+ * distance is at or under `touchDown` (0.02 m): `TOUCH_DEFAULTS.downRadius`,
15
+ * which `@pmndrs/pointer-events` compares every frame, so the release is
16
+ * the way out past the same distance.
17
+ * - **Grab.** The grip is a sphere of `grabRadius` (0.07 m,
18
+ * `createGrabPointer`'s default) and a target whose surface it reaches is
19
+ * the grab candidate. The squeeze (or a hand's pinch on a grab-only
20
+ * target) then grabs it.
21
+ * - **Priority.** Per source, the first of touch, grab, ray with a candidate
22
+ * is the active pointer (`PRIORITY_ORDER`), and once it is pressing or
23
+ * grabbing it stays active until the release (IWSDK's selection lock).
24
+ * Since 29 September 2026 that decision is made ONCE per source across
25
+ * interactables and UI panels by the `PointerArbiter` in
26
+ * `@realitycollective/webxr-input` (`pointer-arbiter.ts`), which this
27
+ * runtime feeds and reads; `POINTER_PRIORITY`, `pickActivePointer`,
28
+ * `PointerCandidate` and `PointerVisuals` live there and are re-exported
29
+ * here unchanged.
30
+ * - **Visuals.** The ray and its cursor show only while the ray is the active
31
+ * pointer or nothing is (`shouldHideRay`); a near pointer owning the hand
32
+ * hides them, and the shared cursor sits at the active pointer's hit. What
33
+ * the app then shows of them is its `PointerDisplay` configuration.
34
+ *
35
+ * One rule is deliberately stricter than the template, as the Pale Signal
36
+ * work order of 28 September 2026 required: a touch never presses from inside
37
+ * or from behind. A fingertip first seen already within the press band, a
38
+ * hand that pops into tracking inside the target, does not press until it has
39
+ * been seen outside the band and comes back in. IWSDK's touch pointer would
40
+ * press on that first frame; `touch-reference.test.ts` records the one frame
41
+ * on which the two differ, and that they agree everywhere else.
42
+ */
43
+ export { POINTER_PRIORITY, pickActivePointer, pointerVisualsFor, } from "@realitycollective/webxr-input";
44
+ /** IWSDK 1.0.0's constants: `TOUCH_HYSTERESIS`, `TOUCH_DEFAULTS.downRadius`, `createGrabPointer`'s radius. */
45
+ export const NEAR_POINTER_DEFAULTS = Object.freeze({
46
+ touchHoverEnter: 0.15,
47
+ touchHoverExit: 0.2,
48
+ touchDown: 0.02,
49
+ grabRadius: 0.07,
50
+ });
51
+ export function resolveNearPointerOptions(options = {}) {
52
+ const resolved = { ...NEAR_POINTER_DEFAULTS, ...options };
53
+ if (resolved.touchDown < 0 || resolved.touchHoverEnter < 0 || resolved.grabRadius < 0) {
54
+ throw new Error("[webxr-interactions] near pointer distances must be >= 0");
55
+ }
56
+ if (resolved.touchHoverExit < resolved.touchHoverEnter) {
57
+ throw new Error(`[webxr-interactions] touchHoverExit (${resolved.touchHoverExit}) must be >= touchHoverEnter (${resolved.touchHoverEnter})`);
58
+ }
59
+ return resolved;
60
+ }
61
+ /**
62
+ * One source's touch pointer: hover with hysteresis, press at `touchDown`
63
+ * armed only from outside the band. Feed it the fingertip's surface distance
64
+ * to the nearest target each frame, or `null` when no target is in reach.
65
+ */
66
+ export class TouchPointerState {
67
+ hovering = false;
68
+ pressed = false;
69
+ /** The fingertip has been seen outside the press band since it last lost contact. */
70
+ armed = false;
71
+ get isHovering() {
72
+ return this.hovering;
73
+ }
74
+ get isPressed() {
75
+ return this.pressed;
76
+ }
77
+ /**
78
+ * Advance one frame. `enter`/`exit` are the hover band for the target in
79
+ * question (an interactable may widen the default), `down` the press
80
+ * distance.
81
+ */
82
+ update(distance, enter, exit, down) {
83
+ const wasPressed = this.pressed;
84
+ const threshold = this.hovering ? exit : enter;
85
+ if (distance === null || distance > threshold) {
86
+ this.hovering = false;
87
+ this.pressed = false;
88
+ this.armed = false;
89
+ return { hovering: false, pressed: false, pressStarted: false, pressEnded: wasPressed };
90
+ }
91
+ this.hovering = true;
92
+ if (distance > down) {
93
+ // Outside the press band: released if it was pressing, and armed for the next press.
94
+ this.armed = true;
95
+ this.pressed = false;
96
+ return { hovering: true, pressed: false, pressStarted: false, pressEnded: wasPressed };
97
+ }
98
+ // Within the press band. IWSDK presses here unconditionally; the core
99
+ // requires the fingertip to have arrived from outside (see the file comment).
100
+ if (!this.pressed && this.armed) {
101
+ this.pressed = true;
102
+ this.armed = false;
103
+ return { hovering: true, pressed: true, pressStarted: true, pressEnded: false };
104
+ }
105
+ return { hovering: true, pressed: this.pressed, pressStarted: false, pressEnded: false };
106
+ }
107
+ /** Drop everything without reporting (the source went away). */
108
+ reset() {
109
+ this.hovering = false;
110
+ this.pressed = false;
111
+ this.armed = false;
112
+ }
113
+ }
114
+ //# sourceMappingURL=near-pointer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-pointer.js","sourceRoot":"","sources":["../src/near-pointer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Near pointers - the touch press, the grip grab, which pointer owns a hand\n * and what that means for the ray and cursor. Pure logic, no engine.\n *\n * The template is IWSDK 1.0.0's pointer stack (`@iwsdk/xr-input`\n * `multi-pointer.js`, `touch-pointer.js`, `grab-pointer.js`, over\n * `@pmndrs/pointer-events`), restated here so every binding runs the same\n * rule from the facts it already reports (`indexTip`, `gripPose`, `ray`):\n *\n * - **Touch.** A fingertip (a controller's ray origin stands in for one)\n * hovers a target once its distance to the target's SURFACE is within\n * `touchHoverEnter` (0.15 m) and keeps hovering until it is beyond\n * `touchHoverExit` (0.2 m): `TOUCH_HYSTERESIS`. It presses while that\n * distance is at or under `touchDown` (0.02 m): `TOUCH_DEFAULTS.downRadius`,\n * which `@pmndrs/pointer-events` compares every frame, so the release is\n * the way out past the same distance.\n * - **Grab.** The grip is a sphere of `grabRadius` (0.07 m,\n * `createGrabPointer`'s default) and a target whose surface it reaches is\n * the grab candidate. The squeeze (or a hand's pinch on a grab-only\n * target) then grabs it.\n * - **Priority.** Per source, the first of touch, grab, ray with a candidate\n * is the active pointer (`PRIORITY_ORDER`), and once it is pressing or\n * grabbing it stays active until the release (IWSDK's selection lock).\n * Since 29 September 2026 that decision is made ONCE per source across\n * interactables and UI panels by the `PointerArbiter` in\n * `@realitycollective/webxr-input` (`pointer-arbiter.ts`), which this\n * runtime feeds and reads; `POINTER_PRIORITY`, `pickActivePointer`,\n * `PointerCandidate` and `PointerVisuals` live there and are re-exported\n * here unchanged.\n * - **Visuals.** The ray and its cursor show only while the ray is the active\n * pointer or nothing is (`shouldHideRay`); a near pointer owning the hand\n * hides them, and the shared cursor sits at the active pointer's hit. What\n * the app then shows of them is its `PointerDisplay` configuration.\n *\n * One rule is deliberately stricter than the template, as the Pale Signal\n * work order of 28 September 2026 required: a touch never presses from inside\n * or from behind. A fingertip first seen already within the press band, a\n * hand that pops into tracking inside the target, does not press until it has\n * been seen outside the band and comes back in. IWSDK's touch pointer would\n * press on that first frame; `touch-reference.test.ts` records the one frame\n * on which the two differ, and that they agree everywhere else.\n */\nexport {\n POINTER_PRIORITY,\n pickActivePointer,\n pointerVisualsFor,\n type ActivePointerKind,\n type PointerCandidate,\n type PointerVisuals,\n} from \"@realitycollective/webxr-input\";\n\nexport interface NearPointerOptions {\n /** Surface distance, metres, within which a fingertip starts hovering a target. IWSDK `enterHoverDistance` 0.15. */\n touchHoverEnter: number;\n /** Surface distance, metres, beyond which a hovering fingertip stops hovering. IWSDK `exitHoverDistance` 0.2. Never less than `touchHoverEnter`. */\n touchHoverExit: number;\n /** Surface distance, metres, at or under which a hovering fingertip presses, and above which it releases. IWSDK `downRadius` 0.02. */\n touchDown: number;\n /** Radius, metres, of the grip sphere that finds a grab candidate. `createGrabPointer` default 0.07. */\n grabRadius: number;\n}\n\n/** IWSDK 1.0.0's constants: `TOUCH_HYSTERESIS`, `TOUCH_DEFAULTS.downRadius`, `createGrabPointer`'s radius. */\nexport const NEAR_POINTER_DEFAULTS: Readonly<NearPointerOptions> = Object.freeze({\n touchHoverEnter: 0.15,\n touchHoverExit: 0.2,\n touchDown: 0.02,\n grabRadius: 0.07,\n});\n\nexport function resolveNearPointerOptions(options: Partial<NearPointerOptions> = {}): NearPointerOptions {\n const resolved = { ...NEAR_POINTER_DEFAULTS, ...options };\n if (resolved.touchDown < 0 || resolved.touchHoverEnter < 0 || resolved.grabRadius < 0) {\n throw new Error(\"[webxr-interactions] near pointer distances must be >= 0\");\n }\n if (resolved.touchHoverExit < resolved.touchHoverEnter) {\n throw new Error(\n `[webxr-interactions] touchHoverExit (${resolved.touchHoverExit}) must be >= touchHoverEnter (${resolved.touchHoverEnter})`,\n );\n }\n return resolved;\n}\n\n/** What the touch machine reports after one frame. */\nexport interface TouchPointerUpdate {\n /** The fingertip is hovering a target this frame (the touch pointer has a candidate). */\n hovering: boolean;\n /** The touch is pressing this frame. */\n pressed: boolean;\n /** True on the exact frame a press started. */\n pressStarted: boolean;\n /** True on the exact frame a press ended. */\n pressEnded: boolean;\n}\n\n/**\n * One source's touch pointer: hover with hysteresis, press at `touchDown`\n * armed only from outside the band. Feed it the fingertip's surface distance\n * to the nearest target each frame, or `null` when no target is in reach.\n */\nexport class TouchPointerState {\n private hovering = false;\n private pressed = false;\n /** The fingertip has been seen outside the press band since it last lost contact. */\n private armed = false;\n\n get isHovering(): boolean {\n return this.hovering;\n }\n\n get isPressed(): boolean {\n return this.pressed;\n }\n\n /**\n * Advance one frame. `enter`/`exit` are the hover band for the target in\n * question (an interactable may widen the default), `down` the press\n * distance.\n */\n update(distance: number | null, enter: number, exit: number, down: number): TouchPointerUpdate {\n const wasPressed = this.pressed;\n const threshold = this.hovering ? exit : enter;\n if (distance === null || distance > threshold) {\n this.hovering = false;\n this.pressed = false;\n this.armed = false;\n return { hovering: false, pressed: false, pressStarted: false, pressEnded: wasPressed };\n }\n this.hovering = true;\n if (distance > down) {\n // Outside the press band: released if it was pressing, and armed for the next press.\n this.armed = true;\n this.pressed = false;\n return { hovering: true, pressed: false, pressStarted: false, pressEnded: wasPressed };\n }\n // Within the press band. IWSDK presses here unconditionally; the core\n // requires the fingertip to have arrived from outside (see the file comment).\n if (!this.pressed && this.armed) {\n this.pressed = true;\n this.armed = false;\n return { hovering: true, pressed: true, pressStarted: true, pressEnded: false };\n }\n return { hovering: true, pressed: this.pressed, pressStarted: false, pressEnded: false };\n }\n\n /** Drop everything without reporting (the source went away). */\n reset(): void {\n this.hovering = false;\n this.pressed = false;\n this.armed = false;\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { type PhysicsFacility } from "./physics.js";
2
+ /** The facility under test. Build a FRESH one per case. */
3
+ export interface PhysicsFacilityContractSubject {
4
+ facility: PhysicsFacility;
5
+ }
6
+ /** One check a {@link PhysicsFacility} implementation must pass. */
7
+ export interface PhysicsFacilityContractCase {
8
+ name: string;
9
+ run(subject: PhysicsFacilityContractSubject): void;
10
+ }
11
+ export declare function physicsFacilityContractCases(): readonly PhysicsFacilityContractCase[];