@rapierphysicsplugin/client 1.0.0

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 (83) hide show
  1. package/dist/__tests__/clock-sync.test.d.ts +2 -0
  2. package/dist/__tests__/clock-sync.test.d.ts.map +1 -0
  3. package/dist/__tests__/clock-sync.test.js +63 -0
  4. package/dist/__tests__/clock-sync.test.js.map +1 -0
  5. package/dist/__tests__/interpolator.test.d.ts +2 -0
  6. package/dist/__tests__/interpolator.test.d.ts.map +1 -0
  7. package/dist/__tests__/interpolator.test.js +82 -0
  8. package/dist/__tests__/interpolator.test.js.map +1 -0
  9. package/dist/__tests__/state-reconciler.test.d.ts +2 -0
  10. package/dist/__tests__/state-reconciler.test.d.ts.map +1 -0
  11. package/dist/__tests__/state-reconciler.test.js +86 -0
  12. package/dist/__tests__/state-reconciler.test.js.map +1 -0
  13. package/dist/clock-sync.d.ts +17 -0
  14. package/dist/clock-sync.d.ts.map +1 -0
  15. package/dist/clock-sync.js +63 -0
  16. package/dist/clock-sync.js.map +1 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +8 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/input-manager.d.ts +18 -0
  22. package/dist/input-manager.d.ts.map +1 -0
  23. package/dist/input-manager.js +62 -0
  24. package/dist/input-manager.js.map +1 -0
  25. package/dist/interpolator.d.ts +35 -0
  26. package/dist/interpolator.d.ts.map +1 -0
  27. package/dist/interpolator.js +198 -0
  28. package/dist/interpolator.js.map +1 -0
  29. package/dist/networked-rapier-plugin.d.ts +82 -0
  30. package/dist/networked-rapier-plugin.d.ts.map +1 -0
  31. package/dist/networked-rapier-plugin.js +698 -0
  32. package/dist/networked-rapier-plugin.js.map +1 -0
  33. package/dist/rapier-body-ops.d.ts +27 -0
  34. package/dist/rapier-body-ops.d.ts.map +1 -0
  35. package/dist/rapier-body-ops.js +208 -0
  36. package/dist/rapier-body-ops.js.map +1 -0
  37. package/dist/rapier-collision-ops.d.ts +6 -0
  38. package/dist/rapier-collision-ops.d.ts.map +1 -0
  39. package/dist/rapier-collision-ops.js +200 -0
  40. package/dist/rapier-collision-ops.js.map +1 -0
  41. package/dist/rapier-constraint-ops.d.ts +29 -0
  42. package/dist/rapier-constraint-ops.d.ts.map +1 -0
  43. package/dist/rapier-constraint-ops.js +286 -0
  44. package/dist/rapier-constraint-ops.js.map +1 -0
  45. package/dist/rapier-plugin.d.ts +145 -0
  46. package/dist/rapier-plugin.d.ts.map +1 -0
  47. package/dist/rapier-plugin.js +263 -0
  48. package/dist/rapier-plugin.js.map +1 -0
  49. package/dist/rapier-shape-ops.d.ts +21 -0
  50. package/dist/rapier-shape-ops.d.ts.map +1 -0
  51. package/dist/rapier-shape-ops.js +314 -0
  52. package/dist/rapier-shape-ops.js.map +1 -0
  53. package/dist/rapier-types.d.ts +58 -0
  54. package/dist/rapier-types.d.ts.map +1 -0
  55. package/dist/rapier-types.js +4 -0
  56. package/dist/rapier-types.js.map +1 -0
  57. package/dist/state-reconciler.d.ts +28 -0
  58. package/dist/state-reconciler.d.ts.map +1 -0
  59. package/dist/state-reconciler.js +119 -0
  60. package/dist/state-reconciler.js.map +1 -0
  61. package/dist/sync-client.d.ts +110 -0
  62. package/dist/sync-client.d.ts.map +1 -0
  63. package/dist/sync-client.js +514 -0
  64. package/dist/sync-client.js.map +1 -0
  65. package/package.json +21 -0
  66. package/src/__tests__/clock-sync.test.ts +72 -0
  67. package/src/__tests__/interpolator.test.ts +98 -0
  68. package/src/__tests__/state-reconciler.test.ts +102 -0
  69. package/src/clock-sync.ts +77 -0
  70. package/src/index.ts +9 -0
  71. package/src/input-manager.ts +72 -0
  72. package/src/interpolator.ts +256 -0
  73. package/src/networked-rapier-plugin.ts +909 -0
  74. package/src/rapier-body-ops.ts +251 -0
  75. package/src/rapier-collision-ops.ts +229 -0
  76. package/src/rapier-constraint-ops.ts +327 -0
  77. package/src/rapier-plugin.ts +364 -0
  78. package/src/rapier-shape-ops.ts +369 -0
  79. package/src/rapier-types.ts +60 -0
  80. package/src/state-reconciler.ts +151 -0
  81. package/src/sync-client.ts +640 -0
  82. package/tsconfig.json +12 -0
  83. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=clock-sync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-sync.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/clock-sync.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { ClockSyncClient } from '../clock-sync.js';
3
+ import { MessageType } from '@rapierphysicsplugin/shared';
4
+ describe('ClockSyncClient', () => {
5
+ let clockSync;
6
+ beforeEach(() => {
7
+ clockSync = new ClockSyncClient();
8
+ });
9
+ it('should start uncalibrated', () => {
10
+ expect(clockSync.isCalibrated).toBe(false);
11
+ expect(clockSync.getRTT()).toBe(0);
12
+ expect(clockSync.getClockOffset()).toBe(0);
13
+ });
14
+ it('should calculate RTT from response', () => {
15
+ const clientTimestamp = Date.now() - 50; // Simulate 50ms ago
16
+ clockSync.handleResponse({
17
+ type: MessageType.CLOCK_SYNC_RESPONSE,
18
+ clientTimestamp,
19
+ serverTimestamp: clientTimestamp + 25, // Server received ~25ms after client sent
20
+ });
21
+ const rtt = clockSync.getRTT();
22
+ expect(rtt).toBeGreaterThan(0);
23
+ expect(rtt).toBeLessThan(200); // Reasonable RTT
24
+ });
25
+ it('should become calibrated after 3 samples', () => {
26
+ for (let i = 0; i < 3; i++) {
27
+ const clientTimestamp = Date.now() - 50;
28
+ clockSync.handleResponse({
29
+ type: MessageType.CLOCK_SYNC_RESPONSE,
30
+ clientTimestamp,
31
+ serverTimestamp: clientTimestamp + 25,
32
+ });
33
+ }
34
+ expect(clockSync.isCalibrated).toBe(true);
35
+ });
36
+ it('should compute rolling average RTT', () => {
37
+ // Send several responses with known RTTs
38
+ for (let i = 0; i < 5; i++) {
39
+ const clientTimestamp = Date.now() - 100;
40
+ clockSync.handleResponse({
41
+ type: MessageType.CLOCK_SYNC_RESPONSE,
42
+ clientTimestamp,
43
+ serverTimestamp: clientTimestamp + 50,
44
+ });
45
+ }
46
+ const rtt = clockSync.getRTT();
47
+ // RTT should be around 100ms (now - clientTimestamp)
48
+ expect(rtt).toBeGreaterThan(50);
49
+ expect(rtt).toBeLessThan(300);
50
+ });
51
+ it('should get server time estimate', () => {
52
+ const now = Date.now();
53
+ clockSync.handleResponse({
54
+ type: MessageType.CLOCK_SYNC_RESPONSE,
55
+ clientTimestamp: now - 50,
56
+ serverTimestamp: now - 25,
57
+ });
58
+ const serverTime = clockSync.getServerTime();
59
+ // Server time should be close to current time
60
+ expect(Math.abs(serverTime - Date.now())).toBeLessThan(200);
61
+ });
62
+ });
63
+ //# sourceMappingURL=clock-sync.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-sync.test.js","sourceRoot":"","sources":["../../src/__tests__/clock-sync.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interpolator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/interpolator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Interpolator } from '../interpolator.js';
3
+ function makeState(id, x, y, z, vx = 0, vy = 0, vz = 0) {
4
+ return {
5
+ id,
6
+ position: { x, y, z },
7
+ rotation: { x: 0, y: 0, z: 0, w: 1 },
8
+ linVel: { x: vx, y: vy, z: vz },
9
+ angVel: { x: 0, y: 0, z: 0 },
10
+ };
11
+ }
12
+ describe('Interpolator', () => {
13
+ let interpolator;
14
+ beforeEach(() => {
15
+ // Use 0ms render delay for deterministic testing
16
+ interpolator = new Interpolator(0);
17
+ });
18
+ it('should return null for unknown bodies', () => {
19
+ expect(interpolator.getInterpolatedState('unknown', Date.now())).toBeNull();
20
+ });
21
+ it('should return earliest state when render time is before all snapshots', () => {
22
+ interpolator.addSnapshot('body1', makeState('body1', 0, 0, 0), 1000);
23
+ interpolator.addSnapshot('body1', makeState('body1', 10, 0, 0), 2000);
24
+ const state = interpolator.getInterpolatedState('body1', 500);
25
+ expect(state).not.toBeNull();
26
+ expect(state.position.x).toBeCloseTo(0);
27
+ });
28
+ it('should interpolate between two snapshots', () => {
29
+ interpolator.addSnapshot('body1', makeState('body1', 0, 0, 0), 1000);
30
+ interpolator.addSnapshot('body1', makeState('body1', 10, 0, 0), 2000);
31
+ // Midpoint
32
+ const state = interpolator.getInterpolatedState('body1', 1500);
33
+ expect(state).not.toBeNull();
34
+ // Hermite interpolation at t=0.5 with zero velocities should give midpoint
35
+ expect(state.position.x).toBeCloseTo(5, 0);
36
+ });
37
+ it('should extrapolate after last snapshot', () => {
38
+ interpolator.addSnapshot('body1', makeState('body1', 0, 0, 0, 10, 0, 0), 1000);
39
+ // 0.1 seconds after the last snapshot
40
+ const state = interpolator.getInterpolatedState('body1', 1100);
41
+ expect(state).not.toBeNull();
42
+ // Should move in the x direction due to velocity
43
+ expect(state.position.x).toBeGreaterThan(0);
44
+ });
45
+ it('should interpolate rotation via slerp', () => {
46
+ const state1 = {
47
+ id: 'body1',
48
+ position: { x: 0, y: 0, z: 0 },
49
+ rotation: { x: 0, y: 0, z: 0, w: 1 }, // identity
50
+ linVel: { x: 0, y: 0, z: 0 },
51
+ angVel: { x: 0, y: 0, z: 0 },
52
+ };
53
+ const state2 = {
54
+ id: 'body1',
55
+ position: { x: 0, y: 0, z: 0 },
56
+ rotation: { x: 0, y: 0.7071, z: 0, w: 0.7071 }, // 90deg around Y
57
+ linVel: { x: 0, y: 0, z: 0 },
58
+ angVel: { x: 0, y: 0, z: 0 },
59
+ };
60
+ interpolator.addSnapshot('body1', state1, 1000);
61
+ interpolator.addSnapshot('body1', state2, 2000);
62
+ const result = interpolator.getInterpolatedState('body1', 1500);
63
+ expect(result).not.toBeNull();
64
+ // Quaternion magnitude should be ~1
65
+ const q = result.rotation;
66
+ const mag = Math.sqrt(q.x ** 2 + q.y ** 2 + q.z ** 2 + q.w ** 2);
67
+ expect(mag).toBeCloseTo(1, 2);
68
+ });
69
+ it('should remove a body', () => {
70
+ interpolator.addSnapshot('body1', makeState('body1', 0, 0, 0), 1000);
71
+ interpolator.removeBody('body1');
72
+ expect(interpolator.getInterpolatedState('body1', 1000)).toBeNull();
73
+ });
74
+ it('should clear all data', () => {
75
+ interpolator.addSnapshot('body1', makeState('body1', 0, 0, 0), 1000);
76
+ interpolator.addSnapshot('body2', makeState('body2', 5, 5, 5), 1000);
77
+ interpolator.clear();
78
+ expect(interpolator.getInterpolatedState('body1', 1000)).toBeNull();
79
+ expect(interpolator.getInterpolatedState('body2', 1000)).toBeNull();
80
+ });
81
+ });
82
+ //# sourceMappingURL=interpolator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolator.test.js","sourceRoot":"","sources":["../../src/__tests__/interpolator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state-reconciler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-reconciler.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/state-reconciler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { StateReconciler, needsCorrection, blendBodyState } from '../state-reconciler.js';
3
+ function makeState(id, x, y, z) {
4
+ return {
5
+ id,
6
+ position: { x, y, z },
7
+ rotation: { x: 0, y: 0, z: 0, w: 1 },
8
+ linVel: { x: 0, y: 0, z: 0 },
9
+ angVel: { x: 0, y: 0, z: 0 },
10
+ };
11
+ }
12
+ describe('StateReconciler', () => {
13
+ let reconciler;
14
+ beforeEach(() => {
15
+ reconciler = new StateReconciler();
16
+ });
17
+ it('should separate local and remote bodies', () => {
18
+ reconciler.addLocalBody('player1');
19
+ const timestamp = Date.now();
20
+ const snapshot = {
21
+ tick: 10,
22
+ timestamp,
23
+ bodies: [
24
+ makeState('player1', 1, 2, 3),
25
+ makeState('enemy1', 4, 5, 6),
26
+ ],
27
+ };
28
+ const result = reconciler.processServerState(snapshot);
29
+ // Local body goes to corrections
30
+ expect(result.localCorrections.has('player1')).toBe(true);
31
+ // Remote body is fed to interpolator — query it at render time
32
+ const interpolated = reconciler.getInterpolatedRemoteState('enemy1', timestamp);
33
+ expect(interpolated).not.toBeNull();
34
+ expect(interpolated.position.x).toBeCloseTo(4, 1);
35
+ });
36
+ it('should discard old pending inputs', () => {
37
+ reconciler.addPendingInput({ tick: 5, sequenceNum: 0, actions: [] });
38
+ reconciler.addPendingInput({ tick: 10, sequenceNum: 1, actions: [] });
39
+ reconciler.addPendingInput({ tick: 15, sequenceNum: 2, actions: [] });
40
+ reconciler.processServerState({
41
+ tick: 10,
42
+ timestamp: Date.now(),
43
+ bodies: [],
44
+ });
45
+ const pending = reconciler.getPendingInputs();
46
+ expect(pending).toHaveLength(1);
47
+ expect(pending[0].tick).toBe(15);
48
+ });
49
+ it('should track last server tick', () => {
50
+ reconciler.processServerState({
51
+ tick: 42,
52
+ timestamp: Date.now(),
53
+ bodies: [],
54
+ });
55
+ expect(reconciler.lastProcessedServerTick).toBe(42);
56
+ });
57
+ it('should clear all state', () => {
58
+ reconciler.addLocalBody('player1');
59
+ reconciler.addPendingInput({ tick: 5, sequenceNum: 0, actions: [] });
60
+ reconciler.clear();
61
+ expect(reconciler.getPendingInputs()).toHaveLength(0);
62
+ expect(reconciler.lastProcessedServerTick).toBe(0);
63
+ });
64
+ });
65
+ describe('needsCorrection', () => {
66
+ it('should return false for small differences', () => {
67
+ const a = makeState('body1', 1, 2, 3);
68
+ const b = makeState('body1', 1.01, 2.01, 3.01);
69
+ expect(needsCorrection(a, b)).toBe(false);
70
+ });
71
+ it('should return true for large differences', () => {
72
+ const a = makeState('body1', 1, 2, 3);
73
+ const b = makeState('body1', 2, 2, 3);
74
+ expect(needsCorrection(a, b)).toBe(true);
75
+ });
76
+ });
77
+ describe('blendBodyState', () => {
78
+ it('should blend positions toward target', () => {
79
+ const current = makeState('body1', 0, 0, 0);
80
+ const target = makeState('body1', 10, 0, 0);
81
+ const blended = blendBodyState(current, target);
82
+ expect(blended.position.x).toBeGreaterThan(0);
83
+ expect(blended.position.x).toBeLessThan(10);
84
+ });
85
+ });
86
+ //# sourceMappingURL=state-reconciler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-reconciler.test.js","sourceRoot":"","sources":["../../src/__tests__/state-reconciler.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ClockSyncResponseMessage } from '@rapierphysicsplugin/shared';
2
+ export declare class ClockSyncClient {
3
+ private rttSamples;
4
+ private offsetSamples;
5
+ private intervalId;
6
+ private sendFn;
7
+ start(sendFn: (data: Uint8Array) => void): void;
8
+ stop(): void;
9
+ private sendSyncRequest;
10
+ handleResponse(message: ClockSyncResponseMessage): void;
11
+ getRTT(): number;
12
+ getClockOffset(): number;
13
+ getServerTime(): number;
14
+ getServerTick(): number;
15
+ get isCalibrated(): boolean;
16
+ }
17
+ //# sourceMappingURL=clock-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-sync.d.ts","sourceRoot":"","sources":["../src/clock-sync.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAE5E,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,MAAM,CAA6C;IAE3D,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAM/C,IAAI,IAAI,IAAI;IAQZ,OAAO,CAAC,eAAe;IAQvB,cAAc,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI;IAgBvD,MAAM,IAAI,MAAM;IAKhB,cAAc,IAAI,MAAM;IAKxB,aAAa,IAAI,MAAM;IAIvB,aAAa,IAAI,MAAM;IAKvB,IAAI,YAAY,IAAI,OAAO,CAE1B;CACF"}
@@ -0,0 +1,63 @@
1
+ import { MessageType, CLOCK_SYNC_INTERVAL_MS, CLOCK_SYNC_SAMPLES, FIXED_TIMESTEP, encodeMessage, } from '@rapierphysicsplugin/shared';
2
+ export class ClockSyncClient {
3
+ constructor() {
4
+ this.rttSamples = [];
5
+ this.offsetSamples = [];
6
+ this.intervalId = null;
7
+ this.sendFn = null;
8
+ }
9
+ start(sendFn) {
10
+ this.sendFn = sendFn;
11
+ this.sendSyncRequest();
12
+ this.intervalId = setInterval(() => this.sendSyncRequest(), CLOCK_SYNC_INTERVAL_MS);
13
+ }
14
+ stop() {
15
+ if (this.intervalId) {
16
+ clearInterval(this.intervalId);
17
+ this.intervalId = null;
18
+ }
19
+ this.sendFn = null;
20
+ }
21
+ sendSyncRequest() {
22
+ if (!this.sendFn)
23
+ return;
24
+ this.sendFn(encodeMessage({
25
+ type: MessageType.CLOCK_SYNC_REQUEST,
26
+ clientTimestamp: Date.now(),
27
+ }));
28
+ }
29
+ handleResponse(message) {
30
+ const now = Date.now();
31
+ const rtt = now - message.clientTimestamp;
32
+ const offset = message.serverTimestamp - message.clientTimestamp - rtt / 2;
33
+ this.rttSamples.push(rtt);
34
+ if (this.rttSamples.length > CLOCK_SYNC_SAMPLES) {
35
+ this.rttSamples.shift();
36
+ }
37
+ this.offsetSamples.push(offset);
38
+ if (this.offsetSamples.length > CLOCK_SYNC_SAMPLES) {
39
+ this.offsetSamples.shift();
40
+ }
41
+ }
42
+ getRTT() {
43
+ if (this.rttSamples.length === 0)
44
+ return 0;
45
+ return this.rttSamples.reduce((a, b) => a + b, 0) / this.rttSamples.length;
46
+ }
47
+ getClockOffset() {
48
+ if (this.offsetSamples.length === 0)
49
+ return 0;
50
+ return this.offsetSamples.reduce((a, b) => a + b, 0) / this.offsetSamples.length;
51
+ }
52
+ getServerTime() {
53
+ return Date.now() + this.getClockOffset();
54
+ }
55
+ getServerTick() {
56
+ const serverTimeMs = this.getServerTime();
57
+ return Math.floor(serverTimeMs / (FIXED_TIMESTEP * 1000));
58
+ }
59
+ get isCalibrated() {
60
+ return this.rttSamples.length >= 3;
61
+ }
62
+ }
63
+ //# sourceMappingURL=clock-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-sync.js","sourceRoot":"","sources":["../src/clock-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export { PhysicsSyncClient } from './sync-client.js';
2
+ export { RapierPlugin } from './rapier-plugin.js';
3
+ export { NetworkedRapierPlugin } from './networked-rapier-plugin.js';
4
+ export type { NetworkedRapierPluginConfig } from './networked-rapier-plugin.js';
5
+ export { ClockSyncClient } from './clock-sync.js';
6
+ export { StateReconciler, needsCorrection, blendBodyState } from './state-reconciler.js';
7
+ export { Interpolator } from './interpolator.js';
8
+ export type { InterpolatorStats } from './interpolator.js';
9
+ export { InputManager } from './input-manager.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { PhysicsSyncClient } from './sync-client.js';
2
+ export { RapierPlugin } from './rapier-plugin.js';
3
+ export { NetworkedRapierPlugin } from './networked-rapier-plugin.js';
4
+ export { ClockSyncClient } from './clock-sync.js';
5
+ export { StateReconciler, needsCorrection, blendBodyState } from './state-reconciler.js';
6
+ export { Interpolator } from './interpolator.js';
7
+ export { InputManager } from './input-manager.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ClientInput, InputAction } from '@rapierphysicsplugin/shared';
2
+ export declare class InputManager {
3
+ private pendingActions;
4
+ private inputHistory;
5
+ private sequenceNum;
6
+ private currentTick;
7
+ private sendFn;
8
+ private intervalId;
9
+ start(sendFn: (input: ClientInput) => void, getTickFn: () => number): void;
10
+ stop(): void;
11
+ queueAction(action: InputAction): void;
12
+ flush(): void;
13
+ getInputHistory(): ClientInput[];
14
+ getInputsSince(tick: number): ClientInput[];
15
+ clearHistory(): void;
16
+ clear(): void;
17
+ }
18
+ //# sourceMappingURL=input-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-manager.d.ts","sourceRoot":"","sources":["../src/input-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG5E,qBAAa,YAAY;IACvB,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,UAAU,CAA+C;IAEjE,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,IAAI;IAU1E,IAAI,IAAI,IAAI;IAQZ,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAItC,KAAK,IAAI,IAAI;IAqBb,eAAe,IAAI,WAAW,EAAE;IAIhC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE;IAI3C,YAAY,IAAI,IAAI;IAIpB,KAAK,IAAI,IAAI;CAKd"}
@@ -0,0 +1,62 @@
1
+ import { MAX_INPUT_BUFFER, CLIENT_INPUT_RATE } from '@rapierphysicsplugin/shared';
2
+ export class InputManager {
3
+ constructor() {
4
+ this.pendingActions = [];
5
+ this.inputHistory = [];
6
+ this.sequenceNum = 0;
7
+ this.currentTick = 0;
8
+ this.sendFn = null;
9
+ this.intervalId = null;
10
+ }
11
+ start(sendFn, getTickFn) {
12
+ this.sendFn = sendFn;
13
+ const intervalMs = 1000 / CLIENT_INPUT_RATE;
14
+ this.intervalId = setInterval(() => {
15
+ this.currentTick = getTickFn();
16
+ this.flush();
17
+ }, intervalMs);
18
+ }
19
+ stop() {
20
+ if (this.intervalId) {
21
+ clearInterval(this.intervalId);
22
+ this.intervalId = null;
23
+ }
24
+ this.sendFn = null;
25
+ }
26
+ queueAction(action) {
27
+ this.pendingActions.push(action);
28
+ }
29
+ flush() {
30
+ if (this.pendingActions.length === 0)
31
+ return;
32
+ if (!this.sendFn)
33
+ return;
34
+ const input = {
35
+ tick: this.currentTick,
36
+ sequenceNum: this.sequenceNum++,
37
+ actions: [...this.pendingActions],
38
+ };
39
+ this.pendingActions = [];
40
+ // Store in history for reconciliation
41
+ this.inputHistory.push(input);
42
+ if (this.inputHistory.length > MAX_INPUT_BUFFER) {
43
+ this.inputHistory.shift();
44
+ }
45
+ this.sendFn(input);
46
+ }
47
+ getInputHistory() {
48
+ return this.inputHistory;
49
+ }
50
+ getInputsSince(tick) {
51
+ return this.inputHistory.filter(input => input.tick > tick);
52
+ }
53
+ clearHistory() {
54
+ this.inputHistory = [];
55
+ }
56
+ clear() {
57
+ this.pendingActions = [];
58
+ this.inputHistory = [];
59
+ this.sequenceNum = 0;
60
+ }
61
+ }
62
+ //# sourceMappingURL=input-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-manager.js","sourceRoot":"","sources":["../src/input-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAElF,MAAM,OAAO,YAAY;IAAzB;QACU,mBAAc,GAAkB,EAAE,CAAC;QACnC,iBAAY,GAAkB,EAAE,CAAC;QACjC,gBAAW,GAAG,CAAC,CAAC;QAChB,gBAAW,GAAG,CAAC,CAAC;QAChB,WAAM,GAA0C,IAAI,CAAC;QACrD,eAAU,GAA0C,IAAI,CAAC;IA8DnE,CAAC;IA5DC,KAAK,CAAC,MAAoC,EAAE,SAAuB;QACjE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,UAAU,GAAG,IAAI,GAAG,iBAAiB,CAAC;QAC5C,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YACjC,IAAI,CAAC,WAAW,GAAG,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,WAAW,CAAC,MAAmB;QAC7B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC7C,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEzB,MAAM,KAAK,GAAgB;YACzB,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE;YAC/B,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC;SAClC,CAAC;QAEF,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QAEzB,sCAAsC;QACtC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YAChD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,cAAc,CAAC,IAAY;QACzB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED,YAAY;QACV,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ import type { BodyState } from '@rapierphysicsplugin/shared';
2
+ export interface InterpolatorStats {
3
+ /** Bodies that had two bracketing snapshots — true interpolation */
4
+ interpolatedCount: number;
5
+ /** Bodies where renderTime was past all snapshots — velocity extrapolation */
6
+ extrapolatedCount: number;
7
+ /** Bodies where renderTime was before all snapshots — returned earliest */
8
+ staleCount: number;
9
+ /** Bodies with empty buffer — returned null */
10
+ emptyCount: number;
11
+ /** Render delay being used (ms) */
12
+ renderDelay: number;
13
+ /** Sample body diagnostics (first dynamic body seen) */
14
+ sampleBodyId: string | null;
15
+ sampleBufferLen: number;
16
+ sampleRenderTime: number;
17
+ sampleBufferOldest: number;
18
+ sampleBufferNewest: number;
19
+ sampleT: number;
20
+ }
21
+ export declare class Interpolator {
22
+ private buffers;
23
+ private renderDelay;
24
+ private _stats;
25
+ constructor(renderDelayMs?: number);
26
+ private _emptyStats;
27
+ /** Reset stats at the start of each render frame, then call getInterpolatedState per body */
28
+ resetStats(): void;
29
+ getStats(): InterpolatorStats;
30
+ addSnapshot(bodyId: string, state: BodyState, timestamp: number): void;
31
+ getInterpolatedState(bodyId: string, currentTime: number): BodyState | null;
32
+ removeBody(bodyId: string): void;
33
+ clear(): void;
34
+ }
35
+ //# sourceMappingURL=interpolator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolator.d.ts","sourceRoot":"","sources":["../src/interpolator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,6BAA6B,CAAC;AAQzE,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAyC;gBAE3C,aAAa,CAAC,EAAE,MAAM;IAKlC,OAAO,CAAC,WAAW;IAgBnB,6FAA6F;IAC7F,UAAU,IAAI,IAAI;IAKlB,QAAQ,IAAI,iBAAiB;IAI7B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAoBtE,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAwD3E,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,KAAK,IAAI,IAAI;CAGd"}