@woosh/meep-engine 2.139.0 → 2.140.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 (172) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
  6. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  7. package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
  8. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  9. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  11. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  12. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  13. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  14. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  17. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  18. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  22. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  23. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  24. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  25. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  26. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  27. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  29. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  30. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  31. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  32. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  41. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  42. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  43. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  44. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  45. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  46. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  47. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  48. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  49. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  50. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  53. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  54. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  55. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  56. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  57. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  59. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  60. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  62. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  63. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  64. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  65. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  66. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  71. package/src/engine/control/first-person/TODO.md +173 -127
  72. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  73. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  74. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  75. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  76. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  77. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  78. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  79. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  80. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  81. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  82. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  83. package/src/engine/physics/PLAN.md +461 -236
  84. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  85. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  87. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  88. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  89. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  90. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  91. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  92. package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
  93. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  94. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
  95. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  96. package/src/engine/physics/fluid/FluidSimulator.js +2 -1
  97. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
  98. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  99. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
  100. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  101. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  102. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  103. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  104. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  105. package/src/engine/physics/gjk/gjk.js +421 -378
  106. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  107. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  108. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  109. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  110. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  111. package/src/engine/physics/gjk/mpr.js +344 -0
  112. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  113. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  114. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  115. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  116. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  117. package/src/engine/physics/integration/integrate_position.js +43 -12
  118. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  119. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  120. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  121. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  122. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  123. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  124. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  125. package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
  126. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  127. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  128. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  129. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  130. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  131. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  132. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  133. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  134. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  135. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  136. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  137. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  138. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  139. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  140. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  141. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  142. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  143. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  144. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  145. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  146. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  147. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  148. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  149. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  150. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  151. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  152. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
  153. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  154. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
  155. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  156. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  157. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  158. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  159. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  160. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  161. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  162. package/src/engine/physics/queries/overlap_shape.js +183 -0
  163. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  164. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  165. package/src/engine/physics/queries/shape_cast.js +387 -0
  166. package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
  167. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  168. package/src/engine/physics/solver/solve_contacts.js +641 -223
  169. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  170. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  171. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  172. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -1,127 +1,173 @@
1
- # First-person controller — TODO
2
-
3
- Working notes. Things we've identified as worth doing but haven't pulled
4
- the trigger on yet. Companion to DESIGN.md (base) and DESIGN_EXTENSIONS.md
5
- (abilities + mastery roadmap).
6
-
7
- ## Mastery evaluators — additional
8
-
9
- We ship StrideTimingJump, FootAsymmetryTurn, BreathRhythm, and
10
- SlideInitiationTiming today. The remaining ones from the
11
- DESIGN_EXTENSIONS Phase 10 list, ordered by what would give the most
12
- texture per unit of work:
13
-
14
- ### WallRunEntryTimingEvaluator
15
- Decision point: `WallRunGravity` (currently un-wired — Wall-run reads
16
- `cfg.wallRun.gravityFactor` straight, no mastery hook). Wire it.
17
-
18
- Input: angle-of-attack against the wall at activation. Lower angle of
19
- attack (entering near-parallel) is the canonical "good" entry — converts
20
- forward momentum into the run smoothly. High angle of attack (entering
21
- near-perpendicular) is "bad" — you're crashing into the wall.
22
-
23
- Curve shape: peak at small angle (1.0 → 0.8, less effective gravity =
24
- more hang time); penalty at high angle (1.0 → 1.15, gravity ramped =
25
- shorter run). Symmetric in behaviour, asymmetric in magnitude.
26
-
27
- Estimated effort: 0.5 day. Capture entry angle in WallRun.onActivate,
28
- stash it on the evaluator's per-instance state OR on a new
29
- `runtime.wallRunEntryAngle` field. Curve + spec.
30
-
31
- ### WallRunTakeoffTimingEvaluator
32
- Decision point: `WallJumpImpulse` (already wired — base WallJump runs
33
- mastery on it).
34
-
35
- Input: how late in the wall-run the wall-jump is fired. Late in the
36
- run (near the timer expiry) is the "explosive last-second push-off"
37
- — bonus. Right at activation is "panicked early jump" — penalty.
38
-
39
- Curve shape: time-based, normalized to [0, 1] over wall-run duration.
40
- Peak at 0.6–0.9 (committed but not desperate). Trough near 0.0
41
- (panicked).
42
-
43
- Estimated effort: 0.5 day. WallJump needs to read how long WallRun was
44
- active when it preempted. Simplest: track `runtime.wallRunElapsedAt
45
- Takeoff` in WallRun.onDeactivate (or via a signal).
46
-
47
- ### WallJumpEntryAngleEvaluator
48
- Decision point: `WallJumpImpulse`.
49
-
50
- Input: angle between the player's velocity vector and the wall normal
51
- at the moment of jump press. A near-perpendicular push-off (velocity
52
- along normal at impact) converts the most into the impulse — bonus.
53
- A near-tangent push (sliding along) is "weak push-off" — penalty.
54
-
55
- Curve shape: peak at dot(velocity, normal) approaching 1.0 (head-on);
56
- penalty near 0 (parallel skim).
57
-
58
- Composes multiplicatively with the takeoff-timing evaluator above —
59
- both fire on `WallJumpImpulse`. That's the point of the multiplicative
60
- composition rule.
61
-
62
- Estimated effort: 0.5 day.
63
-
64
- ### LedgeCatchTimingEvaluator
65
- Decision point: NEW — would need `LedgeCatchExertionScale` or similar.
66
- Currently LedgeGrab uses a flat `cfg.ledgeGrab.exertionRiseRate`.
67
-
68
- Input: |velocityY| at the moment of catch. A gentle catch (low vy) is
69
- forgiving — low exertion rise (longer hang). A desperate save (high
70
- |vy|) is harder — higher exertion rise (shorter hang).
71
-
72
- Curve shape: identity 1.0 at low |vy| (≤ 2 m/s), ramping up to ~1.5×
73
- at high |vy| (≥ 8 m/s).
74
-
75
- Estimated effort: 0.5 day, plus adding the new decision point.
76
-
77
- ### MantleExitSpeedEvaluator
78
- Decision point: `MantleExitSpeed` (currently un-wired).
79
-
80
- Input: stride-phase at the moment of mantle completion. Landing on a
81
- midstance push-off bonus the exit velocity (player runs off the mantle
82
- into the next surface); landing on footfall takes a small penalty.
83
-
84
- Currently Mantle's exit doesn't set any forward velocity — the player
85
- lands at rest. To make this evaluator meaningful, we'd also need
86
- Mantle to apply a small forward push on completion, which the
87
- evaluator then scales. Two-step change.
88
-
89
- Estimated effort: 1 day (more wiring than the others).
90
-
91
- ## Other open notes
92
-
93
- ### Refactor `pose.crouchAmount` away
94
- Now redundant with `pose.postureAmount`. Leave for one or two builds so
95
- any external consumers can migrate, then delete.
96
-
97
- ### Stride-mastery score telemetry
98
- `MasterySet` records EMA scores per decision point. We never surface
99
- these to the HUD or any feedback system. A simple debug overlay
100
- showing the running scores (a sparkline per decision point?) would be
101
- helpful when tuning curves.
102
-
103
- ### Tune the slide bob fade-out
104
- `bobIntensityHalfLife` controls how fast the lateral bob fades when
105
- posture changes to Prone. May be too slow feels like a brief wobble
106
- before the slide settles into "pure motion". Worth play-test tuning.
107
-
108
- ### Posture-aware footstep audio
109
- Surface tag is on `state.surfaceTag`. The footstep signal carries
110
- side + speed + surfaceTag, but emission is gated on
111
- `feetStriking` (Stand or Crouch posture). When other postures want
112
- audio cues (a slide-along-pavement sound, a hang-grip creak), they
113
- need their own signal channels — not piggyback on `onFootStep`.
114
-
115
- ### WallRun direction sense (push-back behaviour)
116
- Currently WallRun projects velocity onto the wall tangent each tick.
117
- If the player turns AWAY from the wall mid-run, the wall-tangent
118
- projection still keeps them pinned. Might want a "lean-away exits the
119
- run" condition: if `intent.move` rotates more than X degrees from the
120
- wall tangent, exit the wall-run.
121
-
122
- ### Animation track for Hang posture
123
- The eye stays at body height while hanging (we don't drop the eye
124
- height in Hang because the body root is already below the ledge). But
125
- the rig should probably show arms reaching up. That's a skeleton
126
- concern, not a controller one — just flagging that `pose.posture =
127
- Hang` is the signal the rig should branch on.
1
+ # First-person controller — TODO
2
+
3
+ Working notes. Things we've identified as worth doing but haven't pulled
4
+ the trigger on yet. Companion to DESIGN.md (base) and DESIGN_EXTENSIONS.md
5
+ (abilities + mastery roadmap).
6
+
7
+ ## Mastery evaluators — additional
8
+
9
+ We ship StrideTimingJump, FootAsymmetryTurn, BreathRhythm, and
10
+ SlideInitiationTiming today. The remaining ones from the
11
+ DESIGN_EXTENSIONS Phase 10 list, ordered by what would give the most
12
+ texture per unit of work:
13
+
14
+ ### WallRunEntryTimingEvaluator
15
+ Decision point: `WallRunGravity` (currently un-wired — Wall-run reads
16
+ `cfg.wallRun.gravityFactor` straight, no mastery hook). Wire it.
17
+
18
+ Input: angle-of-attack against the wall at activation. Lower angle of
19
+ attack (entering near-parallel) is the canonical "good" entry — converts
20
+ forward momentum into the run smoothly. High angle of attack (entering
21
+ near-perpendicular) is "bad" — you're crashing into the wall.
22
+
23
+ Curve shape: peak at small angle (1.0 → 0.8, less effective gravity =
24
+ more hang time); penalty at high angle (1.0 → 1.15, gravity ramped =
25
+ shorter run). Symmetric in behaviour, asymmetric in magnitude.
26
+
27
+ Estimated effort: 0.5 day. Capture entry angle in WallRun.onActivate,
28
+ stash it on the evaluator's per-instance state OR on a new
29
+ `runtime.wallRunEntryAngle` field. Curve + spec.
30
+
31
+ ### WallRunTakeoffTimingEvaluator
32
+ Decision point: `WallJumpImpulse` (already wired — base WallJump runs
33
+ mastery on it).
34
+
35
+ Input: how late in the wall-run the wall-jump is fired. Late in the
36
+ run (near the timer expiry) is the "explosive last-second push-off"
37
+ — bonus. Right at activation is "panicked early jump" — penalty.
38
+
39
+ Curve shape: time-based, normalized to [0, 1] over wall-run duration.
40
+ Peak at 0.6–0.9 (committed but not desperate). Trough near 0.0
41
+ (panicked).
42
+
43
+ Estimated effort: 0.5 day. WallJump needs to read how long WallRun was
44
+ active when it preempted. Simplest: track `runtime.wallRunElapsedAt
45
+ Takeoff` in WallRun.onDeactivate (or via a signal).
46
+
47
+ ### WallJumpEntryAngleEvaluator
48
+ Decision point: `WallJumpImpulse`.
49
+
50
+ Input: angle between the player's velocity vector and the wall normal
51
+ at the moment of jump press. A near-perpendicular push-off (velocity
52
+ along normal at impact) converts the most into the impulse — bonus.
53
+ A near-tangent push (sliding along) is "weak push-off" — penalty.
54
+
55
+ Curve shape: peak at dot(velocity, normal) approaching 1.0 (head-on);
56
+ penalty near 0 (parallel skim).
57
+
58
+ Composes multiplicatively with the takeoff-timing evaluator above —
59
+ both fire on `WallJumpImpulse`. That's the point of the multiplicative
60
+ composition rule.
61
+
62
+ Estimated effort: 0.5 day.
63
+
64
+ ### LedgeCatchTimingEvaluator
65
+ Decision point: NEW — would need `LedgeCatchExertionScale` or similar.
66
+ Currently LedgeGrab uses a flat `cfg.ledgeGrab.exertionRiseRate`.
67
+
68
+ Input: |velocityY| at the moment of catch. A gentle catch (low vy) is
69
+ forgiving — low exertion rise (longer hang). A desperate save (high
70
+ |vy|) is harder — higher exertion rise (shorter hang).
71
+
72
+ Curve shape: identity 1.0 at low |vy| (≤ 2 m/s), ramping up to ~1.5×
73
+ at high |vy| (≥ 8 m/s).
74
+
75
+ Estimated effort: 0.5 day, plus adding the new decision point.
76
+
77
+ ### MantleExitSpeedEvaluator
78
+ Decision point: `MantleExitSpeed` (currently un-wired).
79
+
80
+ Input: stride-phase at the moment of mantle completion. Landing on a
81
+ midstance push-off bonus the exit velocity (player runs off the mantle
82
+ into the next surface); landing on footfall takes a small penalty.
83
+
84
+ Currently Mantle's exit doesn't set any forward velocity — the player
85
+ lands at rest. To make this evaluator meaningful, we'd also need
86
+ Mantle to apply a small forward push on completion, which the
87
+ evaluator then scales. Two-step change.
88
+
89
+ Estimated effort: 1 day (more wiring than the others).
90
+
91
+ ## Move-and-slide follow-ups
92
+
93
+ ### Slide along non-axis-aligned walls
94
+ **Landed.** `PhysicsSystem.shapeCast` now writes the true contact
95
+ normal (narrowphase-refined via GJK + EPA at TOI), and `_moveAndSlide`
96
+ uses the canonical iterative slide: stop at first contact, project the
97
+ residual onto the contact tangent, sweep again, repeat up to 4 times.
98
+ Multi-contact corners stop cleanly; diagonal approaches slide along
99
+ both axis-aligned and oblique surfaces.
100
+
101
+ ### Vertical sweep (anti-tunnel for floors and ceilings)
102
+ `_moveAndSlide` is currently called for horizontal motion only. The
103
+ vertical phase still does a direct position add. For fast falls past
104
+ the ground-resolver's 0.1 m raycast lift, the player can sink through
105
+ the floor in one tick; for vertical wall-jumps into a ceiling, no
106
+ contact is detected.
107
+
108
+ Wiring a vertical sweep into `_integrateVerticalAndResolveGround`
109
+ needs to be coordinated with the SKIN clearance — landing at
110
+ `floor + SKIN` would re-flag the player as airborne by the resolver,
111
+ which would re-apply gravity and bounce. Either drop SKIN on
112
+ floor-normal contacts, or have the resolver use a tolerance instead
113
+ of strict `position.y <= testY`.
114
+
115
+ ### Ability motion routing
116
+ Slide and WallRun integrate position manually (slide friction in the
117
+ ability tick; wall-run reduced-gravity integration). Once the slide-
118
+ along-walls work above lands, threading those through `_moveAndSlide`
119
+ would prevent wall-runners from drifting into the wall and sliders
120
+ from penetrating obstacles in their path. The controller-side change
121
+ is a few lines per ability; the gating issue is having a usable
122
+ surface normal.
123
+
124
+ ## Other open notes
125
+
126
+ ### Horizontal-capsule Prone collider
127
+ The posture collider sync uses a vertical capsule for Prone, sized
128
+ to `cfg.body.proneHeight`. When that's below `2·radius` (the default)
129
+ the capsule degenerates to a sphere of `radius` — Y extent
130
+ `2·radius ≈ 0.68 m`. That fits "low-profile shape" honestly but it
131
+ doesn't reflect what a sliding body actually looks like: long along
132
+ the direction of travel, narrow in the cross-section. A horizontal
133
+ capsule (Y-axis cylinder rotated 90° around X via TransformedShape3D
134
+ matrix, oriented along body-local +Z) would let the player fit
135
+ under 0.4-m ledges while still occupying the full body length
136
+ horizontally. Tracked because the Prone test currently asserts the
137
+ vertical-capsule extent — see PostureCollider.spec.js geometry note.
138
+
139
+ ### Refactor `pose.crouchAmount` away
140
+ Now redundant with `pose.postureAmount`. Leave for one or two builds so
141
+ any external consumers can migrate, then delete.
142
+
143
+ ### Stride-mastery score telemetry
144
+ `MasterySet` records EMA scores per decision point. We never surface
145
+ these to the HUD or any feedback system. A simple debug overlay
146
+ showing the running scores (a sparkline per decision point?) would be
147
+ helpful when tuning curves.
148
+
149
+ ### Tune the slide bob fade-out
150
+ `bobIntensityHalfLife` controls how fast the lateral bob fades when
151
+ posture changes to Prone. May be too slow — feels like a brief wobble
152
+ before the slide settles into "pure motion". Worth play-test tuning.
153
+
154
+ ### Posture-aware footstep audio
155
+ Surface tag is on `state.surfaceTag`. The footstep signal carries
156
+ side + speed + surfaceTag, but emission is gated on
157
+ `feetStriking` (Stand or Crouch posture). When other postures want
158
+ audio cues (a slide-along-pavement sound, a hang-grip creak), they
159
+ need their own signal channels — not piggyback on `onFootStep`.
160
+
161
+ ### WallRun direction sense (push-back behaviour)
162
+ Currently WallRun projects velocity onto the wall tangent each tick.
163
+ If the player turns AWAY from the wall mid-run, the wall-tangent
164
+ projection still keeps them pinned. Might want a "lean-away exits the
165
+ run" condition: if `intent.move` rotates more than X degrees from the
166
+ wall tangent, exit the wall-run.
167
+
168
+ ### Animation track for Hang posture
169
+ The eye stays at body height while hanging (we don't drop the eye
170
+ height in Hang because the body root is already below the ledge). But
171
+ the rig should probably show arms reaching up. That's a skeleton
172
+ concern, not a controller one — just flagging that `pose.posture =
173
+ Hang` is the signal the rig should branch on.
@@ -1 +1 @@
1
- {"version":3,"file":"Slide.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/control/first-person/abilities/Slide.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAOQ,gEAAgE;IAChE,0BAA0B;IAG9B,mEAeC;IAED,gDAuBC;IAED,uDAGC;IAED,uFAmEC;IAED,oDAEC;CACJ;wBA1JuB,cAAc"}
1
+ {"version":3,"file":"Slide.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/control/first-person/abilities/Slide.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAOQ,gEAAgE;IAChE,0BAA0B;IAG9B,mEAuBC;IAED,gDAuBC;IAED,uDAGC;IAED,uFAmEC;IAED,oDAEC;CACJ;wBAlKuB,cAAc"}
@@ -47,7 +47,15 @@ export class Slide extends Ability {
47
47
  // simply by holding crouch and walking up to sprint speed.)
48
48
  if (runtime.prevCrouchHeld) return false;
49
49
  // Need significant horizontal speed — slide is a run-into-slide,
50
- // not a standstill-into-slide.
50
+ // not a standstill-into-slide. The mono-exponential ground
51
+ // accel model (see FirstPersonPlayerControllerConfig.js's
52
+ // `groundAccelHalfLife`) ensures reaching this threshold from
53
+ // standstill naturally requires ~300 ms of committed sprint —
54
+ // longer than human reaction time — so the speed gate alone is
55
+ // enough to make slide feel earned. Speed-only is also the
56
+ // right model for the cases the design wants to explicitly
57
+ // allow: walking off a ledge or accelerating down a slope into
58
+ // a slide. (Following Apex Legends' velocity-only gate.)
51
59
  const speed = Math.hypot(runtime.velocityX, runtime.velocityZ);
52
60
  if (speed < cfg.minEntrySpeed) return false;
53
61
  return true;
@@ -1,7 +1,9 @@
1
- import { BoxGeometry, MeshStandardMaterial } from "three";
1
+ import { BoxGeometry, MeshStandardMaterial, TorusKnotGeometry } from "three";
2
2
  import { Ray3 } from "../../../core/geom/3d/ray/Ray3.js";
3
3
  import { BoxShape3D } from "../../../core/geom/3d/shape/BoxShape3D.js";
4
4
  import { CapsuleShape3D } from "../../../core/geom/3d/shape/CapsuleShape3D.js";
5
+ import { TransformedShape3D } from "../../../core/geom/3d/shape/TransformedShape3D.js";
6
+ import { shape_mesh_from_geometry } from "../../../core/geom/3d/shape/shape_mesh_from_geometry.js";
5
7
  import Vector2 from "../../../core/geom/Vector2.js";
6
8
  import Vector3 from "../../../core/geom/Vector3.js";
7
9
  import { SerializationMetadata } from "../../ecs/components/SerializationMetadata.js";
@@ -217,6 +219,80 @@ function buildGym(ecd) {
217
219
  buildLedgeGrabStation(ecd);
218
220
  buildSlideTunnel(ecd);
219
221
  buildGapJumpStation(ecd);
222
+ buildMeshShapeShowcase(ecd);
223
+ }
224
+
225
+ /**
226
+ * Drop a torus-knot prop off to the side as a smoke test for
227
+ * {@link MeshShape3D}: arbitrary indexed triangle mesh → tetrahedralised
228
+ * collider via {@link shape_mesh_from_geometry}.
229
+ *
230
+ * Dynamic body, so the player capsule (kinematic) can bump it around.
231
+ * KinematicPosition bodies don't receive impulses themselves (the
232
+ * controller writes the Transform), so a static knot vs. kinematic
233
+ * capsule produces zero solver response and the capsule clips through.
234
+ * Making the knot dynamic flips the relationship: when the capsule
235
+ * penetrates, the solver applies impulse to the knot — it bounces away
236
+ * and the player gets a visible reaction.
237
+ *
238
+ * Placed off the spawn plaza's main travel path (radius 18 north-east of
239
+ * spawn) so it's visible but not in the way of the parkour drills.
240
+ */
241
+ function buildMeshShapeShowcase(ecd) {
242
+ // TorusKnotGeometry (radius, tube, tubularSegments, radialSegments).
243
+ // 64 × 8 = 512 segments → ~512 verts / ~1024 tris — plenty of detail
244
+ // for visual interest, light enough that the per-vertex `support()`
245
+ // scan stays fast.
246
+ const geom = new TorusKnotGeometry(0.5, 0.15, 64, 8);
247
+ geom.computeVertexNormals();
248
+
249
+ // Three packs positions as Float32Array (interleaved x, y, z) and
250
+ // indices as Uint16Array by default. The MeshShape3D factory wants
251
+ // a Uint32Array index buffer — convert once at construction.
252
+ const positions = geom.attributes.position.array;
253
+ const src_indices = geom.index.array;
254
+ const indices = src_indices instanceof Uint32Array
255
+ ? src_indices
256
+ : Uint32Array.from(src_indices);
257
+
258
+ const shape = shape_mesh_from_geometry(positions, indices);
259
+
260
+ const material = new MeshStandardMaterial({ color: 0xffaa66, roughness: 0.5 });
261
+ const sg = ShadedGeometry.from(geom, material);
262
+ sg.setFlag(ShadedGeometryFlags.CastShadow);
263
+ sg.setFlag(ShadedGeometryFlags.ReceiveShadow);
264
+ sg.setFlag(ShadedGeometryFlags.Visible);
265
+
266
+ const transform = new Transform();
267
+ // North-east of spawn, lifted ~2m off the ground so the knot drops
268
+ // onto the floor when the sim starts and the player can walk over,
269
+ // bump it, and watch it tumble.
270
+ transform.position.set(SPAWN_X + 18, 2, SPAWN_Z - 18);
271
+
272
+ const rigidBody = new RigidBody();
273
+ rigidBody.kind = BodyKind.Dynamic;
274
+ rigidBody.mass = 1;
275
+ // Bounding radius ≈ main_radius + tube_radius = 0.65. For a chunky
276
+ // bumpable body matching the existing 1m cube reference, use the
277
+ // same `(6, 6, 6)` inverse inertia — it's slightly under-rotational
278
+ // for the torus knot's mass distribution but reads as solid.
279
+ rigidBody.inverseInertiaLocal.set(6, 6, 6);
280
+ rigidBody.linearDamping = 0.5;
281
+ rigidBody.angularDamping = 0.5;
282
+
283
+ const collider = new Collider();
284
+ collider.shape = shape;
285
+ collider.friction = 0.5;
286
+ collider.restitution = 0.2;
287
+
288
+ new Entity()
289
+ .add(sg)
290
+ .add(rigidBody)
291
+ .add(collider)
292
+ .add(transform)
293
+ .add(Tag.fromJSON(["GymObstacle"]))
294
+ .add(SerializationMetadata.Transient)
295
+ .build(ecd);
220
296
  }
221
297
 
222
298
  /**
@@ -612,7 +688,17 @@ function buildPlayerEntity(ecd) {
612
688
  rigidBody.kind = BodyKind.KinematicPosition;
613
689
  rigidBody.mass = bodyCfg.mass;
614
690
  const collider = new Collider();
615
- collider.shape = CapsuleShape3D.from(radius, cylinderHeight);
691
+ // Capsule lives in [feet .. head] in world space. CapsuleShape3D is
692
+ // centred at its local origin, so wrap it in a TransformedShape3D
693
+ // with a +Y translation of totalHeight/2 — that puts the capsule
694
+ // bottom at the player's feet (Transform.position) and the top at
695
+ // the head. Without this, shape_cast queries against the ground
696
+ // would stop the capsule centre at floor + half-extent, leaving
697
+ // the player visibly floating half a body height above the floor.
698
+ collider.shape = TransformedShape3D.from_translation(
699
+ CapsuleShape3D.from(radius, cylinderHeight),
700
+ [0, totalHeight / 2, 0],
701
+ );
616
702
 
617
703
  // Build order matters: Transform is added LAST so the FirstPerson
618
704
  // controller system's link() sees the RigidBody already attached.
@@ -1 +1 @@
1
- {"version":3,"file":"buildTestPlayer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/control/first-person/test/buildTestPlayer.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AACH;eAF0B,SAAS;cAAY,QAAQ;EAWtD;0BAxByB,mCAAmC;yBADpC,kCAAkC"}
1
+ {"version":3,"file":"buildTestPlayer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/control/first-person/test/buildTestPlayer.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH;eAF0B,SAAS;cAAY,QAAQ;EAkBtD;0BA/ByB,mCAAmC;yBADpC,kCAAkC"}
@@ -1,4 +1,5 @@
1
1
  import { CapsuleShape3D } from "../../../../core/geom/3d/shape/CapsuleShape3D.js";
2
+ import { TransformedShape3D } from "../../../../core/geom/3d/shape/TransformedShape3D.js";
2
3
  import { BodyKind } from "../../../physics/ecs/BodyKind.js";
3
4
  import { Collider } from "../../../physics/ecs/Collider.js";
4
5
  import { RigidBody } from "../../../physics/ecs/RigidBody.js";
@@ -22,7 +23,14 @@ export function buildTestPlayerBody() {
22
23
  rigidBody.mass = 1;
23
24
 
24
25
  const collider = new Collider();
25
- collider.shape = CapsuleShape3D.from(0.34, 1.12);
26
+ // Capsule lives in [feet .. head] in world space — see the prototype's
27
+ // buildPlayerEntity for the rationale. Wrap the centred CapsuleShape3D
28
+ // in a TransformedShape3D with a +Y offset of half the body height
29
+ // (1.8 / 2 = 0.9), matching prototype geometry.
30
+ collider.shape = TransformedShape3D.from_translation(
31
+ CapsuleShape3D.from(0.34, 1.12),
32
+ [0, 0.9, 0],
33
+ );
26
34
 
27
35
  return { rigidBody, collider };
28
36
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A capsule aligned with the Y axis, built as a single continuous surface of
3
+ * revolution: a top hemispherical cap, a cylindrical mid-section and a bottom
4
+ * hemispherical cap.
5
+ *
6
+ * Dimensions follow the {@link CapsuleShape3D} convention:
7
+ * - `radius` is the radius of both the caps and the cylindrical section.
8
+ * - `height` is the length of the cylindrical section ONLY (the caps are
9
+ * added on top of it), so the total extent along Y is `height + 2 * radius`,
10
+ * spanning `[-(height/2 + radius), +(height/2 + radius)]`, centred at the
11
+ * origin.
12
+ *
13
+ * Two further parameters control tessellation:
14
+ * - `capSegments` rings per hemispherical cap (from equator to pole).
15
+ * - `radialSegments` segments around the Y axis.
16
+ *
17
+ * Watertightness: the mesh is emitted as one grid of vertex rings running from
18
+ * the top pole down to the bottom pole. Consecutive rings — including the
19
+ * top-equator -> bottom-equator ring that spans the cylinder — are stitched
20
+ * sharing their ring vertices, so there is never a gap between the caps and the
21
+ * body. The angular seam is duplicated only for UV continuity (the duplicate
22
+ * sits at the same position) and each pole is a proper triangle fan, so welding
23
+ * vertices by position yields a closed, consistently oriented 2-manifold.
24
+ */
25
+ export class CapsuleGeometry extends BufferGeometry {
26
+ static fromJSON(data: any): CapsuleGeometry;
27
+ /**
28
+ * @param {number} [radius=0.5] radius of the caps and the cylindrical section
29
+ * @param {number} [height=1] length of the cylindrical section, excluding the caps
30
+ * @param {number} [capSegments=8] number of rings in each hemispherical cap
31
+ * @param {number} [radialSegments=16] number of segments around the Y axis
32
+ */
33
+ constructor(radius?: number, height?: number, capSegments?: number, radialSegments?: number);
34
+ parameters: {
35
+ radius: number;
36
+ height: number;
37
+ capSegments: number;
38
+ radialSegments: number;
39
+ };
40
+ }
41
+ import { BufferGeometry } from "three";
42
+ //# sourceMappingURL=CapsuleGeometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CapsuleGeometry.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/geometry/CapsuleGeometry.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IA6II,4CAEC;IA9ID;;;;;OAKG;IACH,qBALW,MAAM,WACN,MAAM,gBACN,MAAM,mBACN,MAAM,EAsIhB;IA/HG;;;;;MAKC;CA+HR;+BA1K+D,OAAO"}