@woosh/meep-engine 2.142.0 → 2.144.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 (64) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +1 -1
  3. package/src/core/geom/3d/shape/CapsuleShape3D.js +1 -1
  4. package/src/core/geom/3d/shape/PointShape3D.d.ts +1 -0
  5. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/PointShape3D.js +11 -0
  7. package/src/core/geom/3d/shape/SphereShape3D.d.ts +48 -0
  8. package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -0
  9. package/src/core/geom/3d/shape/SphereShape3D.js +131 -0
  10. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +30 -18
  11. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
  12. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -92
  13. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  14. package/src/core/geom/3d/shape/json/shape_to_type.js +4 -2
  15. package/src/core/geom/3d/shape/json/type_adapters.d.ts +12 -3
  16. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  17. package/src/core/geom/3d/shape/json/type_adapters.js +16 -4
  18. package/src/core/geom/3d/shape/util/shape_to_visual_entity.js +2 -2
  19. package/src/engine/control/first-person/DESIGN_COLLISION.md +302 -0
  20. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +91 -58
  21. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  22. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1814 -1789
  23. package/src/engine/control/first-person/TODO.md +17 -32
  24. package/src/engine/control/first-person/collision/KinematicMover.d.ts +176 -0
  25. package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -0
  26. package/src/engine/control/first-person/collision/KinematicMover.js +424 -0
  27. package/src/engine/control/first-person/prototype_first_person_controller.js +65 -0
  28. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  29. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +3 -1
  30. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +30 -16
  31. package/src/engine/physics/PLAN.md +94 -32
  32. package/src/engine/physics/contact/ManifoldStore.d.ts +28 -2
  33. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  34. package/src/engine/physics/contact/ManifoldStore.js +37 -3
  35. package/src/engine/physics/contact/combine_material.d.ts +30 -0
  36. package/src/engine/physics/contact/combine_material.d.ts.map +1 -0
  37. package/src/engine/physics/contact/combine_material.js +35 -0
  38. package/src/engine/physics/ecs/Collider.d.ts +15 -0
  39. package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
  40. package/src/engine/physics/ecs/Collider.js +34 -0
  41. package/src/engine/physics/ecs/Joint.d.ts +18 -0
  42. package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
  43. package/src/engine/physics/ecs/Joint.js +70 -0
  44. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +29 -0
  45. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -0
  46. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -0
  47. package/src/engine/physics/ecs/PhysicsSystem.d.ts +9 -4
  48. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  49. package/src/engine/physics/ecs/PhysicsSystem.js +9 -4
  50. package/src/engine/physics/ecs/RigidBody.d.ts +15 -0
  51. package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
  52. package/src/engine/physics/ecs/RigidBody.js +46 -0
  53. package/src/engine/physics/narrowphase/compute_penetration.d.ts +41 -41
  54. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  55. package/src/engine/physics/narrowphase/compute_penetration.js +96 -169
  56. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +52 -0
  57. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  58. package/src/engine/physics/narrowphase/narrowphase_step.js +150 -16
  59. package/src/engine/physics/narrowphase/refine_ray_hit.js +2 -2
  60. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +8 -7
  61. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -1
  62. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +8 -7
  63. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  64. package/src/engine/physics/solver/solve_contacts.js +10 -21
@@ -0,0 +1,302 @@
1
+ # Collision handling — ground-up construction plan
2
+
3
+ Companion to DESIGN.md (base controller) and TODO.md.
4
+
5
+ **Premise.** The control layer is excellent and stays. The collision
6
+ layer is being **replaced wholesale** — the current
7
+ `_moveAndSlide` + `_integrateVerticalAndResolveGround` + scalar
8
+ ground-resolver regime is not a foundation to build on; it's scaffolding
9
+ to delete once the replacement reaches parity. We keep relying on
10
+ `PhysicsSystem` for all the heavy lifting (`shapeCast`, `raycast`,
11
+ `overlap`, `compute_penetration`) — this plan does *more* with it, not
12
+ less.
13
+
14
+ Every phase is **guard-test-first**, and ordered so the controller stays
15
+ shippable and green after each one. The new mover is built alongside,
16
+ proven against ported + new specs, switched over, and only then is the
17
+ old code deleted.
18
+
19
+ ---
20
+
21
+ ## 1. The seam: control produces motion, collision resolves it
22
+
23
+ The clean split that makes a ground-up rebuild safe — the control layer
24
+ already does its half today:
25
+
26
+ ```
27
+ CONTROL (sacred, unchanged) COLLISION (rebuilt)
28
+ ────────────────────────── ──────────────────
29
+ intent → desired velocity move(pose, shape, velocity, dt)
30
+ accel curves, jump FSM, ──v──▶ → resolved position
31
+ gravity/impulse, abilities, → corrected velocity
32
+ mastery, posture → ground state {grounded, normal}
33
+ ▲ │
34
+ └────────── grounded, groundNormal ◀─────────┘
35
+ (read next tick by jump FSM,
36
+ gravity gating, slope logic)
37
+ ```
38
+
39
+ The control layer's output each tick is a **desired velocity** (it
40
+ already computes this — intent+accel horizontally, gravity+jump
41
+ vertically). The collision layer's *only* job is: given that velocity,
42
+ the capsule pose, and the world, produce the actual resulting position,
43
+ the velocity corrected for what it hit, and the ground state. It never
44
+ invents motion; it only constrains it.
45
+
46
+ One cleanup this forces (Phase 4): **gravity + jump-impulse application
47
+ move out of the deleted collision method into a small motor helper**
48
+ the base loop and abilities call. The solver stays purely about
49
+ collision.
50
+
51
+ ---
52
+
53
+ ## 2. What we keep / replace / delete
54
+
55
+ **Keep (untouched):** the whole control stack — intent, mono-exp accel,
56
+ jump FSM, posture→collider sizing, abilities (Slide/WallRun/Mantle/
57
+ LedgeGrab), mastery, camera composition, sensors.
58
+
59
+ **Replace:** the regime that *consumes* velocity —
60
+ `_moveAndSlide` (horizontal-only swept slide), the direct vertical
61
+ position add, and the downward-ray scalar-Y ground snap.
62
+
63
+ **Delete (Phase 4, after parity):** `_moveAndSlide`,
64
+ `_integrateVerticalAndResolveGround`'s collision body, the
65
+ `CAST_STEP_HEIGHT` implicit-step hack, and the scalar `groundResolver`
66
+ *as the grounding authority* (it survives only as a no-physics fallback
67
+ inside the new ground probe).
68
+
69
+ ---
70
+
71
+ ## 3. Physics capabilities we build on
72
+
73
+ The P1–P6 narrowphase/query work is the enabling foundation — the
74
+ rebuild is only feasible because these are now narrowphase-exact.
75
+
76
+ | Capability | Status | Source |
77
+ |---|---|---|
78
+ | `raycast` exact surface **normal** (sphere/box/capsule/mesh/heightmap) | ✅ | refine_ray_hit P1–P3 (`6f931b4`…`4c2292c`) |
79
+ | `shapeCast` true contact **normal at TOI** (MPR, not broadphase −dir) | ✅ | `shape_cast.js` normal-recovery |
80
+ | `shapeCast` **start-in-contact** → `t=0` + valid normal | ✅ | `shape_cast.js:195`, `:350` |
81
+ | `shapeCast` analytic slab-narrowing (long-sweep accuracy) | ✅ | `shape_cast.js:203` |
82
+ | `compute_penetration(out, A,pa,ra, B,pb,rb) → depth`, `out`=unit B→A | ✅ **public** | `compute_penetration.js:138` |
83
+ | `overlap(shape,pos,rot,out,off,filter)` → body-id list | ✅ | `overlap_shape.js` |
84
+ | EPA adaptive tol / MPR fallback / box-box edge / capsule-tri manifold / GJK cache | ✅ | P2.2, P3.1, P3.2, P1.1c, P2.1 |
85
+
86
+ Contracts the plan honours:
87
+ - `shapeCast` `result.t` is a **world distance** when direction is
88
+ unit-length and `tMax = sweepLength`. New casts keep that.
89
+ - `compute_penetration` accepts the convex player capsule vs any
90
+ shape (only concave-vs-concave throws); `out_direction` is the unit
91
+ **B→A** separation, depth is the scalar return. Push the capsule (A)
92
+ out along `+out_direction · depth`.
93
+ - `shapeCast` `result.position` is the swept **centre**, not the
94
+ contact point — we derive ground-surface Y from the cast `t` + the
95
+ capsule's bottom offset, so we never need a contact point.
96
+
97
+ ---
98
+
99
+ ## 4. Target architecture — a dedicated `KinematicMover`
100
+
101
+ Extract the collision solver into its own module (name placeholder —
102
+ `KinematicMover` / `CharacterMover`), testable in isolation against a
103
+ real `PhysicsSystem`, not buried in the 1500-line system file. Matches
104
+ the repo's extraction taste (Spring, EyeOffsetStack, FirstPersonSensors).
105
+
106
+ It knows nothing about the controller — just a capsule, a velocity, the
107
+ world, and an `up` axis. Single entry point:
108
+
109
+ ```
110
+ move(transform, shape, velocity, dt, filter) → MoveResult
111
+ // mutates transform.position; returns:
112
+ // { velocityX/Y/Z (corrected), grounded, groundNormal, hit }
113
+ ```
114
+
115
+ Modelled on modern kinematic character controllers (Jolt
116
+ `CharacterVirtual`, Source `TryPlayerMove`+`CategorizePosition`), the
117
+ move is an explicit sequence — **recover → slide → (stairs) → ground →
118
+ settle**:
119
+
120
+ 1. **Recover (depenetration).** Before moving, get clear. `overlap()`
121
+ the capsule; for each body, `compute_penetration` → push the capsule
122
+ out by `depth · dir`; iterate a few times (deepest-first) to
123
+ convergence. Handles spawned-in-geometry, world-crushed-into-us, and
124
+ the start-solid case a pure sweep *cannot*. This is now a **core
125
+ step**, available from Phase 1 — not a bolt-on — because
126
+ `compute_penetration` is public.
127
+
128
+ 2. **Sweep-and-slide (unified 3D).** Sweep the *full* velocity·dt
129
+ (gravity folded in — no H/V split) via `shapeCast`; stop at TOI,
130
+ clip velocity onto the contact tangent, project the residual, repeat
131
+ up to N iterations. **Crease-aware**: a second plane re-violating the
132
+ first → project onto the seam `normalize(n₁×n₂)`; a third plane in
133
+ one move → dead-stop. Walls, floors, ceilings are all just contact
134
+ planes, so anti-tunnelling (H and V) falls out of one loop.
135
+
136
+ 3. **Stairs (explicit, optional per move).** When grounded and blocked
137
+ by a low step: sweep up by `stepHeight`, sweep the residual forward,
138
+ sweep back down — the Jolt/Source step pattern. Replaces the 5 cm
139
+ implicit-cast hack with real stair climbing, gated so walls taller
140
+ than `stepHeight` still block.
141
+
142
+ 4. **Ground categorize + stick.** Short downward `shapeCast` (capsule
143
+ swept down a `SKIN + band`); if it hits within the band with
144
+ `normal.y ≥ MIN_WALK_NORMAL` (~0.7), set `grounded`, capture the
145
+ normal, snap to the surface, and kill the into-ground velocity
146
+ component. Band-test + active snap (not a strict `y ≤ testY`) is what
147
+ structurally kills the landing **bounce**. Too-steep normal →
148
+ not grounded → the slope was already a slide plane in step 2, so the
149
+ player slides down it.
150
+
151
+ 5. **Settle.** One final `compute_penetration` recover pass guarantees
152
+ the tick ends penetration-free (resting-contact float drift
153
+ insurance; usually a no-op).
154
+
155
+ Slope handling, multi-body resting contact, and unstick all fall out of
156
+ steps 1/2/4 — no special cases. Abilities get correct collision for free
157
+ by routing their motion through the same `move()`.
158
+
159
+ ---
160
+
161
+ ## 5. Phased plan (rebuild, not patch)
162
+
163
+ ### Phase 1 — `KinematicMover`: recover + unified sweep-and-slide
164
+ Stand up the module. Implement **recover** (overlap +
165
+ compute_penetration) and **unified 3D sweep-and-slide** (crease-aware).
166
+ The system calls `move()` for the combined velocity·dt, replacing both
167
+ `_moveAndSlide` and the direct vertical add. Grounding stays on the old
168
+ resolver as a short-lived interim shim (removed in Phase 2) so this
169
+ phase isolates "does the new mover move correctly."
170
+
171
+ **Guard tests** (`KinematicMover.spec.js`, against a real PhysicsSystem):
172
+ spawn straddling a box → recovered outside within a tick; wall stop;
173
+ slide-along axis-aligned + oblique wall; **vertical** anti-tunnel (drop
174
+ through a thin slab → lands on top); jump into ceiling → stops; corner
175
+ crease → clean stop, no leak/chatter. Port the existing
176
+ `MoveAndSlide.spec.js` scenarios through the new path.
177
+
178
+ **Risk:** medium. New module, but isolated; old grounding still in place.
179
+
180
+ ### Phase 2 — Ground categorize + slope + stick (inside the mover)
181
+ Add steps 4 (categorize/stick) to the mover. Down-`shapeCast` →
182
+ `{grounded, normal}`; `MIN_WALK_NORMAL` gate; snap + kill into-ground
183
+ velocity; reproject grounded velocity onto the slope. **Remove the
184
+ interim grounding shim and the scalar resolver authority** (resolver
185
+ demoted to no-physics fallback *inside* the probe, behind one function —
186
+ uniform flow). Feed `grounded`/`groundNormal` back to control.
187
+
188
+ **Guard tests** (`GroundSlope.spec.js`): walk up a 30° ramp keeps speed,
189
+ stays grounded, correct normal; stationary-on-floor stays at y≈0 across
190
+ 120 ticks (the no-bounce repro, now with nothing masking it); 60° face →
191
+ not grounded, slides down, not glued.
192
+
193
+ **Risk:** medium. Changes grounding semantics; the bounce reconciliation
194
+ lives here.
195
+
196
+ ### Phase 3 — Stairs ✅ **landed**
197
+ Real stair climbing, gated by `stepHeight` (default 0.3 m, just under
198
+ the capsule radius).
199
+
200
+ The implementation diverged from the planned explicit "up-forward-down"
201
+ step sweep — that was tried and removed. The capsule's *round bottom*
202
+ already rolls up a riser shorter than its radius (the slide moves the
203
+ player up positionally), and an explicit step-up fought it (incremental
204
+ wall-climbing, edge-wedging). What was actually needed was **honest
205
+ ground categorization on a step edge**, which two probes split cleanly:
206
+
207
+ - **Step-DOWN** = the ground-stick reach is `stepHeight`: walking off
208
+ a drop ≤ stepHeight snaps onto the lower surface (stays grounded);
209
+ a larger drop goes airborne.
210
+ - **Step-UP** = categorize takes **walkability from a centre raycast**
211
+ (sees the real planar surface; ignores a step's convex top edge and
212
+ a wall's side face — so a steep *slope* is correctly not-grounded)
213
+ and **rest height from a footprint shapecast** (raises onto a step
214
+ the leading edge overhangs, capped at `stepHeight`). A single probe
215
+ can't tell a climbable step edge from a steep slope — both are
216
+ steep-normal contacts — which is why the two are split.
217
+
218
+ This is also what fixed the "launch off every low step" jank: with the
219
+ player correctly grounded through the roll, the per-tick vertical
220
+ velocity is zeroed and never accumulates.
221
+
222
+ **Guard tests** (`collision/Stairs.spec.js`, all green): climb a 5-step
223
+ staircase staying grounded; clear a single curb without stalling; a
224
+ 0.5 m riser (> stepHeight) blocks; descend a staircase grounded the
225
+ whole way (no launch off each lip).
226
+
227
+ ### Phase 4 — Motor seam + delete old code ✅ **landed**
228
+ The mover is now the **only** collision path when a `PhysicsSystem` is
229
+ present — the opt-in flag is gone; dispatch is on physics presence, not
230
+ a flag.
231
+
232
+ What shipped:
233
+ - `_integrateVerticalAndResolveGround` is now a thin dispatcher:
234
+ `_applyGravity` (the motor) → `_moveViaMover` (physics) **or**
235
+ `_moveFlatGround` (no physics) → `_detectJumpApex`.
236
+ - **Gravity** extracted to `_applyGravity`; **land / leave-ground**
237
+ extracted to `_onLand` / `_onLeaveGround`, shared by both move paths
238
+ (the duplicated scaffolding from Phase 1's wiring is gone).
239
+ - **Deleted** `_moveAndSlide` (+ its `CAST_STEP_HEIGHT` / `SKIN`
240
+ constants and the `slideRay` / `slideHit` scratch), the legacy ground-
241
+ resolution body, and the `useKinematicMover` flag.
242
+ - `MoveAndSlide.spec.js` deleted — it tested `_moveAndSlide`; its
243
+ scenarios (wall-stop, axis + oblique slide, anti-tunnel) are covered
244
+ by `collision/KinematicMover.spec.js`.
245
+
246
+ What stayed (deliberately):
247
+ - `useBuiltInFlatGround` / `groundY` / `groundResolver` remain — they're
248
+ the **no-physics** fallback (`_moveFlatGround`) for headless and
249
+ control-layer unit tests, and the resolver is still spec-covered. With
250
+ physics present they're unused (the mover probes the world).
251
+ - **WallRun still self-integrates** its reduced-gravity model rather than
252
+ routing through `move()`. Slide already routes through the mover (it
253
+ calls `_integrateVerticalAndResolveGround`); WallRun's custom model is
254
+ left as a future enhancement, not required for the legacy deletion.
255
+
256
+ **Result:** 29 suites / 236 tests green; the entire ability + jump +
257
+ momentum + posture suite passes through the consolidated path.
258
+
259
+ ---
260
+
261
+ ## 6. Physics requirements — status
262
+
263
+ **Resolved.** My one HIGH ask from the review — a public penetration
264
+ query — is **satisfied**: `compute_penetration` is public and is exactly
265
+ the right shape (unit B→A direction + scalar depth, capsule-vs-anything).
266
+ It graduates from "deferred safety net" to the **core recover/settle
267
+ primitive** the whole mover leans on (steps 1 & 5). Confirmed it works:
268
+ yes, this is precisely what's needed — thank you for keeping it decoupled
269
+ and public.
270
+
271
+ **No remaining blocking requirements.** Phases 1–4 need nothing further
272
+ from the physics engine. The two low-priority niceties from the review
273
+ turned out not to bite:
274
+ - *Contact point from `shapeCast`* — not needed; ground-surface Y is
275
+ derivable from the cast `t` + the capsule bottom offset.
276
+ - *Multi-contact manifold from one query* — covered for resting contact
277
+ by iterating `compute_penetration` over **every** `overlap()` body in
278
+ the recover pass; the single-sweep corner case is handled by the slide
279
+ iteration instead. So no native multi-contact query is required.
280
+
281
+ If anything *would* be a future nicety (not needed now): a swept query
282
+ returning the *set* of TOI-simultaneous contacts (for one-pass corner
283
+ creases instead of iterating). Strictly an optimisation; the iterative
284
+ slide is correct without it.
285
+
286
+ ---
287
+
288
+ ## 7. Constants (ours ← reference lineage)
289
+
290
+ | Constant | Plan value | Reference | Phase |
291
+ |---|---|---|---|
292
+ | slide iterations | 4 | Quake `numbumps` 4 | 1 |
293
+ | `SKIN` | 0.005 m | Fauerby `veryCloseDistance` ~0.005 | 1 |
294
+ | recover max iters | ~4 | — (convergence cap) | 1 |
295
+ | `MIN_WALK_NORMAL` | ~0.7 (≈45°) | Quake3 0.7 / Source `normal.z ≥ 0.7` | 2 |
296
+ | ground-probe band | ~0.06 m | Source `StayOnGround` ~2 u | 2 |
297
+ | crease dead-stop | zero on 3rd plane | Quake `SV_FlyMove` | 1 |
298
+ | `stepHeight` | ~0.3 m (tunable) | Source `sv_stepsize` 18 u (~0.34 m) | 3 |
299
+
300
+ Clip stays at effective overbounce `1.0` gated on into-wall (`v·n < 0`),
301
+ relying on `SKIN` for clearance — equivalent in practice to Quake3's
302
+ `OVERCLIP 1.001` nudge; no change planned.
@@ -56,14 +56,30 @@ export class FirstPersonPlayerControllerSystem extends System<any, any, any, any
56
56
  */
57
57
  groundResolver: (x: number, y: number, z: number) => number | null;
58
58
  /**
59
- * PhysicsSystem reference used by {@link _moveAndSlide}. Auto-
60
- * acquired at startup; can be overridden by the caller. When
61
- * null (no physics in the world), move-and-slide degrades to a
62
- * direct position add — useful for spec setups that don't wire
63
- * physics.
59
+ * PhysicsSystem reference. Auto-acquired at startup; can be
60
+ * overridden by the caller. When present, collision is resolved
61
+ * by the {@link KinematicMover}; when null, the controller uses
62
+ * the flat-ground integrator ({@link _moveFlatGround}) — useful
63
+ * for spec setups that don't wire physics.
64
64
  * @type {PhysicsSystem|null}
65
65
  */
66
66
  physicsSystem: PhysicsSystem | null;
67
+ /**
68
+ * The collision solver — the authoritative move resolver whenever
69
+ * a {@link PhysicsSystem} is present (recover + unified
70
+ * sweep-and-slide + ground-categorize; see DESIGN_COLLISION.md).
71
+ * Lazily built on first use. With no physics the controller falls
72
+ * back to a flat-ground integrator ({@link _moveFlatGround}).
73
+ * @private
74
+ * @type {KinematicMover|null}
75
+ */
76
+ private _mover;
77
+ /** @private Scratch velocity handed to the mover each tick. */
78
+ private _moverVelocity;
79
+ /** @private Scratch for the posture-headroom up-cast. */
80
+ private _postureRay;
81
+ /** @private @type {PhysicsSurfacePoint} */
82
+ private _postureHit;
67
83
  startup(entityManager: any): Promise<void>;
68
84
  /**
69
85
  * @param {FirstPersonPlayerController} controller
@@ -117,7 +133,27 @@ export class FirstPersonPlayerControllerSystem extends System<any, any, any, any
117
133
  * @private
118
134
  */
119
135
  private _syncColliderShape;
120
- _resolveCrouchHeld(controller: any, runtime: any): any;
136
+ _resolveCrouchHeld(controller: any, runtime: any, bodyTransform: any): any;
137
+ /**
138
+ * Headroom probe for a posture change that GROWS the collider.
139
+ * Sweeps `fromShape` (the shorter posture's capsule) straight up by
140
+ * `growDelta` (the height the collider would gain). A hit means an
141
+ * overhang occupies the space the taller posture needs, so the grow
142
+ * is blocked. Returns true (clear) when no physics is wired or the
143
+ * grow is non-positive.
144
+ *
145
+ * Why an up-cast rather than an overlap-test of the taller shape:
146
+ * overlap would false-positive on a wall *beside* the player. The
147
+ * up-cast sweeps parallel to (and, per the mover's skin clearance,
148
+ * clear of) lateral walls so they aren't hit; the floor is below
149
+ * the rising probe; the player's own body is filtered out. Only
150
+ * genuine overhead geometry blocks the grow. Relies on the player
151
+ * resting at `floor+skin` (the mover's stick-to-ground), so the
152
+ * probe's bottom starts clear of the floor.
153
+ *
154
+ * @private
155
+ */
156
+ private _hasHeadroomToGrow;
121
157
  /**
122
158
  * Jump finite-state-machine: button-edge detection, buffer + coyote
123
159
  * grace, anticipation timer, impulse on completion. Variable-height
@@ -132,48 +168,12 @@ export class FirstPersonPlayerControllerSystem extends System<any, any, any, any
132
168
  */
133
169
  private _advanceJumpFsm;
134
170
  /**
135
- * Sweep the player's collider along (dx, dy, dz) via
136
- * {@link PhysicsSystem.shapeCast} and translate the Transform up to
137
- * (but not past) the first contact. Prevents tunneling through
138
- * static geometry and creep-penetration over many ticks.
139
- *
140
- * v1 limitations:
141
- * - The broadphase shape-cast returns the back-along-the-sweep
142
- * normal (`−direction`), not the true surface normal. With
143
- * that, the principled "slide along the surface" residual is
144
- * `delta -= dot(delta, n)·n = 0` — i.e. the player stops at
145
- * contact instead of sliding tangent to the wall. Once
146
- * narrowphase refinement lands and `result.normal` becomes the
147
- * true surface normal, the same residual computation will
148
- * naturally produce sliding without an API change.
149
- * - SKIN clearance (5 mm) keeps the player just shy of the wall
150
- * so the next cast doesn't start with the capsule already in
151
- * contact. Picking this too small risks GJK reporting `t = 0`
152
- * and the player getting stuck; too large is visible as a gap.
153
- *
154
- * Falls through to a direct position add when the host hasn't
155
- * wired a {@link PhysicsSystem} — useful for spec setups that
156
- * don't bring physics up.
157
- *
158
- * @private
159
- * @param {PerEntityRuntime} runtime
160
- * @param {Transform} bodyTransform
161
- * @param {number} deltaX
162
- * @param {number} deltaY
163
- * @param {number} deltaZ
164
- * @returns {boolean} true if a contact occurred (and the sweep was
165
- * truncated); false on a clean full advance.
166
- */
167
- private _moveAndSlide;
168
- /**
169
- * Gravity (with fall and cut multipliers), vertical integration,
170
- * built-in flat-floor resolution (land event + impulse), and jump-apex
171
- * detection. The full vertical phase of one fixed step.
172
- *
173
- * The built-in flat-floor branch only runs when `useBuiltInFlatGround`
174
- * is true (the prototype's standalone mode); with an external physics
175
- * layer attached the system relies on the layer to set `state.grounded`
176
- * and only maintains airborne/grounded timers here.
171
+ * The vertical + collision phase of one fixed step: apply gravity
172
+ * (the motor), resolve the move against the world, then detect the
173
+ * jump apex. Collision resolution is the {@link KinematicMover} when
174
+ * a physics layer is present; otherwise a lightweight flat-ground
175
+ * integrator for headless / unit-test scenes with no collidable
176
+ * geometry. Land / leave-ground events flow identically from either.
177
177
  *
178
178
  * @private
179
179
  * @param {FirstPersonPlayerController} controller
@@ -182,6 +182,49 @@ export class FirstPersonPlayerControllerSystem extends System<any, any, any, any
182
182
  * @param {number} dt
183
183
  */
184
184
  private _integrateVerticalAndResolveGround;
185
+ /**
186
+ * Motor: integrate gravity into `velocityY` with the fall / variable-
187
+ * cut multipliers. The mover never invents motion, so gravity lives
188
+ * here (callable by abilities that want standard gravity too).
189
+ * @private
190
+ */
191
+ private _applyGravity;
192
+ /**
193
+ * Resolve the move against the world via {@link KinematicMover}
194
+ * (recover + unified sweep-and-slide + ground-categorize), then map
195
+ * the result back to controller state and land / leave events.
196
+ * @private
197
+ */
198
+ private _moveViaMover;
199
+ /**
200
+ * No-physics fallback: integrate the velocity directly (there's no
201
+ * collidable geometry to sweep against) and resolve the floor from
202
+ * the built-in flat ground and/or the host `groundResolver`. For
203
+ * headless scenes and control-layer unit tests. With both ground
204
+ * sources off, an external physics layer is expected to own
205
+ * `state.grounded`; we only advance timers.
206
+ * @private
207
+ */
208
+ private _moveFlatGround;
209
+ /**
210
+ * Landing transition: dip / impact springs, exertion impulse, flag
211
+ * resets, and the `onLand` signal (fired LAST so handlers see fully-
212
+ * reacted state). Shared by both move paths. `impactVy` is the
213
+ * downward speed at touchdown (positive).
214
+ * @private
215
+ */
216
+ private _onLand;
217
+ /**
218
+ * Leave-ground transition: `onLeaveGround` signal + takeoff bookkeeping.
219
+ * Shared by both move paths.
220
+ * @private
221
+ */
222
+ private _onLeaveGround;
223
+ /**
224
+ * Fire `onJumpApex` once, when an in-progress jump stops rising.
225
+ * @private
226
+ */
227
+ private _detectJumpApex;
185
228
  /**
186
229
  * Run the base (no-ability) L1 locomotion phases: speed selection,
187
230
  * desired-velocity computation, accel/decel, jump FSM, gravity, body
@@ -264,16 +307,6 @@ declare class PerEntityRuntime {
264
307
  * @type {Collider|null}
265
308
  */
266
309
  collider: Collider | null;
267
- /**
268
- * Pre-allocated move-and-slide scratch — Ray3 and PhysicsSurfacePoint
269
- * reused per cast so the controller doesn't churn the allocator
270
- * each fixed step. Lazily filled by {@link _moveAndSlide}.
271
- * @private
272
- * @type {Ray3|null}
273
- */
274
- private slideRay;
275
- /** @private @type {PhysicsSurfacePoint|null} */
276
- private slideHit;
277
310
  /**
278
311
  * Pre-built capsule colliders, one per posture. Cached at link
279
312
  * from `config.body.{height, crouchHeight, proneHeight, radius}`
@@ -1 +1 @@
1
- {"version":3,"file":"FirstPersonPlayerControllerSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/control/first-person/FirstPersonPlayerControllerSystem.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FirstPersonPlayerControllerSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/control/first-person/FirstPersonPlayerControllerSystem.js"],"names":[],"mappings":"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"}