@woosh/meep-engine 2.146.0 → 2.148.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 (105) hide show
  1. package/package.json +1 -1
  2. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
  3. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
  4. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
  5. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
  6. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
  7. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
  8. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
  9. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  10. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
  11. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +0 -11
  12. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  13. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +8 -6
  14. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  15. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -551
  16. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
  17. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  18. package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
  19. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  20. package/src/engine/control/first-person/abilities/Mantle.js +195 -188
  21. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  22. package/src/engine/control/first-person/abilities/WallRun.js +183 -175
  23. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
  24. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  25. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
  26. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
  27. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  28. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
  29. package/src/engine/ecs/EntityManager.d.ts +34 -11
  30. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  31. package/src/engine/ecs/EntityManager.js +71 -42
  32. package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
  33. package/src/engine/interpolation/Interpoland.d.ts +48 -0
  34. package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
  35. package/src/engine/interpolation/Interpoland.js +49 -0
  36. package/src/engine/interpolation/Interpolated.d.ts +101 -0
  37. package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
  38. package/src/engine/interpolation/Interpolated.js +149 -0
  39. package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
  40. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
  41. package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
  42. package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
  43. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
  44. package/src/engine/interpolation/InterpolationSystem.js +233 -0
  45. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
  46. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
  47. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
  48. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
  49. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
  50. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
  51. package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
  52. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
  53. package/src/engine/interpolation/pose_interpoland.js +27 -0
  54. package/src/engine/network/NetworkSession.d.ts +2 -2
  55. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  56. package/src/engine/network/NetworkSession.js +2 -2
  57. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  58. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
  59. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  60. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  61. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
  62. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  63. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  64. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
  65. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  66. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
  67. package/src/engine/physics/PLAN.md +10 -9
  68. package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
  69. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
  70. package/src/engine/physics/body/SolverBodyState.js +251 -0
  71. package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
  72. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  73. package/src/engine/physics/broadphase/generate_pairs.js +5 -3
  74. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  75. package/src/engine/physics/constraint/solve_constraints.js +691 -673
  76. package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -15
  77. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  78. package/src/engine/physics/ecs/PhysicsSystem.js +387 -87
  79. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
  80. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  81. package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
  82. package/src/engine/physics/integration/integrate_position.d.ts +11 -1
  83. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  84. package/src/engine/physics/integration/integrate_position.js +97 -79
  85. package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
  86. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  87. package/src/engine/physics/integration/integrate_velocity.js +201 -160
  88. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  89. package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
  90. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  91. package/src/engine/physics/narrowphase/box_triangle_contact.js +19 -46
  92. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
  93. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
  94. package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
  95. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  96. package/src/engine/physics/narrowphase/narrowphase_step.js +52 -4
  97. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  98. package/src/engine/physics/queries/raycast.js +7 -4
  99. package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
  100. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  101. package/src/engine/physics/solver/solve_contacts.js +1341 -1173
  102. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
  103. package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
  104. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
  105. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
@@ -1,196 +1,229 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { Ray3 } from "../../../../core/geom/3d/ray/Ray3.js";
3
- import { PhysicsSystem } from "../../../physics/ecs/PhysicsSystem.js";
4
- import { PhysicsSurfacePoint } from "../../../physics/queries/PhysicsSurfacePoint.js";
5
- import { System } from "../../../ecs/System.js";
6
- import { Transform } from "../../../ecs/transform/Transform.js";
7
- import { FirstPersonPlayerController } from "../FirstPersonPlayerController.js";
8
- import { FirstPersonPlayerControllerSystem } from "../FirstPersonPlayerControllerSystem.js";
9
-
10
- /**
11
- * Populates the {@link FirstPersonSensors} struct on each controller
12
- * entity, once per fixed step. Designed to run BEFORE the main
13
- * {@link FirstPersonPlayerControllerSystem} `fixedUpdate` so abilities
14
- * see fresh sensor data on the same tick.
15
- *
16
- * Raycasts go through {@link PhysicsSystem.raycast} — BVH-backed,
17
- * static + dynamic broadphase. The player's own body is excluded via a
18
- * per-call filter so downward probes don't collide with the player's
19
- * own capsule.
20
- *
21
- * BVH raycasts are cheap (~1000/frame at 60Hz is comfortable), so this
22
- * system doesn't aggressively optimize. NPC controllers that don't
23
- * need sensors can simply not have this system attached.
24
- *
25
- * Sensor data lives at `runtime.sensors` on the
26
- * {@link FirstPersonPlayerControllerSystem}'s per-entity runtime.
27
- *
28
- * @author Alex Goldring
29
- * @copyright Company Named Limited (c) 2026
30
- */
31
- export class FirstPersonSensorsSystem extends System {
32
- constructor() {
33
- super();
34
-
35
- this.dependencies = [FirstPersonPlayerController, Transform];
36
-
37
- /**
38
- * Reference to the controller system — needed to look up each
39
- * entity's per-controller runtime (which owns the sensors slot).
40
- * Auto-acquired in startup; can be overridden by the caller.
41
- * @type {FirstPersonPlayerControllerSystem|null}
42
- */
43
- this.controllerSystem = null;
44
-
45
- /**
46
- * Physics system used as the spatial-query backend. Auto-acquired
47
- * in startup; can be overridden. Required — sensors are unusable
48
- * without it.
49
- * @type {PhysicsSystem|null}
50
- */
51
- this.physicsSystem = null;
52
-
53
- /**
54
- * Pre-allocated raycast query primitive. Refilled in place per
55
- * {@link _probeRay} call so the system doesn't allocate a new
56
- * `Ray3` every probe.
57
- * @private
58
- * @type {Ray3}
59
- */
60
- this._probe_ray = new Ray3();
61
-
62
- /**
63
- * Pre-allocated raycast result. Refilled in place per probe;
64
- * gameplay-visible sensor fields are copied out before the next
65
- * probe overwrites it.
66
- * @private
67
- * @type {PhysicsSurfacePoint}
68
- */
69
- this._probe_hit = new PhysicsSurfacePoint();
70
- }
71
-
72
- async startup(entityManager) {
73
- this.entityManager = entityManager;
74
-
75
- // Auto-acquire collaborators if they're already in the world.
76
- // Caller can still override either field before fixedUpdate runs.
77
- if (this.controllerSystem === null) {
78
- const cs = entityManager.getSystem(FirstPersonPlayerControllerSystem);
79
- if (cs !== null) this.controllerSystem = cs;
80
- }
81
- if (this.physicsSystem === null) {
82
- const ps = entityManager.getSystem(PhysicsSystem);
83
- if (ps !== null) this.physicsSystem = ps;
84
- }
85
- }
86
-
87
- fixedUpdate(dt) {
88
- const ecd = this.entityManager.dataset;
89
- if (ecd === null) return;
90
- ecd.traverseComponents(FirstPersonPlayerController, this._populateForEntity, this);
91
- }
92
-
93
- /**
94
- * @private
95
- */
96
- _populateForEntity(controller, entity) {
97
- // Sensors live on the per-entity runtime owned by the
98
- // controller's own system. Look it up by entity — no leakage
99
- // through the controller component itself.
100
- if (this.controllerSystem === null) return; // not wired up
101
- const runtime = this.controllerSystem.getRuntime(entity);
102
- if (runtime === undefined) return;
103
- const sensors = runtime.sensors;
104
- if (sensors === undefined || sensors === null) return;
105
-
106
- // Sensors are useless without physics. Assert at the first
107
- // populate; the misconfiguration would otherwise show up as
108
- // mysteriously inert abilities.
109
- assert.notEqual(this.physicsSystem, null,
110
- "FirstPersonSensorsSystem requires a PhysicsSystem to be in the world. "
111
- + "Add it before this system, or set `sensorsSystem.physicsSystem` "
112
- + "explicitly.");
113
-
114
- const ecd = this.entityManager.dataset;
115
- const transform = ecd.getComponent(entity, Transform);
116
- if (transform === undefined) return;
117
-
118
- sensors.clearAll();
119
-
120
- // Probe origin: at chest height (approx body height × 0.65).
121
- const cfg = controller.config;
122
- const chestY = cfg.body.height * 0.65;
123
- const ox = transform.position.x;
124
- const oy = transform.position.y + chestY;
125
- const oz = transform.position.z;
126
-
127
- // Body-local forward + right (engine-local axes — see DESIGN.md
128
- // §5.3 for the conventions). bodyYaw lives on the runtime.
129
- const yaw = runtime.bodyYaw;
130
- const fx = Math.sin(yaw);
131
- const fz = Math.cos(yaw);
132
- const rx = Math.cos(yaw);
133
- const rz = -Math.sin(yaw);
134
-
135
- const wallProbeDist = cfg.body.radius * 2 + 0.05; // just past the capsule
136
- const obstacleProbeDist = 1.2; // ~one stride
137
-
138
- // Wall left: probe along -right.
139
- this._probeRay(sensors.wallLeft, ox, oy, oz, -rx, 0, -rz, wallProbeDist, entity);
140
- // Wall right: probe along +right.
141
- this._probeRay(sensors.wallRight, ox, oy, oz, rx, 0, rz, wallProbeDist, entity);
142
- // Wall front: probe along +forward.
143
- this._probeRay(sensors.wallFront, ox, oy, oz, fx, 0, fz, wallProbeDist, entity);
144
- // Obstacle ahead: same direction as wallFront but longer reach.
145
- // Used by mantle/vault; the consumer compares the hit altitude to
146
- // the body height to decide vault vs. mantle vs. neither.
147
- this._probeRay(sensors.obstacleAhead, ox, oy, oz, fx, 0, fz, obstacleProbeDist, entity);
148
-
149
- // Ledge ahead — only meaningful if obstacleAhead hit something
150
- // around chest height: probe down from just past the top of the
151
- // obstacle, look for ground.
152
- if (sensors.obstacleAhead.hit) {
153
- const topY = transform.position.y + cfg.body.height + 0.1;
154
- const px = sensors.obstacleAhead.point.x + fx * 0.3;
155
- const pz = sensors.obstacleAhead.point.z + fz * 0.3;
156
- this._probeRay(
157
- sensors.ledgeAhead,
158
- px, topY, pz,
159
- 0, -1, 0,
160
- cfg.body.height * 0.5,
161
- entity,
162
- );
163
- }
164
- }
165
-
166
- /**
167
- * Raycast through {@link PhysicsSystem.raycast}, populating the slot
168
- * if anything is hit. Direction is assumed unit (the controller's
169
- * callers pass body-local unit axes).
170
- *
171
- * The raycast now writes the AABB face normal into `_probe_hit.normal`,
172
- * which is exact for AABB-shaped colliders (the common case in our gym)
173
- * and a stable approximation elsewhere. Future narrowphase refinement
174
- * will replace this with the true shape normal at the same call site.
175
- *
176
- * @private
177
- */
178
- _probeRay(slot, ox, oy, oz, dx, dy, dz, maxDist, excludeEntity) {
179
- // Exclude the player's own body from the result — downward
180
- // probes from chest start inside the capsule, would otherwise
181
- // hit the capsule's bottom hemisphere.
182
- const ray = this._probe_ray;
183
- ray.setOrigin(ox, oy, oz);
184
- ray.setDirection(dx, dy, dz);
185
- ray.tMax = maxDist;
186
- const filter = (e) => e !== excludeEntity;
187
- const out = this._probe_hit;
188
- if (!this.physicsSystem.raycast(ray, out, filter)) return;
189
-
190
- slot.hit = true;
191
- slot.distance = out.t;
192
- slot.point.set(out.position.x, out.position.y, out.position.z);
193
- slot.normal.set(out.normal.x, out.normal.y, out.normal.z);
194
- slot.surfaceTag = null; // future: surface-tag lookup
195
- }
196
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { Ray3 } from "../../../../core/geom/3d/ray/Ray3.js";
3
+ import { PhysicsSystem } from "../../../physics/ecs/PhysicsSystem.js";
4
+ import { PhysicsSurfacePoint } from "../../../physics/queries/PhysicsSurfacePoint.js";
5
+ import { System } from "../../../ecs/System.js";
6
+ import { Transform } from "../../../ecs/transform/Transform.js";
7
+ import { FirstPersonPlayerController } from "../FirstPersonPlayerController.js";
8
+ import { FirstPersonPlayerControllerSystem } from "../FirstPersonPlayerControllerSystem.js";
9
+
10
+ /**
11
+ * Populates the {@link FirstPersonSensors} struct on each controller
12
+ * entity, once per fixed step. Designed to run BEFORE the main
13
+ * {@link FirstPersonPlayerControllerSystem} `fixedUpdate` so abilities
14
+ * see fresh sensor data on the same tick.
15
+ *
16
+ * Raycasts go through {@link PhysicsSystem.raycast} — BVH-backed,
17
+ * static + dynamic broadphase. The player's own body is excluded via a
18
+ * per-call filter so downward probes don't collide with the player's
19
+ * own capsule.
20
+ *
21
+ * BVH raycasts are cheap (~1000/frame at 60Hz is comfortable), so this
22
+ * system doesn't aggressively optimize. NPC controllers that don't
23
+ * need sensors can simply not have this system attached.
24
+ *
25
+ * Sensor data lives at `runtime.sensors` on the
26
+ * {@link FirstPersonPlayerControllerSystem}'s per-entity runtime.
27
+ *
28
+ * @author Alex Goldring
29
+ * @copyright Company Named Limited (c) 2026
30
+ */
31
+ export class FirstPersonSensorsSystem extends System {
32
+ constructor() {
33
+ super();
34
+
35
+ this.dependencies = [FirstPersonPlayerController, Transform];
36
+
37
+ /**
38
+ * Reference to the controller system — needed to look up each
39
+ * entity's per-controller runtime (which owns the sensors slot).
40
+ * Auto-acquired in startup; can be overridden by the caller.
41
+ * @type {FirstPersonPlayerControllerSystem|null}
42
+ */
43
+ this.controllerSystem = null;
44
+
45
+ /**
46
+ * Physics system used as the spatial-query backend. Auto-acquired
47
+ * in startup; can be overridden. Required — sensors are unusable
48
+ * without it.
49
+ * @type {PhysicsSystem|null}
50
+ */
51
+ this.physicsSystem = null;
52
+
53
+ /**
54
+ * Pre-allocated raycast query primitive. Refilled in place per
55
+ * {@link _probeRay} call so the system doesn't allocate a new
56
+ * `Ray3` every probe.
57
+ * @private
58
+ * @type {Ray3}
59
+ */
60
+ this._probe_ray = new Ray3();
61
+
62
+ /**
63
+ * Pre-allocated raycast result. Refilled in place per probe;
64
+ * gameplay-visible sensor fields are copied out before the next
65
+ * probe overwrites it.
66
+ * @private
67
+ * @type {PhysicsSurfacePoint}
68
+ */
69
+ this._probe_hit = new PhysicsSurfacePoint();
70
+ }
71
+
72
+ async startup(entityManager) {
73
+ this.entityManager = entityManager;
74
+
75
+ // Auto-acquire collaborators if they're already in the world.
76
+ // Caller can still override either field before fixedUpdate runs.
77
+ if (this.controllerSystem === null) {
78
+ const cs = entityManager.getSystem(FirstPersonPlayerControllerSystem);
79
+ if (cs !== null) this.controllerSystem = cs;
80
+ }
81
+ if (this.physicsSystem === null) {
82
+ const ps = entityManager.getSystem(PhysicsSystem);
83
+ if (ps !== null) this.physicsSystem = ps;
84
+ }
85
+ }
86
+
87
+ fixedUpdate(dt) {
88
+ const ecd = this.entityManager.dataset;
89
+ if (ecd === null) return;
90
+ ecd.traverseComponents(FirstPersonPlayerController, this._populateForEntity, this);
91
+ }
92
+
93
+ /**
94
+ * @private
95
+ */
96
+ _populateForEntity(controller, entity) {
97
+ // Sensors live on the per-entity runtime owned by the
98
+ // controller's own system. Look it up by entity — no leakage
99
+ // through the controller component itself.
100
+ if (this.controllerSystem === null) return; // not wired up
101
+ const runtime = this.controllerSystem.getRuntime(entity);
102
+ if (runtime === undefined) return;
103
+ const sensors = runtime.sensors;
104
+ if (sensors === undefined || sensors === null) return;
105
+
106
+ // Sensors are useless without physics. Assert at the first
107
+ // populate; the misconfiguration would otherwise show up as
108
+ // mysteriously inert abilities.
109
+ assert.notEqual(this.physicsSystem, null,
110
+ "FirstPersonSensorsSystem requires a PhysicsSystem to be in the world. "
111
+ + "Add it before this system, or set `sensorsSystem.physicsSystem` "
112
+ + "explicitly.");
113
+
114
+ const ecd = this.entityManager.dataset;
115
+ const transform = ecd.getComponent(entity, Transform);
116
+ if (transform === undefined) return;
117
+
118
+ sensors.clearAll();
119
+
120
+ // Probe origin: at chest height (approx body height × 0.65).
121
+ const cfg = controller.config;
122
+ const chestY = cfg.body.height * 0.65;
123
+ const ox = transform.position.x;
124
+ const oy = transform.position.y + chestY;
125
+ const oz = transform.position.z;
126
+
127
+ // Body-local forward + right (engine-local axes — see DESIGN.md
128
+ // §5.3 for the conventions). bodyYaw lives on the runtime.
129
+ const yaw = runtime.bodyYaw;
130
+ const fx = Math.sin(yaw);
131
+ const fz = Math.cos(yaw);
132
+ const rx = Math.cos(yaw);
133
+ const rz = -Math.sin(yaw);
134
+
135
+ const wallProbeDist = cfg.body.radius * 2 + 0.05; // just past the capsule
136
+ const obstacleProbeDist = 1.2; // ~one stride
137
+
138
+ // Wall left: probe along -right.
139
+ this._probeRay(sensors.wallLeft, ox, oy, oz, -rx, 0, -rz, wallProbeDist, entity);
140
+ // Wall right: probe along +right.
141
+ this._probeRay(sensors.wallRight, ox, oy, oz, rx, 0, rz, wallProbeDist, entity);
142
+ // Wall front: probe along +forward.
143
+ this._probeRay(sensors.wallFront, ox, oy, oz, fx, 0, fz, wallProbeDist, entity);
144
+ // Obstacle ahead: same direction as wallFront but longer reach.
145
+ // Used by mantle/vault; the consumer compares the hit altitude to
146
+ // the body height to decide vault vs. mantle vs. neither.
147
+ this._probeRay(sensors.obstacleAhead, ox, oy, oz, fx, 0, fz, obstacleProbeDist, entity);
148
+
149
+ // Ledge ahead — only meaningful if obstacleAhead hit something
150
+ // around chest height: probe down from just past the top of the
151
+ // obstacle, look for ground.
152
+ if (sensors.obstacleAhead.hit) {
153
+ const topY = transform.position.y + cfg.body.height + 0.1;
154
+ const px = sensors.obstacleAhead.point.x + fx * 0.3;
155
+ const pz = sensors.obstacleAhead.point.z + fz * 0.3;
156
+ this._probeRay(
157
+ sensors.ledgeAhead,
158
+ px, topY, pz,
159
+ 0, -1, 0,
160
+ cfg.body.height * 0.5,
161
+ entity,
162
+ );
163
+ }
164
+
165
+ // Can the player STAND on that ledge, or only HANG from it? Probe
166
+ // straight down a capsule-width forward of the grab edge: if the top
167
+ // surface is still there a standing capsule fits; if it has dropped
168
+ // away it's a thin wall / knife-edge you could only hang from. Mantle
169
+ // reads this to refuse vaulting onto a top it would fall right off.
170
+ if (sensors.ledgeAhead.hit) {
171
+ const reach = cfg.body.radius * 2;
172
+ sensors.ledgeStandable = this._probeStandable(
173
+ sensors.ledgeAhead.point.x + fx * reach,
174
+ sensors.ledgeAhead.point.y,
175
+ sensors.ledgeAhead.point.z + fz * reach,
176
+ entity,
177
+ );
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Down-probe a point on (or just past) a ledge top: returns true if a
183
+ * surface is still there near `edgeY` — i.e. the top is wide enough for a
184
+ * standing capsule. A miss (or a much-lower hit) means the surface dropped
185
+ * away: a thin wall / lip you can only hang from, not stand on.
186
+ * @private
187
+ */
188
+ _probeStandable(px, edgeY, pz, excludeEntity) {
189
+ const ray = this._probe_ray;
190
+ ray.setOrigin(px, edgeY + 0.2, pz);
191
+ ray.setDirection(0, -1, 0);
192
+ ray.tMax = 0.4;
193
+ const filter = (e) => e !== excludeEntity;
194
+ const out = this._probe_hit;
195
+ if (!this.physicsSystem.raycast(ray, out, filter)) return false;
196
+ return out.position.y > edgeY - 0.15; // surface still up near the edge height
197
+ }
198
+
199
+ /**
200
+ * Raycast through {@link PhysicsSystem.raycast}, populating the slot
201
+ * if anything is hit. Direction is assumed unit (the controller's
202
+ * callers pass body-local unit axes).
203
+ *
204
+ * The raycast now writes the AABB face normal into `_probe_hit.normal`,
205
+ * which is exact for AABB-shaped colliders (the common case in our gym)
206
+ * and a stable approximation elsewhere. Future narrowphase refinement
207
+ * will replace this with the true shape normal at the same call site.
208
+ *
209
+ * @private
210
+ */
211
+ _probeRay(slot, ox, oy, oz, dx, dy, dz, maxDist, excludeEntity) {
212
+ // Exclude the player's own body from the result — downward
213
+ // probes from chest start inside the capsule, would otherwise
214
+ // hit the capsule's bottom hemisphere.
215
+ const ray = this._probe_ray;
216
+ ray.setOrigin(ox, oy, oz);
217
+ ray.setDirection(dx, dy, dz);
218
+ ray.tMax = maxDist;
219
+ const filter = (e) => e !== excludeEntity;
220
+ const out = this._probe_hit;
221
+ if (!this.physicsSystem.raycast(ray, out, filter)) return;
222
+
223
+ slot.hit = true;
224
+ slot.distance = out.t;
225
+ slot.point.set(out.position.x, out.position.y, out.position.z);
226
+ slot.normal.set(out.normal.x, out.normal.y, out.normal.z);
227
+ slot.surfaceTag = null; // future: surface-tag lookup
228
+ }
229
+ }
@@ -75,14 +75,6 @@ export class EntityManager {
75
75
  * @type {EntityManagerState}
76
76
  */
77
77
  state: EntityManagerState;
78
- /**
79
- * Track remainders of simulation time for fixed step
80
- * Needed for accurate time keeping
81
- * @private
82
- * @readonly
83
- * @type {Map<System, number>}
84
- */
85
- private readonly systemAccumulatedFixedStepTime;
86
78
  /**
87
79
  * Value used to execute {@link System.fixedUpdate}
88
80
  * In seconds.
@@ -95,12 +87,32 @@ export class EntityManager {
95
87
  */
96
88
  fixedUpdateStepSize: number;
97
89
  /**
98
- * How long can any given system run it's {@link System.fixedUpdate}, per simulation update
99
- * This is value allows us to avoid cases where {@link System.fixedUpdate} takes longer that its time step and causes a runaway freeze
100
- * In milliseconds.
90
+ * Wall-clock budget, in milliseconds, for the whole fixed-step catch-up loop
91
+ * within a single {@link simulate} call. Once a cycle's accumulated
92
+ * fixedUpdate work exceeds this, the loop stops early and the unconsumed time
93
+ * stays in the accumulator to be caught up later — this avoids a runaway
94
+ * freeze when {@link System.fixedUpdate} work outpaces its time step. (Now a
95
+ * per-cycle budget across all systems' lock-step steps; previously per-system.)
101
96
  * @type {number}
102
97
  */
103
98
  fixedUpdatePerSystemExecutionTimeLimit: number;
99
+ /**
100
+ * Monotonic **simulation tick id** — the count of fixed steps the simulation
101
+ * has executed. Advances once per {@link fixedUpdateStepSize} step inside
102
+ * {@link simulate}. This is the hub-owned identity that producers (physics,
103
+ * network) stamp per-tick state with, and that render-rate consumers
104
+ * (interpolation) read as "the latest completed step". Pairs with
105
+ * {@link getFixedStepAlpha}.
106
+ * @type {number}
107
+ */
108
+ fixedStepTick: number;
109
+ /**
110
+ * Canonical fixed-step time accumulator backing {@link fixedStepTick} and
111
+ * {@link getFixedStepAlpha}. Holds the sub-step remainder in `[0, step)`.
112
+ * @private
113
+ * @type {number}
114
+ */
115
+ private __fixedStepAccumulator;
104
116
  /**
105
117
  * Currently attached dataset.
106
118
  * Do not modify directly, instead use {@link attachDataset} and {@link detachDataset} respectively.
@@ -160,6 +172,17 @@ export class EntityManager {
160
172
  * @param {number} time_delta_seconds in seconds
161
173
  */
162
174
  simulate(time_delta_seconds: number): void;
175
+ /**
176
+ * Fractional progress through the current fixed step, in `[0, 1)` — the
177
+ * leftover sim time that hasn't yet formed a whole {@link fixedUpdateStepSize}
178
+ * step, normalized by the step size. This is the blend parameter the
179
+ * render-time interpolation system pairs with {@link fixedStepTick} to
180
+ * interpolate between the last two completed steps: `alpha = 0` sits on the
181
+ * previous tick, `alpha → 1` approaches the latest.
182
+ *
183
+ * @returns {number} alpha in `[0, 1)`
184
+ */
185
+ getFixedStepAlpha(): number;
163
186
  /**
164
187
  * If the {@link EntityManager} is already started, the system will be started automatically before being added
165
188
  * @param {System} system
@@ -1 +1 @@
1
- {"version":3,"file":"EntityManager.d.ts","sourceRoot":"","sources":["../../../../src/engine/ecs/EntityManager.js"],"names":[],"mappings":"iCAgBU,MAAM;;;;;;;;;AAiBhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IAEI;;;;OAIG;IACH,kBAFU,iCAAQ,CAEL;IAEb;;;;;;OAMG;IACH,uCAA2B;IAE3B;;;;;OAKG;IACH,iCAA4B;IAE5B;;OAEG;IACH;;;QAGI;;WAEG;qBADO,uCAAc;;MAI1B;IAEF;;;OAGG;IACH,OAFU,kBAAkB,CAEO;IAEnC;;;;;;OAMG;IACH,gDAA2C;IAE3C;;;;;;;;;OASG;IACH,qBAFU,MAAM,CAEqB;IAErC;;;;;OAKG;IACH,wCAFU,MAAM,CAE4B;IAE5C;;;;OAIG;IACH,SAFU,sBAAsB,CAEjB;IAEf;;;;;OAKG;IACH,uCAAsC;IAEtC;;;OAGG;IACH,6BAmFC;IAED;;;OAGG;IACH,uBAFa,OAAO,CAuBnB;IAED;;;;;OAKG;IACH,uBAJW,sBAAsB,QA4ChC;IAGD;;;OAGG;IACH,sBA2BC;IAED;;;;OAIG;IACH,qCAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,6CAgBC;IAED;;;;;OAKG;IACH,wCAHW,MAAM,GACJ,IAAI,aAAS,CAiBzB;IAGD;;;OAGG;IACH,6BAFW,MAAM,QAyEhB;IAED;;;;;OAKG;IACH,iEA6DC;IAED;;;;OAIG;IACH,uDAFa,QAAQ,OAAO,CAAC,CA2C5B;IAED;;;;;OAKG;IACH,mBAmCC;IAED;;;;;OAKG;IACH,oBAqFC;IAED;;;;;OAKG;IACH,kEAyEC;IAED;;;;OAIG;IACH,mCAFa,wCAAgB,CA0B5B;IAED;;;;OAIG;IACH,8DAFa,wCAAgB,CA0B5B;IAED;;;;;OAKG;IACH,mEAoFC;IAGL;;OAEG;IACH,yBAjsBe,sBAAsB,UAisBA;IACrC;;OAEG;IACH,0BAAqC;CATpC;uBA/7BmC,aAAa;mBAR9B,oCAAoC;uCAMhB,6BAA6B"}
1
+ {"version":3,"file":"EntityManager.d.ts","sourceRoot":"","sources":["../../../../src/engine/ecs/EntityManager.js"],"names":[],"mappings":"iCAgBU,MAAM;;;;;;;;;AAiBhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IAEI;;;;OAIG;IACH,kBAFU,iCAAQ,CAEL;IAEb;;;;;;OAMG;IACH,uCAA2B;IAE3B;;;;;OAKG;IACH,iCAA4B;IAE5B;;OAEG;IACH;;;QAGI;;WAEG;qBADO,uCAAc;;MAI1B;IAEF;;;OAGG;IACH,OAFU,kBAAkB,CAEO;IAEnC;;;;;;;;;OASG;IACH,qBAFU,MAAM,CAEqB;IAErC;;;;;;;;OAQG;IACH,wCAFU,MAAM,CAE4B;IAE5C;;;;;;;;OAQG;IACH,eAFU,MAAM,CAEE;IAElB;;;;;OAKG;IACH,+BAA2B;IAE3B;;;;OAIG;IACH,SAFU,sBAAsB,CAEjB;IAEf;;;;;OAKG;IACH,uCAAsC;IAEtC;;;OAGG;IACH,6BAmFC;IAED;;;OAGG;IACH,uBAFa,OAAO,CAuBnB;IAED;;;;;OAKG;IACH,uBAJW,sBAAsB,QA4ChC;IAGD;;;OAGG;IACH,sBA2BC;IAED;;;;OAIG;IACH,qCAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,6CAgBC;IAED;;;;;OAKG;IACH,wCAHW,MAAM,GACJ,IAAI,aAAS,CAiBzB;IAGD;;;OAGG;IACH,6BAFW,MAAM,QA8EhB;IAED;;;;;;;;;OASG;IACH,qBAFa,MAAM,CAKlB;IAED;;;;;OAKG;IACH,iEA2DC;IAED;;;;OAIG;IACH,uDAFa,QAAQ,OAAO,CAAC,CAyC5B;IAED;;;;;OAKG;IACH,mBAmCC;IAED;;;;;OAKG;IACH,oBAqFC;IAED;;;;;OAKG;IACH,kEAyEC;IAED;;;;OAIG;IACH,mCAFa,wCAAgB,CA0B5B;IAED;;;;OAIG;IACH,8DAFa,wCAAgB,CA0B5B;IAED;;;;;OAKG;IACH,mEAoFC;IAGL;;OAEG;IACH,yBAjtBe,sBAAsB,UAitBA;IACrC;;OAEG;IACH,0BAAqC;CATpC;uBA59BmC,aAAa;mBAR9B,oCAAoC;uCAMhB,6BAA6B"}