@woosh/meep-engine 2.143.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.
- package/package.json +1 -1
- package/src/core/geom/3d/shape/PointShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +11 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/SphereShape3D.js +4 -0
- package/src/engine/control/first-person/DESIGN_COLLISION.md +264 -217
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +91 -58
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1814 -1789
- package/src/engine/control/first-person/TODO.md +17 -32
- package/src/engine/control/first-person/collision/KinematicMover.d.ts +176 -0
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -0
- package/src/engine/control/first-person/collision/KinematicMover.js +424 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +65 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +18 -9
- package/src/engine/physics/PLAN.md +94 -32
- package/src/engine/physics/contact/ManifoldStore.d.ts +28 -2
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +37 -3
- package/src/engine/physics/contact/combine_material.d.ts +30 -0
- package/src/engine/physics/contact/combine_material.d.ts.map +1 -0
- package/src/engine/physics/contact/combine_material.js +35 -0
- package/src/engine/physics/ecs/Collider.d.ts +15 -0
- package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
- package/src/engine/physics/ecs/Collider.js +34 -0
- package/src/engine/physics/ecs/Joint.d.ts +18 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +70 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +9 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +9 -4
- package/src/engine/physics/ecs/RigidBody.d.ts +15 -0
- package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBody.js +46 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +41 -41
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +96 -169
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +52 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +130 -3
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +10 -21
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Pure JavaScript game engine. Fully featured and production ready.",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": "Alexander Goldring",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.144.0",
|
|
10
10
|
"main": "build/meep.module.js",
|
|
11
11
|
"module": "build/meep.module.js",
|
|
12
12
|
"exports": {
|
|
@@ -9,6 +9,7 @@ export class PointShape3D extends AbstractShape3D {
|
|
|
9
9
|
nearest_point_on_surface(result: any, reference: any): void;
|
|
10
10
|
sample_random_point_in_volume(result: any, result_offset: any, random: any): void;
|
|
11
11
|
support(result: any, result_offset: any, direction_x: any, direction_y: any, direction_z: any): void;
|
|
12
|
+
equals(other: any): boolean;
|
|
12
13
|
}
|
|
13
14
|
import { AbstractShape3D } from "./AbstractShape3D.js";
|
|
14
15
|
//# sourceMappingURL=PointShape3D.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PointShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/PointShape3D.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;
|
|
1
|
+
{"version":3,"file":"PointShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/PointShape3D.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;IAuDI,8BAAqC;IA9CrC,wCAOC;IAED,6CAEC;IAED,oCAEC;IAED,4DAIC;IAED,kFAIC;IAED,qGAIC;IAED,4BAGC;CASJ;gCA7D+B,sBAAsB"}
|
|
@@ -48,5 +48,16 @@ export class PointShape3D extends AbstractShape3D {
|
|
|
48
48
|
result[result_offset + 2] = 0;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
equals(other) {
|
|
52
|
+
// Parameterless: identity is the type alone (a point has no fields).
|
|
53
|
+
return super.equals(other);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
hash() {
|
|
57
|
+
// Constant — every PointShape3D is identical. Distinct from the
|
|
58
|
+
// base-class 0 so it doesn't collide with a hypothetical default shape.
|
|
59
|
+
return 0x504f494e; // "POIN"
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
static INSTANCE = new PointShape3D();
|
|
52
63
|
}
|
|
@@ -32,6 +32,7 @@ export class SphereShape3D extends AbstractShape3D {
|
|
|
32
32
|
signed_distance_at_point(point: any): number;
|
|
33
33
|
contains_point(point: any): boolean;
|
|
34
34
|
sample_random_point_in_volume(result: any, result_offset: any, random: any): void;
|
|
35
|
+
equals(other: any): boolean;
|
|
35
36
|
/**
|
|
36
37
|
* Fast type-check marker. Lets the physics narrowphase short-circuit
|
|
37
38
|
* sphere-involved pairs to closed-form solvers (reading `radius`) rather than
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SphereShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/SphereShape3D.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH;IAUI;;;;OAIG;IACH,oBAHW,MAAM,GACJ,aAAa,CAMzB;IAhBG;;;OAGG;IACH,QAFU,MAAM,CAED;IAwBnB,qGAQC;IAED,wCAQC;IAED,4DAaC;IAED,mEAEC;IAED,6CAEC;IAED,oCAOC;IAED,kFAMC;IAOL;;;;;;;;OAQG;IACH,0BAFU,OAAO,CAEsB;CAXtC;
|
|
1
|
+
{"version":3,"file":"SphereShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/SphereShape3D.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH;IAUI;;;;OAIG;IACH,oBAHW,MAAM,GACJ,aAAa,CAMzB;IAhBG;;;OAGG;IACH,QAFU,MAAM,CAED;IAwBnB,qGAQC;IAED,wCAQC;IAED,4DAaC;IAED,mEAEC;IAED,6CAEC;IAED,oCAOC;IAED,kFAMC;IAED,4BAEC;IAOL;;;;;;;;OAQG;IACH,0BAFU,OAAO,CAEsB;CAXtC;gCApH+B,sBAAsB"}
|
|
@@ -110,6 +110,10 @@ export class SphereShape3D extends AbstractShape3D {
|
|
|
110
110
|
result[result_offset + 2] *= r;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
equals(other) {
|
|
114
|
+
return super.equals(other) && this.radius === other.radius;
|
|
115
|
+
}
|
|
116
|
+
|
|
113
117
|
hash() {
|
|
114
118
|
return computeHashFloat(this.radius);
|
|
115
119
|
}
|
|
@@ -1,255 +1,302 @@
|
|
|
1
|
-
# Collision handling — construction plan
|
|
1
|
+
# Collision handling — ground-up construction plan
|
|
2
2
|
|
|
3
|
-
Companion to DESIGN.md (base controller) and TODO.md.
|
|
4
|
-
roadmap for taking the controller's collision response from the current
|
|
5
|
-
2.5D split to a unified, slope-aware, anti-tunnelling mover modelled on
|
|
6
|
-
the Quake → Source `TryPlayerMove` / `CategorizePosition` lineage —
|
|
7
|
-
using the now-accurate `shapeCast` / `raycast` the physics engine
|
|
8
|
-
gained over the P1–P6 narrowphase work.
|
|
3
|
+
Companion to DESIGN.md (base controller) and TODO.md.
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
17
|
-
## 1.
|
|
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
|
+
```
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
23
45
|
|
|
24
|
-
**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- A full dynamics solver for the player. The player stays a
|
|
29
|
-
`KinematicPosition` body; the controller owns the Transform.
|
|
30
|
-
- Replacing the abilities' bespoke motion models — only giving them a
|
|
31
|
-
correct shared mover to call.
|
|
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.
|
|
32
50
|
|
|
33
51
|
---
|
|
34
52
|
|
|
35
|
-
## 2.
|
|
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.
|
|
36
58
|
|
|
37
|
-
|
|
38
|
-
horizontal-only swept slide
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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).
|
|
46
68
|
|
|
47
69
|
---
|
|
48
70
|
|
|
49
|
-
## 3. Physics capabilities we
|
|
71
|
+
## 3. Physics capabilities we build on
|
|
50
72
|
|
|
51
|
-
The P1–P6 narrowphase
|
|
52
|
-
|
|
73
|
+
The P1–P6 narrowphase/query work is the enabling foundation — the
|
|
74
|
+
rebuild is only feasible because these are now narrowphase-exact.
|
|
53
75
|
|
|
54
76
|
| Capability | Status | Source |
|
|
55
77
|
|---|---|---|
|
|
56
|
-
| `raycast` exact surface **normal** (sphere/box/capsule/mesh/heightmap) | ✅
|
|
57
|
-
| `shapeCast` true contact **normal at TOI**
|
|
58
|
-
| `shapeCast` **start-in-contact** →
|
|
59
|
-
| `shapeCast` analytic slab-narrowing (long-sweep accuracy
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
| box-box edge
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
in case edge/foot placement ever does.
|
|
75
|
-
- `result.normal` is the **target's outward** surface normal.
|
|
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.
|
|
76
96
|
|
|
77
97
|
---
|
|
78
98
|
|
|
79
|
-
## 4. Target architecture
|
|
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).
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
It knows nothing about the controller — just a capsule, a velocity, the
|
|
107
|
+
world, and an `up` axis. Single entry point:
|
|
83
108
|
|
|
84
109
|
```
|
|
85
|
-
move(dt)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
collideAndSlide(delta) # swept, all axes, crease-aware
|
|
89
|
-
categorizeGround() # short down-probe → grounded + normal
|
|
90
|
-
if grounded and walkable: stayOnGround() # snap to surface, zero vy
|
|
110
|
+
move(transform, shape, velocity, dt, filter) → MoveResult
|
|
111
|
+
// mutates transform.position; returns:
|
|
112
|
+
// { velocityX/Y/Z (corrected), grounded, groundNormal, hit }
|
|
91
113
|
```
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
down
|
|
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()`.
|
|
117
158
|
|
|
118
159
|
---
|
|
119
160
|
|
|
120
|
-
## 5. Phased plan
|
|
121
|
-
|
|
122
|
-
### Phase 1 —
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
**Risk:**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
**Risk:**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
### Phase 3 —
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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.
|
|
202
258
|
|
|
203
259
|
---
|
|
204
260
|
|
|
205
|
-
## 6.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
`result.position` is the swept centre today. **Priority: LOW.**
|
|
230
|
-
|
|
231
|
-
4. **Multi-contact ground manifold from one query.** `shapeCast` returns
|
|
232
|
-
the single closest-`t` contact. A capsule straddling the seam between
|
|
233
|
-
two boxes gets one normal, not both — fine for v1 grounded snap, a
|
|
234
|
-
limitation for precise edge-balancing. **Priority: LOW.**
|
|
235
|
-
|
|
236
|
-
If only one thing gets built: **#1**. It turns Phase 4 from "tune a
|
|
237
|
-
blind nudge" into "push out by the exact reported depth," and it's
|
|
238
|
-
mostly plumbing over code that already exists and is tested.
|
|
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.
|
|
239
285
|
|
|
240
286
|
---
|
|
241
287
|
|
|
242
288
|
## 7. Constants (ours ← reference lineage)
|
|
243
289
|
|
|
244
|
-
| Constant | Plan value | Reference |
|
|
290
|
+
| Constant | Plan value | Reference | Phase |
|
|
245
291
|
|---|---|---|---|
|
|
246
|
-
| slide iterations | 4 | Quake `numbumps` 4 |
|
|
247
|
-
| `SKIN` | 0.005 m | Fauerby `veryCloseDistance` ~0.005 |
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
(`v·n < 0`),
|
|
255
|
-
|
|
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.
|