@woosh/meep-engine 2.138.17 → 2.138.19

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 (449) hide show
  1. package/README.md +2 -2
  2. package/build/bundle-worker-image-decoder.js +1 -1
  3. package/editor/actions/concrete/ActionUpdateTexture.d.ts +12 -0
  4. package/editor/actions/concrete/ActionUpdateTexture.d.ts.map +1 -0
  5. package/editor/actions/concrete/ArrayCopyAction.d.ts +20 -0
  6. package/editor/actions/concrete/ArrayCopyAction.d.ts.map +1 -0
  7. package/editor/actions/concrete/ComponentRemoveAction.d.ts +11 -0
  8. package/editor/actions/concrete/ComponentRemoveAction.d.ts.map +1 -0
  9. package/editor/actions/concrete/ModifyPatchSampler2DAction.d.ts +47 -0
  10. package/editor/actions/concrete/ModifyPatchSampler2DAction.d.ts.map +1 -0
  11. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.d.ts +38 -0
  12. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.d.ts.map +1 -0
  13. package/editor/actions/concrete/PaintTerrainOverlayAction.d.ts +23 -0
  14. package/editor/actions/concrete/PaintTerrainOverlayAction.d.ts.map +1 -0
  15. package/editor/actions/concrete/PatchTerrainHeightAction.d.ts +19 -0
  16. package/editor/actions/concrete/PatchTerrainHeightAction.d.ts.map +1 -0
  17. package/editor/actions/concrete/SelectionRemoveAction.d.ts +10 -0
  18. package/editor/actions/concrete/SelectionRemoveAction.d.ts.map +1 -0
  19. package/editor/actions/concrete/WriteGridValueAction.d.ts +15 -0
  20. package/editor/actions/concrete/WriteGridValueAction.d.ts.map +1 -0
  21. package/editor/ecs/component/FieldDescriptor.d.ts +27 -0
  22. package/editor/ecs/component/FieldDescriptor.d.ts.map +1 -0
  23. package/editor/ecs/component/FieldValueAdapter.d.ts +7 -0
  24. package/editor/ecs/component/FieldValueAdapter.d.ts.map +1 -0
  25. package/editor/ecs/component/createFieldEditor.d.ts +9 -0
  26. package/editor/ecs/component/createFieldEditor.d.ts.map +1 -0
  27. package/editor/ecs/component/createObjectEditor.d.ts +14 -0
  28. package/editor/ecs/component/createObjectEditor.d.ts.map +1 -0
  29. package/editor/ecs/component/editors/geom/QuaternionEditor.d.ts.map +1 -1
  30. package/editor/ecs/component/findNearestRegisteredType.d.ts +8 -0
  31. package/editor/ecs/component/findNearestRegisteredType.d.ts.map +1 -0
  32. package/editor/process/ObstacleGridDisplayProcess.d.ts.map +1 -1
  33. package/editor/process/symbolic/makeGridPositionSymbolDisplay.d.ts.map +1 -1
  34. package/editor/tools/GridPaintTool.d.ts +17 -0
  35. package/editor/tools/GridPaintTool.d.ts.map +1 -0
  36. package/editor/tools/SelectionTool.d.ts +27 -0
  37. package/editor/tools/SelectionTool.d.ts.map +1 -0
  38. package/editor/tools/TopDownCameraControlTool.d.ts +13 -0
  39. package/editor/tools/TopDownCameraControlTool.d.ts.map +1 -0
  40. package/editor/view/ecs/ComponentControlFactory.d.ts.map +1 -0
  41. package/editor/view/ecs/ComponentControlView.d.ts.map +1 -1
  42. package/editor/view/ecs/EntityEditor.d.ts.map +1 -0
  43. package/editor/view/ecs/EntityList.d.ts.map +1 -0
  44. package/editor/view/ecs/HierarchicalEntityListView.d.ts.map +1 -0
  45. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  46. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  47. package/editor/view/node-graph/NodeView.d.ts.map +1 -1
  48. package/editor/view/node-graph/PortView.d.ts.map +1 -1
  49. package/package.json +1 -1
  50. package/src/core/binary/BinaryBuffer.d.ts +1 -1
  51. package/src/core/binary/BinaryBuffer.js +1 -1
  52. package/src/core/binary/BitSet.d.ts +1 -1
  53. package/src/core/binary/BitSet.js +1 -1
  54. package/src/core/cache/Cache.js +1 -1
  55. package/src/core/cache/LoadingCache.js +1 -1
  56. package/src/core/collection/list/List.js +1 -1
  57. package/src/core/collection/map/HashMap.js +1 -1
  58. package/src/core/collection/table/RowFirstTable.d.ts +1 -1
  59. package/src/core/collection/table/RowFirstTable.js +1 -1
  60. package/src/core/collection/table/RowFirstTableSpec.d.ts +1 -1
  61. package/src/core/collection/table/RowFirstTableSpec.js +1 -1
  62. package/src/core/color/oklab/compute_max_saturation.d.ts +1 -1
  63. package/src/core/color/oklab/compute_max_saturation.js +1 -1
  64. package/src/core/color/oklab/find_cusp.d.ts +1 -1
  65. package/src/core/color/oklab/find_cusp.js +1 -1
  66. package/src/core/color/oklab/find_gamut_intersection.d.ts +1 -1
  67. package/src/core/color/oklab/find_gamut_intersection.js +1 -1
  68. package/src/core/color/oklab/okhsv_to_linear_srgb.d.ts +1 -1
  69. package/src/core/color/oklab/okhsv_to_linear_srgb.js +1 -1
  70. package/src/core/color/oklab/oklab_to_linear_srgb.d.ts +1 -1
  71. package/src/core/color/oklab/oklab_to_linear_srgb.js +1 -1
  72. package/src/core/color/oklab/oklab_to_xyz.d.ts +1 -1
  73. package/src/core/color/oklab/oklab_to_xyz.js +1 -1
  74. package/src/core/color/oklab/xyz_to_oklab.d.ts +1 -1
  75. package/src/core/color/oklab/xyz_to_oklab.js +1 -1
  76. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.d.ts +1 -1
  77. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +1 -1
  78. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.d.ts.map +1 -1
  79. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +57 -65
  80. package/src/core/geom/3d/octahedra/octahedral_uv_wrap.d.ts +1 -1
  81. package/src/core/geom/3d/octahedra/octahedral_uv_wrap.js +1 -1
  82. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.d.ts +1 -1
  83. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +1 -1
  84. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.d.ts +1 -1
  85. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +1 -1
  86. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +74 -3
  87. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +37 -0
  88. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -0
  89. package/src/core/geom/3d/shape/CapsuleShape3D.js +210 -0
  90. package/src/core/geom/3d/shape/PointShape3D.d.ts +5 -0
  91. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  92. package/src/core/geom/3d/shape/PointShape3D.js +52 -14
  93. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +75 -12
  94. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  95. package/src/core/geom/3d/shape/TransformedShape3D.js +12 -3
  96. package/src/core/geom/3d/shape/UnionShape3D.d.ts +47 -5
  97. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  98. package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts +12 -5
  99. package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts.map +1 -1
  100. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +17 -5
  101. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
  102. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  103. package/src/core/geom/3d/shape/json/shape_to_type.js +4 -1
  104. package/src/core/geom/3d/shape/json/type_adapters.d.ts +17 -2
  105. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  106. package/src/core/geom/3d/shape/json/type_adapters.js +18 -2
  107. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.d.ts.map +1 -1
  108. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -48
  109. package/src/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.d.ts +1 -1
  110. package/src/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +1 -1
  111. package/src/core/geom/ConicRay.d.ts +1 -1
  112. package/src/core/geom/ConicRay.js +1 -1
  113. package/src/core/geom/Quaternion.d.ts +2 -2
  114. package/src/core/geom/Quaternion.js +2 -2
  115. package/src/core/geom/Vector1.d.ts +1 -1
  116. package/src/core/geom/Vector1.js +1 -1
  117. package/src/core/geom/Vector2.d.ts +1 -1
  118. package/src/core/geom/Vector2.js +1 -1
  119. package/src/core/geom/Vector3.d.ts +1 -1
  120. package/src/core/geom/Vector3.js +1 -1
  121. package/src/core/geom/packing/miniball/Miniball.d.ts +1 -1
  122. package/src/core/geom/packing/miniball/Miniball.js +1 -1
  123. package/src/core/geom/vec3/serialization/v3_binary_equality_decode.d.ts +1 -1
  124. package/src/core/geom/vec3/serialization/v3_binary_equality_decode.js +1 -1
  125. package/src/core/geom/vec3/serialization/v3_binary_equality_encode.d.ts +1 -1
  126. package/src/core/geom/vec3/serialization/v3_binary_equality_encode.js +1 -1
  127. package/src/core/math/spline/spline3_hermite.d.ts +1 -1
  128. package/src/core/math/spline/spline3_hermite.js +1 -1
  129. package/src/core/math/spline/spline3_hermite_bounds.d.ts +1 -1
  130. package/src/core/math/spline/spline3_hermite_bounds.js +1 -1
  131. package/src/core/math/spline/spline3_hermite_bounds_t.d.ts +1 -1
  132. package/src/core/math/spline/spline3_hermite_bounds_t.js +1 -1
  133. package/src/core/math/spline/spline3_hermite_to_monomial.d.ts +1 -1
  134. package/src/core/math/spline/spline3_hermite_to_monomial.js +1 -1
  135. package/src/core/model/ObservedBoolean.d.ts +1 -1
  136. package/src/core/model/ObservedBoolean.js +1 -1
  137. package/src/core/model/ObservedString.d.ts +1 -1
  138. package/src/core/model/ObservedString.js +1 -1
  139. package/src/core/process/undo/Action.js +1 -1
  140. package/src/core/process/undo/ActionProcessor.js +1 -1
  141. package/src/engine/animation/curve/AnimationCurve.d.ts +1 -1
  142. package/src/engine/animation/curve/AnimationCurve.js +1 -1
  143. package/src/engine/animation/curve/Keyframe.d.ts +1 -1
  144. package/src/engine/animation/curve/Keyframe.js +1 -1
  145. package/src/engine/animation/curve/animation_curve_compute_aabb.d.ts +1 -1
  146. package/src/engine/animation/curve/animation_curve_compute_aabb.js +1 -1
  147. package/src/engine/animation/curve/animation_curve_optimize.d.ts +1 -1
  148. package/src/engine/animation/curve/animation_curve_optimize.js +2 -2
  149. package/src/engine/asset/loaders/image/jpeg/JpegImage.js +1 -1
  150. package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
  151. package/src/engine/asset/loaders/image/png/PNGReader.js +27 -7
  152. package/src/engine/asset/loaders/image/png/crc32.d.ts +1 -1
  153. package/src/engine/asset/loaders/image/png/crc32.js +1 -1
  154. package/src/engine/control/first-person/DESIGN.md +616 -0
  155. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +229 -0
  156. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -0
  157. package/src/engine/control/first-person/FirstPersonPlayerController.js +176 -0
  158. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +251 -0
  159. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -0
  160. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +205 -0
  161. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +151 -0
  162. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -0
  163. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +799 -0
  164. package/src/engine/control/first-person/math/computeJumpFromApex.d.ts +23 -0
  165. package/src/engine/control/first-person/math/computeJumpFromApex.d.ts.map +1 -0
  166. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -0
  167. package/src/engine/control/first-person/math/criticallyDampedSpring.d.ts +23 -0
  168. package/src/engine/control/first-person/math/criticallyDampedSpring.d.ts.map +1 -0
  169. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -0
  170. package/src/engine/control/first-person/math/dampedSpringStep.d.ts +23 -0
  171. package/src/engine/control/first-person/math/dampedSpringStep.d.ts.map +1 -0
  172. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -0
  173. package/src/engine/control/first-person/math/stepTowards.d.ts +12 -0
  174. package/src/engine/control/first-person/math/stepTowards.d.ts.map +1 -0
  175. package/src/engine/control/first-person/math/stepTowards.js +20 -0
  176. package/src/engine/control/first-person/pose/FirstPersonPose.d.ts +69 -0
  177. package/src/engine/control/first-person/pose/FirstPersonPose.d.ts.map +1 -0
  178. package/src/engine/control/first-person/pose/FirstPersonPose.js +91 -0
  179. package/src/engine/control/first-person/prototype_first_person_controller.d.ts +2 -0
  180. package/src/engine/control/first-person/prototype_first_person_controller.d.ts.map +1 -0
  181. package/src/engine/control/first-person/prototype_first_person_controller.js +314 -0
  182. package/src/engine/ecs/Entity.js +1 -1
  183. package/src/engine/ecs/EntityComponentDataset.js +1 -1
  184. package/src/engine/ecs/EntityManager.d.ts +1 -1
  185. package/src/engine/ecs/EntityManager.js +1 -1
  186. package/src/engine/ecs/EntityObserver.js +1 -1
  187. package/src/engine/ecs/EntityReference.d.ts +1 -1
  188. package/src/engine/ecs/EntityReference.js +1 -1
  189. package/src/engine/ecs/System.js +1 -1
  190. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +2 -2
  191. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +1 -1
  192. package/src/engine/ecs/grid/HeightMap2AOMap.d.ts.map +1 -1
  193. package/src/engine/ecs/grid/HeightMap2AOMap.js +3 -2
  194. package/src/engine/ecs/guid/UUID.d.ts +1 -1
  195. package/src/engine/ecs/guid/UUID.js +1 -1
  196. package/src/engine/ecs/name/Name.d.ts +1 -1
  197. package/src/engine/ecs/name/Name.js +1 -1
  198. package/src/engine/ecs/parent/ParentEntity.js +1 -1
  199. package/src/engine/ecs/storage/BinaryBufferDeSerializer.d.ts +1 -1
  200. package/src/engine/ecs/storage/BinaryBufferDeSerializer.js +1 -1
  201. package/src/engine/ecs/storage/BinaryBufferSerializer.d.ts +1 -1
  202. package/src/engine/ecs/storage/BinaryBufferSerializer.js +1 -1
  203. package/src/engine/ecs/storage/binary/BinaryClassSerializationAdapter.js +1 -1
  204. package/src/engine/ecs/storage/binary/BinarySerializationRegistry.d.ts +1 -1
  205. package/src/engine/ecs/storage/binary/BinarySerializationRegistry.js +1 -1
  206. package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.d.ts +1 -1
  207. package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.js +1 -1
  208. package/src/engine/ecs/transform/Transform.d.ts +1 -1
  209. package/src/engine/ecs/transform/Transform.js +1 -1
  210. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts.map +1 -1
  211. package/src/engine/graphics/impostors/octahedral/ImpostorBaker.d.ts.map +1 -1
  212. package/src/engine/graphics/impostors/octahedral/ImpostorBaker.js +10 -1
  213. package/src/engine/graphics/impostors/voxel/README.md +149 -0
  214. package/src/engine/graphics/impostors/voxel/VoxelImpostorBaker.d.ts +91 -0
  215. package/src/engine/graphics/impostors/voxel/VoxelImpostorBaker.d.ts.map +1 -0
  216. package/src/engine/graphics/impostors/voxel/VoxelImpostorBaker.js +376 -0
  217. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.d.ts +128 -0
  218. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.d.ts.map +1 -0
  219. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -0
  220. package/src/engine/graphics/impostors/voxel/bake/read_atlas_to_samples.d.ts +38 -0
  221. package/src/engine/graphics/impostors/voxel/bake/read_atlas_to_samples.d.ts.map +1 -0
  222. package/src/engine/graphics/impostors/voxel/bake/read_atlas_to_samples.js +338 -0
  223. package/src/engine/graphics/impostors/voxel/bake/voxelize_samples.d.ts +43 -0
  224. package/src/engine/graphics/impostors/voxel/bake/voxelize_samples.d.ts.map +1 -0
  225. package/src/engine/graphics/impostors/voxel/bake/voxelize_samples.js +192 -0
  226. package/src/engine/graphics/impostors/voxel/prototype_voxel_impostors.d.ts +2 -0
  227. package/src/engine/graphics/impostors/voxel/prototype_voxel_impostors.d.ts.map +1 -0
  228. package/src/engine/graphics/impostors/voxel/prototype_voxel_impostors.js +343 -0
  229. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderDepthV0.d.ts +13 -0
  230. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderDepthV0.d.ts.map +1 -0
  231. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderDepthV0.js +99 -0
  232. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderLitV0.d.ts +19 -0
  233. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderLitV0.d.ts.map +1 -0
  234. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderLitV0.js +231 -0
  235. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderNormalsV0.d.ts +5 -0
  236. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderNormalsV0.d.ts.map +1 -0
  237. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderNormalsV0.js +70 -0
  238. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderV0.d.ts +13 -0
  239. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderV0.d.ts.map +1 -0
  240. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderV0.js +127 -0
  241. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderViewportDepthV0.d.ts +5 -0
  242. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderViewportDepthV0.d.ts.map +1 -0
  243. package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderViewportDepthV0.js +68 -0
  244. package/src/engine/graphics/impostors/voxel/util/make_voxel_impostor_geometry.d.ts +27 -0
  245. package/src/engine/graphics/impostors/voxel/util/make_voxel_impostor_geometry.d.ts.map +1 -0
  246. package/src/engine/graphics/impostors/voxel/util/make_voxel_impostor_geometry.js +96 -0
  247. package/src/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.d.ts +113 -29
  248. package/src/engine/graphics/render/forward_plus/SPECIFICATION.md +1 -1
  249. package/src/engine/graphics/shaders/AmbientOcclusionShader.d.ts.map +1 -1
  250. package/src/engine/graphics/shaders/AmbientOcclusionShader.js +20 -5
  251. package/src/engine/graphics/texture/sampler/Sampler2D.d.ts +1 -1
  252. package/src/engine/graphics/texture/sampler/Sampler2D.js +1 -1
  253. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_NaiveFlood.d.ts +1 -1
  254. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_NaiveFlood.js +1 -1
  255. package/src/engine/graphics/util/build_max_height_pyramid.d.ts +21 -14
  256. package/src/engine/graphics/util/build_max_height_pyramid.d.ts.map +1 -1
  257. package/src/engine/graphics/util/build_max_height_pyramid.js +107 -70
  258. package/src/engine/input/devices/InputDeviceSwitch.js +1 -1
  259. package/src/engine/input/devices/KeyboardDevice.js +1 -1
  260. package/src/engine/input/devices/PointerDevice.js +1 -1
  261. package/src/engine/intelligence/behavior/Behavior.js +1 -1
  262. package/src/engine/intelligence/behavior/SelectorBehavior.d.ts +1 -1
  263. package/src/engine/intelligence/behavior/SelectorBehavior.js +1 -1
  264. package/src/engine/intelligence/behavior/behavior_to_dot.d.ts +1 -1
  265. package/src/engine/intelligence/behavior/behavior_to_dot.js +1 -1
  266. package/src/engine/intelligence/behavior/composite/CompositeBehavior.d.ts +1 -1
  267. package/src/engine/intelligence/behavior/composite/CompositeBehavior.js +1 -1
  268. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts +1 -1
  269. package/src/engine/intelligence/behavior/composite/ParallelBehavior.js +1 -1
  270. package/src/engine/intelligence/behavior/composite/SequenceBehavior.d.ts +1 -1
  271. package/src/engine/intelligence/behavior/composite/SequenceBehavior.js +1 -1
  272. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.d.ts +1 -1
  273. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.js +1 -1
  274. package/src/engine/intelligence/behavior/decorator/IgnoreFailureBehavior.d.ts +1 -1
  275. package/src/engine/intelligence/behavior/decorator/IgnoreFailureBehavior.js +1 -1
  276. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.d.ts +1 -1
  277. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.js +1 -1
  278. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts +1 -1
  279. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.js +1 -1
  280. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.d.ts +1 -1
  281. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.js +1 -1
  282. package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts +1 -1
  283. package/src/engine/intelligence/behavior/ecs/BehaviorComponent.js +1 -1
  284. package/src/engine/intelligence/behavior/ecs/BehaviorSystem.d.ts +1 -1
  285. package/src/engine/intelligence/behavior/ecs/BehaviorSystem.js +1 -1
  286. package/src/engine/intelligence/behavior/ecs/DieBehavior.d.ts +1 -1
  287. package/src/engine/intelligence/behavior/ecs/DieBehavior.js +1 -1
  288. package/src/engine/intelligence/behavior/ecs/EntityBehavior.d.ts +1 -1
  289. package/src/engine/intelligence/behavior/ecs/EntityBehavior.js +1 -1
  290. package/src/engine/intelligence/behavior/ecs/KillBehavior.d.ts +1 -1
  291. package/src/engine/intelligence/behavior/ecs/KillBehavior.js +1 -1
  292. package/src/engine/intelligence/behavior/ecs/SendEventBehavior.d.ts +1 -1
  293. package/src/engine/intelligence/behavior/ecs/SendEventBehavior.js +1 -1
  294. package/src/engine/intelligence/behavior/ecs/WaitForEventBehavior.d.ts +1 -1
  295. package/src/engine/intelligence/behavior/ecs/WaitForEventBehavior.js +1 -1
  296. package/src/engine/intelligence/behavior/primitive/ActionBehavior.d.ts +1 -1
  297. package/src/engine/intelligence/behavior/primitive/ActionBehavior.js +1 -1
  298. package/src/engine/intelligence/behavior/primitive/FailingBehavior.d.ts +1 -1
  299. package/src/engine/intelligence/behavior/primitive/FailingBehavior.js +1 -1
  300. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.d.ts +1 -1
  301. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.js +1 -1
  302. package/src/engine/intelligence/behavior/primitive/SucceedingBehavior.d.ts +1 -1
  303. package/src/engine/intelligence/behavior/primitive/SucceedingBehavior.js +1 -1
  304. package/src/engine/intelligence/behavior/selector/WeightedElement.d.ts +1 -1
  305. package/src/engine/intelligence/behavior/selector/WeightedElement.js +1 -1
  306. package/src/engine/intelligence/behavior/selector/WeightedRandomBehavior.d.ts +1 -1
  307. package/src/engine/intelligence/behavior/selector/WeightedRandomBehavior.js +1 -1
  308. package/src/engine/intelligence/behavior/util/BranchBehavior.d.ts +1 -1
  309. package/src/engine/intelligence/behavior/util/BranchBehavior.js +1 -1
  310. package/src/engine/intelligence/behavior/util/DelayBehavior.d.ts +1 -1
  311. package/src/engine/intelligence/behavior/util/DelayBehavior.js +1 -1
  312. package/src/engine/intelligence/behavior/util/LogMessageBehavior.d.ts +1 -1
  313. package/src/engine/intelligence/behavior/util/LogMessageBehavior.js +1 -1
  314. package/src/engine/intelligence/behavior/util/RandomDelayBehavior.d.ts +1 -1
  315. package/src/engine/intelligence/behavior/util/RandomDelayBehavior.js +1 -1
  316. package/src/engine/intelligence/blackboard/Blackboard.js +1 -1
  317. package/src/engine/intelligence/mcts/MonteCarlo.d.ts +1 -1
  318. package/src/engine/intelligence/mcts/MonteCarlo.js +1 -1
  319. package/src/engine/intelligence/mcts/MoveEdge.d.ts +1 -1
  320. package/src/engine/intelligence/mcts/MoveEdge.js +1 -1
  321. package/src/engine/intelligence/mcts/StateNode.d.ts +1 -1
  322. package/src/engine/intelligence/mcts/StateNode.js +1 -1
  323. package/src/engine/intelligence/resource/ActionSequence.d.ts +1 -1
  324. package/src/engine/intelligence/resource/ActionSequence.js +1 -1
  325. package/src/engine/intelligence/resource/Resource.d.ts +1 -1
  326. package/src/engine/intelligence/resource/Resource.js +1 -1
  327. package/src/engine/intelligence/resource/ResourceAllocation.d.ts +1 -1
  328. package/src/engine/intelligence/resource/ResourceAllocation.js +1 -1
  329. package/src/engine/intelligence/resource/ResourceAllocationBid.d.ts +1 -1
  330. package/src/engine/intelligence/resource/ResourceAllocationBid.js +1 -1
  331. package/src/engine/intelligence/resource/ResourceAllocationSolver.d.ts +1 -1
  332. package/src/engine/intelligence/resource/ResourceAllocationSolver.js +1 -1
  333. package/src/engine/intelligence/resource/StrategicResourceAllocator.d.ts +1 -1
  334. package/src/engine/intelligence/resource/StrategicResourceAllocator.js +1 -1
  335. package/src/engine/intelligence/resource/TacticalModule.d.ts +1 -1
  336. package/src/engine/intelligence/resource/TacticalModule.js +1 -1
  337. package/src/engine/network/NetworkSession.d.ts +1 -1
  338. package/src/engine/network/NetworkSession.js +1 -1
  339. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  340. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  341. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  342. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  343. package/src/engine/network/adapters/TransformReplicationAdapter.d.ts +1 -1
  344. package/src/engine/network/adapters/TransformReplicationAdapter.js +1 -1
  345. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  346. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  347. package/src/engine/network/core/quantize/quantize_float.d.ts +1 -1
  348. package/src/engine/network/core/quantize/quantize_float.js +1 -1
  349. package/src/engine/network/core/quantize/quantize_position.d.ts +1 -1
  350. package/src/engine/network/core/quantize/quantize_position.js +1 -1
  351. package/src/engine/network/core/sequence/ack_bitfield.d.ts +1 -1
  352. package/src/engine/network/core/sequence/ack_bitfield.js +1 -1
  353. package/src/engine/network/core/sequence/seq16.d.ts +1 -1
  354. package/src/engine/network/core/sequence/seq16.js +1 -1
  355. package/src/engine/network/core/sequence/seq32.d.ts +1 -1
  356. package/src/engine/network/core/sequence/seq32.js +1 -1
  357. package/src/engine/network/diagnostics/BandwidthMeter.d.ts +1 -1
  358. package/src/engine/network/diagnostics/BandwidthMeter.js +1 -1
  359. package/src/engine/network/diagnostics/ReplayLog.d.ts +1 -1
  360. package/src/engine/network/diagnostics/ReplayLog.js +1 -1
  361. package/src/engine/network/diagnostics/SyncTest.d.ts +1 -1
  362. package/src/engine/network/diagnostics/SyncTest.js +1 -1
  363. package/src/engine/network/ecs/NetworkSystem.d.ts +1 -1
  364. package/src/engine/network/ecs/NetworkSystem.js +1 -1
  365. package/src/engine/network/ecs/components/NetworkIdentity.d.ts +1 -1
  366. package/src/engine/network/ecs/components/NetworkIdentity.js +1 -1
  367. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.d.ts +1 -1
  368. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +1 -1
  369. package/src/engine/network/orchestrator/NetworkPeer.d.ts +1 -1
  370. package/src/engine/network/orchestrator/NetworkPeer.js +1 -1
  371. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +1 -1
  372. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +1 -1
  373. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +1 -1
  374. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +1 -1
  375. package/src/engine/network/replication/Replicator.d.ts +1 -1
  376. package/src/engine/network/replication/Replicator.js +1 -1
  377. package/src/engine/network/replication/ScopeFilter.d.ts +2 -2
  378. package/src/engine/network/replication/ScopeFilter.js +2 -2
  379. package/src/engine/network/sim/ActionLog.d.ts +1 -1
  380. package/src/engine/network/sim/ActionLog.js +1 -1
  381. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts +1 -1
  382. package/src/engine/network/sim/BinaryInterpolationAdapter.js +1 -1
  383. package/src/engine/network/sim/InterpolationLog.d.ts +1 -1
  384. package/src/engine/network/sim/InterpolationLog.js +1 -1
  385. package/src/engine/network/sim/ReplicatedComponentRegistry.d.ts +1 -1
  386. package/src/engine/network/sim/ReplicatedComponentRegistry.js +1 -1
  387. package/src/engine/network/sim/RewindEngine.d.ts +1 -1
  388. package/src/engine/network/sim/RewindEngine.js +1 -1
  389. package/src/engine/network/sim/SimAction.d.ts +1 -1
  390. package/src/engine/network/sim/SimAction.js +1 -1
  391. package/src/engine/network/sim/SimActionExecutor.d.ts +1 -1
  392. package/src/engine/network/sim/SimActionExecutor.js +1 -1
  393. package/src/engine/network/sim/SimActionRegistry.d.ts +1 -1
  394. package/src/engine/network/sim/SimActionRegistry.js +1 -1
  395. package/src/engine/network/sim/SmoothingState.js +1 -1
  396. package/src/engine/network/sim/Snapshotter.d.ts +1 -1
  397. package/src/engine/network/sim/Snapshotter.js +1 -1
  398. package/src/engine/network/sim/SpeculationLog.d.ts +1 -1
  399. package/src/engine/network/sim/SpeculationLog.js +1 -1
  400. package/src/engine/network/state/Baseline.d.ts +1 -1
  401. package/src/engine/network/state/Baseline.js +1 -1
  402. package/src/engine/network/state/ChangedEntitySet.d.ts +1 -1
  403. package/src/engine/network/state/ChangedEntitySet.js +1 -1
  404. package/src/engine/network/state/InputRing.d.ts +1 -1
  405. package/src/engine/network/state/InputRing.js +1 -1
  406. package/src/engine/network/state/MutationLedger.d.ts +1 -1
  407. package/src/engine/network/state/MutationLedger.js +1 -1
  408. package/src/engine/network/state/PriorityAccumulator.d.ts +1 -1
  409. package/src/engine/network/state/PriorityAccumulator.js +1 -1
  410. package/src/engine/network/state/ReplicationSlotTable.d.ts +1 -1
  411. package/src/engine/network/state/ReplicationSlotTable.js +1 -1
  412. package/src/engine/network/time/AdaptiveRenderDelay.d.ts +1 -1
  413. package/src/engine/network/time/AdaptiveRenderDelay.js +1 -1
  414. package/src/engine/network/time/JitterBuffer.d.ts +1 -1
  415. package/src/engine/network/time/JitterBuffer.js +1 -1
  416. package/src/engine/network/time/TimeDilation.d.ts +1 -1
  417. package/src/engine/network/time/TimeDilation.js +1 -1
  418. package/src/engine/network/time/TimeSync.d.ts +1 -1
  419. package/src/engine/network/time/TimeSync.js +1 -1
  420. package/src/engine/network/transport/Channel.d.ts.map +1 -1
  421. package/src/engine/network/transport/Channel.js +3 -6
  422. package/src/engine/network/transport/LoopbackTransport.d.ts +1 -1
  423. package/src/engine/network/transport/LoopbackTransport.js +1 -1
  424. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +1 -1
  425. package/src/engine/network/transport/ReliableCommandPipeline.js +1 -1
  426. package/src/engine/network/transport/Transport.d.ts +1 -1
  427. package/src/engine/network/transport/Transport.js +1 -1
  428. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts +1 -1
  429. package/src/engine/network/transport/adapters/NodeUDPTransport.js +2 -2
  430. package/src/engine/network/transport/adapters/SimulatedTransport.d.ts +1 -1
  431. package/src/engine/network/transport/adapters/SimulatedTransport.js +1 -1
  432. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts +1 -1
  433. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +1 -1
  434. package/src/engine/network/transport/adapters/WebSocketTransport.d.ts +1 -1
  435. package/src/engine/network/transport/adapters/WebSocketTransport.js +1 -1
  436. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts +1 -1
  437. package/src/engine/network/transport/adapters/WebTransportTransport.js +1 -1
  438. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts +1 -1
  439. package/src/engine/network/transport/fragments/FragmentAssembler.js +1 -1
  440. package/src/engine/network/transport/fragments/FragmentRetention.d.ts +1 -1
  441. package/src/engine/network/transport/fragments/FragmentRetention.js +1 -1
  442. package/src/engine/network/transport/fragments/packet_size.d.ts +1 -1
  443. package/src/engine/network/transport/fragments/packet_size.js +1 -1
  444. package/src/engine/simulation/Ticker.d.ts +1 -1
  445. package/src/engine/simulation/Ticker.js +1 -1
  446. package/src/engine/sound/SoundEngine.js +1 -1
  447. package/src/engine/ui/DraggableAspect.d.ts +1 -1
  448. package/src/engine/ui/DraggableAspect.js +1 -1
  449. package/src/view/View.js +1 -1
@@ -0,0 +1,616 @@
1
+ # FirstPersonPlayerController — Design
2
+
3
+ > Status: **Draft** — design doc. No implementation yet.
4
+ > Scope: one entity, one body, one camera. No skeleton, no avatar mesh, no sound, no input wiring.
5
+ > Companion files (planned): `FirstPersonPlayerController.js` (component), `FirstPersonPlayerControllerSystem.js` (system), serialization adapter, spec.
6
+
7
+ ---
8
+
9
+ ## 1. Goals
10
+
11
+ The controller has two intertwined goals. Every design decision below should be justified against at least one of them.
12
+
13
+ 1. **Feel alive.** Convey weight, inertia, breath, exertion, and consequence. The player should sense a body even when the screen shows only the world.
14
+ 2. **Be configurable.** Every parameter that contributes to "feel" must be exposed and tunable per-entity, with sensible defaults. Different characters (heavy soldier vs. light scout vs. drunk NPC) must be expressible by data alone.
15
+
16
+ ### Non-goals (explicitly out of scope)
17
+
18
+ - Visual representation of the body (arms, weapon, third-person mesh, shadow).
19
+ - Input wiring (keyboard, gamepad, touch). The controller exposes an **intent surface**; binding is the job of `InputController` or a script.
20
+ - Collision and ground-truth physics resolution. This document assumes a separate physics or capsule-controller layer reports `grounded`, `groundNormal`, and resolves penetration. The controller writes desired velocity / impulses.
21
+ - Skeletal pose evaluation. The controller exports **pose channels** (numeric outputs) intended for a future skeleton system to consume.
22
+ - Sound. The controller exports **events** intended for a future audio system to consume.
23
+ - Networking / rollback. The component is designed to be deterministic per fixed update so this is possible later, but no replication is included.
24
+
25
+ ---
26
+
27
+ ## 2. Research notes
28
+
29
+ A condensed reading of public sources informed this design:
30
+
31
+ - **3Cs framework** (Camera, Character, Controls) — these three are interdependent; tuning one warps the others. Treat them as one design surface even though they live in separate components. Joel Nilsson (Massive, *Avatar*) and the gamedevpills writeup are the canonical references.
32
+ - **Mirror's Edge Catalyst camera** (DICE / Erik Söderholm) — the hardest problem is "not feeling like a box that glides around". Solution: animate the camera with body weight, not just track a capsule. Inform jump anticipation, land recovery, breathing, head tilt with the same data the body uses.
33
+ - **Star Citizen FPS stances & breathing** — breathing is an *active subsystem*, not decoration. Breath rate scales with stamina/exertion. Camera sway has 1s rest pauses at full stamina and shrinks to ~0.2s pauses when depleted. Heavy gear amplifies it.
34
+ - **Playtank "First-Person 3Cs: Camera"** — a layered transform hierarchy (Root → Head → Noise → Camera) lets effects compose without coupling. Anti-motion-sickness: keep horizon stable by default, make roll/shake opt-in.
35
+ - **NeoFPS additive transforms** — effects are an additive stack; translation and rotation compose independently so layer order doesn't matter for one but does for the other.
36
+ - **Spring-roll-call** (Daniel Holden) — critically damped springs with half-life parameterization beat lerp for camera smoothing. Under-damped springs are the right tool for landing recovery (they overshoot and ring slightly, which reads as compression).
37
+ - **Coyote time + jump buffer + variable jump height** — three small forgiveness tricks that single-handedly elevate a "stiff" controller. Coyote ~80–120 ms, buffer ~80–150 ms, fall gravity multiplier 1.5–2.5×.
38
+ - **Gait research** — a walk/run cycle is two footfalls per stride; cadence scales roughly with √(g·L) where L is leg length (the pendulum approximation). Heel-strike events happen at the bottom of the vertical bob.
39
+
40
+ ---
41
+
42
+ ## 3. Architectural overview
43
+
44
+ ```
45
+ ┌────────────────────────────────────────────────────────────────────┐
46
+ │ ENTITY │
47
+ │ │
48
+ │ ┌──────────────────────┐ ┌──────────────┐ ┌────────────────┐ │
49
+ │ │ FirstPersonPlayer- │ │ Transform │ │ Motion │ │
50
+ │ │ Controller │ │ (BODY) │ │ (velocity) │ │
51
+ │ │ │ │ │ │ │ │
52
+ │ │ - config │ │ │ │ │ │
53
+ │ │ - intent (RW) │ └──────────────┘ └────────────────┘ │
54
+ │ │ - state (RO) │ ▲ │
55
+ │ │ - signals (RO) │ │ written by System │
56
+ │ │ - pose channels (RO) │ │ │
57
+ │ └──────────────────────┘ │ │
58
+ │ ▲ │ │
59
+ │ │ ┌─────────┴────────┐ │
60
+ │ │ │ Camera entity │ │
61
+ │ │ │ (child or │ │
62
+ │ │ │ referenced) │ │
63
+ │ │ │ - Transform (EYE)│ │
64
+ │ │ │ - Camera │ │
65
+ │ │ └──────────────────┘ │
66
+ │ │ │
67
+ │ └── intent set by: InputController binding / AI script │
68
+ │ pose channels read by: future skeleton system │
69
+ │ signals consumed by: future audio / FX systems │
70
+ └────────────────────────────────────────────────────────────────────┘
71
+ ```
72
+
73
+ ### Two transforms, not one
74
+
75
+ The controller drives **two** transforms:
76
+
77
+ 1. **Body transform** — the entity's own `Transform`. Position is the ground-projected foot/capsule origin. Yaw is the body's facing. Pitch and roll on the body are always zero. The body is what a physics system would integrate.
78
+ 2. **Eye transform** — a separate transform (camera entity, attached via `TransformAttachment` or held as a referenced entity). Position is body + (height − crouchDip) + (bob + breath + landKick + shake). Rotation is body-yaw × eye-pitch × (lean roll). This is where all the "feel" lives.
79
+
80
+ Why split: body kinematics must remain physics-clean (no jitter into the collider); the camera must be free to lie, dip, and oscillate without affecting collision or AI line-of-sight.
81
+
82
+ ### Five processing layers
83
+
84
+ Each frame the system walks these in order:
85
+
86
+ | Layer | Reads | Writes | Cadence |
87
+ |---|---|---|---|
88
+ | **L0 Intent** | (external) | `intent.*` | event-driven |
89
+ | **L1 Locomotion** | `intent`, `state.grounded` | `Motion.velocity`, `Transform` (body) | fixed |
90
+ | **L2 Pose** | `state`, `config` | `pose.*` channels | fixed |
91
+ | **L3 Camera** | `pose`, `state` | eye `Transform`, `Camera.fov` | variable (per-frame) |
92
+ | **L4 Events** | derived from `state` transitions | `signals.*` | fixed (where possible) |
93
+
94
+ L1, L2, L4 run in `fixedUpdate` so they're deterministic and not framerate-coupled. L3 runs in `update` because camera smoothing must be at render rate to avoid judder.
95
+
96
+ ---
97
+
98
+ ## 4. The component: `FirstPersonPlayerController`
99
+
100
+ Standard meep component contract: `typeName`, `toJSON/fromJSON`, `copy/clone/equals/hash`. Fields are grouped into five sub-objects so the public API stays legible.
101
+
102
+ ```
103
+ class FirstPersonPlayerController {
104
+ static typeName = "FirstPersonPlayerController"
105
+
106
+ config : FirstPersonConfig // tunables — serialized
107
+ intent : FirstPersonIntent // written by input/AI — transient
108
+ state : FirstPersonState // read-only outputs — transient
109
+ pose : FirstPersonPose // read-only outputs for skeleton — transient
110
+ signals : FirstPersonSignals // Signal<...> instances — transient
111
+
112
+ /** Reference to the eye/camera entity; created on link if absent. */
113
+ eyeEntity : EntityReference
114
+ }
115
+ ```
116
+
117
+ Only `config` is serialized. Everything else is rebuilt on `link()`.
118
+
119
+ ### 4.1 `FirstPersonConfig` — the tunable surface
120
+
121
+ Grouped by feel-axis so designers can browse without scrolling past 80 floats.
122
+
123
+ ```
124
+ config = {
125
+ body: {
126
+ height : 1.80 // m, standing eye height
127
+ crouchHeight : 1.10 // m, crouched eye height
128
+ radius : 0.35 // m, capsule radius (for downstream physics)
129
+ mass : 80 // kg, drives bob amplitude & land impact
130
+ },
131
+
132
+ motion: {
133
+ walkSpeed : 3.0 // m/s
134
+ sprintSpeed : 6.0 // m/s
135
+ crouchSpeed : 1.4 // m/s
136
+ airControl : 0.25 // [0..1] how much intent influences air velocity
137
+ groundAccel : 35 // m/s² (~ "snap" of start-up)
138
+ groundDecel : 50 // m/s² when intent zero
139
+ airAccel : 8 // m/s²
140
+ turnRateMax : Inf // rad/s — Inf = instantaneous (mouse-look default)
141
+ },
142
+
143
+ jump: {
144
+ peakHeight : 1.2 // m, the *desired* clean-jump apex
145
+ timeToApex : 0.40 // s — together with peakHeight → gravity & impulse
146
+ coyoteTime : 0.10 // s of grace after walking off a ledge
147
+ bufferTime : 0.12 // s a queued jump remains valid before landing
148
+ fallGravityMult : 2.0 // gravity ×N once vy < 0 or jump released early
149
+ cutGravityMult : 1.8 // gravity ×N if jump released before apex (variable height)
150
+ anticipation: { // squash before take-off
151
+ duration : 0.08 // s
152
+ dipAmount : 0.06 // m, eye dips this much
153
+ curve : "ease-out-quad"
154
+ }
155
+ },
156
+
157
+ landing: {
158
+ softThreshold : 3.0 // m/s vertical — below = soft signal
159
+ hardThreshold : 7.5 // m/s vertical — above = hard signal
160
+ recovery: {
161
+ dipPerVy : 0.012 // m of eye dip per m/s impact velocity
162
+ dipMax : 0.18 // m, clamp
163
+ spring: {
164
+ halfLife : 0.12 // s — under-damped (zeta < 1) for the ring
165
+ zeta : 0.55
166
+ }
167
+ }
168
+ },
169
+
170
+ crouch: {
171
+ transitionTime : 0.18 // s to lerp eye height
172
+ mode : "hold" // "hold" | "toggle" — affects intent semantics
173
+ },
174
+
175
+ bob: {
176
+ // Cadence is derived from speed: freq = stepFreqAtWalk * (speed / walkSpeed) ^ stepFreqExp
177
+ stepFreqAtWalk : 1.8 // Hz (≈ 1.8 footfalls/s, both feet)
178
+ stepFreqExp : 0.9 // sub-linear so sprint isn't frantic
179
+ verticalAmpAtWalk: 0.045 // m
180
+ lateralAmpAtWalk : 0.025 // m — figure-8 (vert is 2x lateral freq)
181
+ ampMassScale : 0.005 // m per kg over reference (80kg)
182
+ rollAtWalkDeg : 0.6 // deg, sympathetic head roll
183
+ // Pattern: vertical(t) = A_v * |sin(πft)| (footfall = trough)
184
+ // lateral (t) = A_l * sin(2πft / 2) (one full cycle = 2 steps)
185
+ // roll (t) = A_r * sin(2πft / 2) (in phase with lateral)
186
+ },
187
+
188
+ breath: {
189
+ // Resting respiration — 14 breaths/min ≈ 0.23 Hz
190
+ rateRestHz : 0.23
191
+ rateMaxHz : 0.70 // panting at full exertion
192
+ amplitudeRestM : 0.004 // tiny — barely visible at rest
193
+ amplitudeMaxM : 0.022 // visible chest heave when exhausted
194
+ pitchAmpRestDeg : 0.10
195
+ pitchAmpMaxDeg : 0.55
196
+ // Optional micro-noise on top of the sine, perlin@0.7Hz, ±20% of amp
197
+ noiseAmount : 0.20
198
+ },
199
+
200
+ exertion: {
201
+ // Single scalar [0..1]. Inputs raise it, time decays it.
202
+ sprintRiseRate : 0.20 // /s while sprinting
203
+ jumpRise : 0.08 // per jump
204
+ idleDecayRate : 0.12 // /s while walking or still
205
+ // Carry-over: after exertion peaks, breath rate decays slower than amplitude
206
+ // (you keep breathing hard for a moment after stopping).
207
+ rateDecayHalfLife: 4.0 // s
208
+ ampDecayHalfLife : 2.0 // s
209
+ },
210
+
211
+ lean: {
212
+ enabled : true // ACCESSIBILITY — make false to disable roll
213
+ maxRollDeg : 2.5 // deg per (lateral m/s² / 9.81)
214
+ spring: { halfLife: 0.18, zeta: 1.0 }
215
+ },
216
+
217
+ fov: {
218
+ base : 70 // deg
219
+ sprintAdd : 6 // deg added when at full sprint speed
220
+ crouchAdd : -3
221
+ smoothHalfLife : 0.20 // s
222
+ },
223
+
224
+ look: {
225
+ pitchMinDeg : -85
226
+ pitchMaxDeg : 85
227
+ invertY : false
228
+ // Sensitivity itself belongs to the input layer, not here.
229
+ }
230
+ }
231
+ ```
232
+
233
+ Every nested object is itself a normal object that round-trips through `toJSON`. Serialization adapter just walks the tree.
234
+
235
+ ### 4.2 `FirstPersonIntent` — the control surface
236
+
237
+ This is what input bindings or AI scripts write to. The system reads it once per fixed update and zeroes the *consumable* bits (`look`).
238
+
239
+ ```
240
+ intent = {
241
+ move : Vector2 // [-1..1, -1..1] — forward+/right+ in body-local (state)
242
+ look : Vector2 // accumulated delta yaw/pitch in radians (consumed each fixed step)
243
+ jump : boolean // HOLD STATE — true while button held; edge detected inside system
244
+ crouch : boolean // HOLD STATE — meaning depends on config.crouch.mode
245
+ sprint : boolean // HOLD STATE — sprint applies only when moveY > 0 and grounded
246
+ }
247
+ ```
248
+
249
+ Three input semantics live here:
250
+ - **Delta**: `look` — accumulated between fixed updates, zeroed on consume.
251
+ - **State (hold)**: `move`, `crouch`, `sprint`, `jump` — latched. Always reflects the current physical button/stick state.
252
+ - **Edge**: detected internally by the system from `jump` (and from `crouch` when `config.crouch.mode === "toggle"`). The intent surface never exposes "I just pressed it" — that's the system's bookkeeping. This is what guarantees a held jump button doesn't auto-repeat.
253
+
254
+ That makes all three common input topologies trivial to wire:
255
+ - Mouse + keyboard: mouse writes `look += delta`, WASD writes `move`, space-down/space-up writes `jump`.
256
+ - Gamepad: right stick writes `look = stick * sensitivity * dt`, left stick writes `move`, A-down/A-up writes `jump`.
257
+ - AI: scripts write the same fields directly, no input device required. Scripts that want a "single jump" set `jump = true` for one fixed step then `jump = false`.
258
+
259
+ ### 4.3 `FirstPersonState` — derived, read-only
260
+
261
+ ```
262
+ state = {
263
+ // Grounding
264
+ grounded : boolean
265
+ timeSinceGrounded : number // s — feeds coyote time
266
+ groundNormal : Vector3 // unit, set by physics layer; default Up
267
+ surfaceTag : string? // optional, "grass" | "wood" | ... for FX
268
+
269
+ // Locomotion
270
+ speed : number // m/s, horizontal magnitude of Motion.velocity
271
+ speedNormalized : number // [0..1] vs sprintSpeed — drives bob amp/freq
272
+ moveMode : enum // Idle | Walk | Sprint | Crouch | Air
273
+
274
+ // Vertical
275
+ verticalSpeed : number // signed m/s
276
+ airborneTime : number // s since leaving ground
277
+ fallDistance : number // m fallen since last grounded
278
+
279
+ // Jump bookkeeping
280
+ jumpBufferRemaining: number // s — set when intent.jump fires; consumed on land
281
+ inJumpAnticipation : boolean // true during the squash window
282
+ isVariableJumpCut : boolean // true if button released before apex
283
+
284
+ // Exertion & breath
285
+ exertion : number // [0..1], smoothed
286
+ breathPhase : number // [0..1) — fraction through current breath cycle
287
+ breathRateHz : number // current actual rate
288
+ breathAmplitudeM : number // current actual amplitude
289
+
290
+ // Stride
291
+ stridePhase : number // [0..1) — fraction through 2-step cycle
292
+ stepCount : integer // monotonic; useful for "every Nth step" effects
293
+
294
+ // Pose
295
+ eyeHeight : number // current m above body origin (lerped during crouch)
296
+
297
+ // Tilt
298
+ leanRollRad : number // current eye roll
299
+ }
300
+ ```
301
+
302
+ ### 4.4 `FirstPersonPose` — channels for a skeleton system
303
+
304
+ These are the *outputs* a future humanoid rig should drive itself from. Designed so the same controller can later feed:
305
+ - a first-person arms rig (arms + weapon),
306
+ - a third-person full-body rig,
307
+ - a network-replicated remote rig.
308
+
309
+ ```
310
+ pose = {
311
+ // World-space body
312
+ rootPosition : Vector3
313
+ rootYawRad : number // body facing
314
+
315
+ // Head decoupled from body (the "look")
316
+ headYawRad : number // = body yaw for most controllers
317
+ headPitchRad : number
318
+ headRollRad : number // includes lean
319
+
320
+ // Locomotion phase — for animation playback
321
+ locomotionPhase : number // [0..1), synced to stridePhase
322
+ locomotionSpeed : number // m/s, for blend tree
323
+ locomotionStrafe : number // [-1..1], for strafe-blend space
324
+
325
+ // Action state — for state machine driving
326
+ actionState : enum // Grounded | Anticipating | Airborne | Landing
327
+ crouchAmount : number // [0..1] interpolation between standing/crouched poses
328
+ aimPitch : number // = headPitchRad, surfaced separately for IK
329
+ }
330
+ ```
331
+
332
+ The pose channels are intentionally *redundant* with `state` — they're a stable contract for downstream systems, even if `state` internals churn.
333
+
334
+ ### 4.5 `FirstPersonSignals` — event surface
335
+
336
+ ```
337
+ signals = {
338
+ onFootStep : Signal<{ side: "L"|"R", speed: number, surfaceTag: string? }>
339
+ onJumpStart : Signal<{ peakHeight: number }> // fires at impulse, after anticipation
340
+ onJumpApex : Signal<{}>
341
+ onLeaveGround: Signal<{ reason: "jump"|"fall" }>
342
+ onLand : Signal<{ verticalSpeed: number, kind: "soft"|"hard" }>
343
+ onCrouchEnter: Signal<{}>
344
+ onCrouchExit : Signal<{}>
345
+ onSprintStart: Signal<{}>
346
+ onSprintStop : Signal<{}>
347
+ onBreathIn : Signal<{ amplitude: number, rateHz: number }> // peak inhale
348
+ onBreathOut : Signal<{ amplitude: number, rateHz: number }> // peak exhale
349
+ }
350
+ ```
351
+
352
+ All signals fire from `fixedUpdate` so listeners see consistent state. `onFootStep` fires twice per stride at the troughs of vertical bob. The `side` flips each footstep.
353
+
354
+ ---
355
+
356
+ ## 5. The system: `FirstPersonPlayerControllerSystem`
357
+
358
+ ```
359
+ class FirstPersonPlayerControllerSystem extends System {
360
+ dependencies = [FirstPersonPlayerController, Transform, Motion]
361
+ components_used = [
362
+ spec(Transform, RW),
363
+ spec(Motion, RW),
364
+ spec(Camera, RW), // on the eye entity
365
+ ]
366
+
367
+ fixedUpdate(dt) { /* L1, L2, L4 — deterministic */ }
368
+ update(dt) { /* L3 — camera composition at render rate */ }
369
+
370
+ link(controller, transform, motion, entity) { /* spawn eye entity if missing, build signals, snapshot defaults */ }
371
+ unlink(controller, transform, motion, entity) { /* tear down eye entity, clear signals */ }
372
+ }
373
+ ```
374
+
375
+ ### 5.1 `fixedUpdate` — pseudocode
376
+
377
+ ```
378
+ for each entity (controller, bodyTransform, motion):
379
+ // ---- L1: Locomotion -------------------------------------------------
380
+ consumeLookDelta(controller.intent.look) → apply to bodyYaw, eyePitch (clamped)
381
+ desiredSpeed = pickSpeed(controller, state.moveMode)
382
+ desiredVel = bodyLocal(intent.move) * desiredSpeed
383
+ accel = state.grounded ? config.motion.groundAccel : config.motion.airAccel
384
+ motion.velocity.xz = stepTowards(motion.velocity.xz, desiredVel, accel * dt)
385
+
386
+ // Jump
387
+ if intent.jump consumed:
388
+ state.jumpBufferRemaining = config.jump.bufferTime
389
+ state.jumpBufferRemaining -= dt
390
+
391
+ canJump = (state.grounded || state.timeSinceGrounded < config.jump.coyoteTime)
392
+ && state.jumpBufferRemaining > 0
393
+ if canJump and not state.inJumpAnticipation:
394
+ beginJumpAnticipation() // schedules impulse after anticipation.duration
395
+ signals.onJumpStart.send(...)
396
+
397
+ integrateAnticipationAndImpulse(dt)
398
+ applyGravity(dt, with fallGravityMult / cutGravityMult)
399
+ integrate(bodyTransform.position += motion.velocity * dt)
400
+
401
+ // Ground detection feedback (set externally by physics layer or assumed flat)
402
+ updateGroundedFromExternalContact()
403
+
404
+ // ---- L2: Pose -------------------------------------------------------
405
+ updateExertion(dt)
406
+ updateBreath(dt) // advances breathPhase, computes amp/rate
407
+ updateStride(dt) // advances stridePhase when grounded & moving
408
+ updateCrouch(dt) // lerps eyeHeight
409
+ updateLeanTarget(dt) // computes target roll from lateral accel; springed in L3
410
+
411
+ // ---- L4: Events -----------------------------------------------------
412
+ if stridePhase wrapped past 0 or 0.5:
413
+ signals.onFootStep.send({ side: nextSide(), ... })
414
+ detectGroundedEdges() // sends onLeaveGround / onLand
415
+ detectJumpApex()
416
+ detectBreathPeaks() // sends onBreathIn / onBreathOut
417
+ detectCrouchEdges()
418
+ detectSprintEdges()
419
+
420
+ // Publish pose channels
421
+ writePoseChannels()
422
+ ```
423
+
424
+ ### 5.2 `update` (variable rate) — camera composition
425
+
426
+ ```
427
+ for each entity:
428
+ eyeT = eye transform
429
+ body = body transform
430
+
431
+ // Base eye position (body local)
432
+ base = (0, state.eyeHeight, 0)
433
+
434
+ // Additive offsets — composed in the body's local space, then world-transformed
435
+ bobOffset = computeBob(state.stridePhase, state.speedNormalized, config)
436
+ breathOffset = computeBreath(state.breathPhase, state.breathAmplitudeM, config)
437
+ landOffset = landingSpring.evaluate(dt) // under-damped, decays to 0
438
+ anticipOffset= state.inJumpAnticipation ? anticipationCurve.eval() : 0
439
+ shakeOffset = externalShakeStack.evaluate(dt) // future: gunfire, explosions
440
+
441
+ localEyePos = base + (bobOffset + breathOffset + landOffset + anticipOffset + shakeOffset)
442
+ eyeT.position = body.position + bodyRotation * localEyePos
443
+
444
+ // Rotation: yaw from body, pitch from look, roll from lean + bob
445
+ rollTotal = leanSpring.evaluate(dt) + bobRoll(state.stridePhase, config)
446
+ eyeT.rotation = quatYaw(body.yaw) * quatPitch(state.eyePitch) * quatRoll(rollTotal)
447
+
448
+ // FOV
449
+ fovTarget = config.fov.base
450
+ + lerp(0, config.fov.sprintAdd, state.speedNormalized * sprintness)
451
+ + (state.moveMode == Crouch ? config.fov.crouchAdd : 0)
452
+ camera.fov = springTowards(camera.fov, fovTarget, config.fov.smoothHalfLife, dt)
453
+ ```
454
+
455
+ ### 5.3 Key formulas
456
+
457
+ **Jump from peak height & time-to-apex** (lets designers think in m and seconds, not Newtons):
458
+ ```
459
+ g = 2 * peakHeight / (timeToApex²)
460
+ initialVy = 2 * peakHeight / timeToApex
461
+ ```
462
+ On variable-height cut: switch gravity to `g * cutGravityMult` once button released *and* `vy > 0`.
463
+ On descent: switch gravity to `g * fallGravityMult` once `vy <= 0`.
464
+ This matches the classic Sonic/Mario "fast fall" feel.
465
+
466
+ **Stride frequency from speed**:
467
+ ```
468
+ f = config.bob.stepFreqAtWalk
469
+ * (max(speed, ε) / config.motion.walkSpeed) ^ config.bob.stepFreqExp
470
+ ```
471
+ At rest `f → 0` and bob amplitude clamps to 0, so the player stops bobbing when they stop.
472
+
473
+ **Bob shape** (vertical uses `|sin|` so each footfall is a trough; horizontal uses half-rate sine so left/right alternate):
474
+ ```
475
+ phase = stridePhase * 2π // one full cycle per 2 steps
476
+ vert = -A_v * |sin(phase)| * speedNormalized
477
+ lateral = A_l * sin(phase / 2) * speedNormalized // half-rate
478
+ roll = A_r * sin(phase / 2) * speedNormalized // in phase with lateral
479
+ ```
480
+ Sign on `vert` is negative so the eye dips on footfall — this is what reads as "weight".
481
+
482
+ **Footstep detection**: emit `onFootStep` when `stridePhase` crosses `0` (right foot) or `0.5` (left foot), but only if `state.grounded && speed > minStepSpeed`. The boundary check uses the *previous* phase so it fires exactly once even at huge dt.
483
+
484
+ **Breath cycle**:
485
+ ```
486
+ breathRateHz = lerp(rateRest, rateMax, exertion) decaying with rateDecayHalfLife
487
+ breathAmpM = lerp(ampRest, ampMax, exertion) decaying with ampDecayHalfLife
488
+ breathPhase += breathRateHz * dt; wrap to [0,1)
489
+
490
+ verticalBreath = -breathAmpM * sin(2π * breathPhase) * (1 + noiseAmount * perlin(t*0.7))
491
+ pitchBreath = breathPitchAmp * cos(2π * breathPhase) // gentle nod, 90° out of phase
492
+ ```
493
+ Negative `vertical` so peak exhale dips the eye slightly (chest collapses). `onBreathIn` fires at phase `0.25`, `onBreathOut` at `0.75`.
494
+
495
+ **Exertion update**:
496
+ ```
497
+ rise = (sprintActive ? sprintRiseRate : 0) + jumpRiseDelta(this frame)
498
+ fall = idleDecayRate * (1 - rise > 0 ? 0 : 1)
499
+ exertion = clamp(exertion + (rise - fall) * dt, 0, 1)
500
+ ```
501
+ Then `breathRateHz` and `breathAmpM` track `exertion` through *separate* exponential lags — rate hangs around longer than amplitude, matching how real recovery works (you breathe fast for a while after the chest has stopped heaving).
502
+
503
+ **Critically damped spring** (used for FOV, lean, crouch height):
504
+ ```
505
+ y = halfLife / 0.6931
506
+ α = exp(-y * dt)
507
+ β = (x - target)
508
+ v = (v - β * y) * α
509
+ x = target + (β + v * dt / y) * α
510
+ ```
511
+ For landing recovery, use the same form with `zeta = 0.55` so it overshoots once before settling.
512
+
513
+ ### 5.4 Edge cases and rules
514
+
515
+ - **Look-pitch clamp** is *non-wrapping*: the pitch is stored as a separate scalar so it cannot accidentally roll over. Yaw on the body wraps freely.
516
+ - **Crouch under a ceiling**: if `intent.crouch` releases but the entity cannot stand up (raycast collides), the controller *holds* crouch and exposes `state.crouchBlocked = true`. Standing happens automatically when clear. This rule lives in the controller because it's universal; the raycast itself is a service-injection point.
517
+ - **Sprint requires forward intent**: sideways-only or backwards movement should not consume exertion at sprint rates. Specifically `sprint = intent.sprint && intent.move.y > 0.5 && state.grounded`.
518
+ - **No bob in air**: stride does not advance while `!grounded`. Phase freezes so landing doesn't re-fire a stale footstep.
519
+ - **Jump cancel during anticipation**: if grounded becomes false during anticipation (e.g. ground pulled out), drop the queued impulse and emit `onLeaveGround{reason:"fall"}` without `onJumpStart`.
520
+ - **Disable when paused**: the system should respect a `controller.enabled` flag (or scene pause) and freeze `update` but still allow `fixedUpdate` to integrate gravity if desired. This is a configuration concern, not behavior — exposed via the existing engine pause mechanism.
521
+
522
+ ---
523
+
524
+ ## 6. Accessibility & motion sickness
525
+
526
+ Three settings every player should be able to toggle (defaults shown):
527
+
528
+ | Setting | Default | Effect when off |
529
+ |---|---|---|
530
+ | `config.lean.enabled` | true | No roll on strafe |
531
+ | `config.bob.verticalAmpAtWalk` | (nonzero) | Set to 0 to disable bob |
532
+ | `config.fov.sprintAdd` | 6 | Set to 0 to disable FOV kick (a known nausea trigger) |
533
+
534
+ The Playtank article calls these out specifically — keeping the horizon flat and the FOV stable accommodates a meaningful fraction of players who otherwise can't play first-person at all. They are exposed as plain config knobs, not buried.
535
+
536
+ ---
537
+
538
+ ## 7. Integration points (deferred work)
539
+
540
+ These don't ship with the controller but the design preserves a clean attachment surface for each:
541
+
542
+ | Future system | How it hooks in |
543
+ |---|---|
544
+ | **Capsule physics / collision** | Reads `Motion.velocity` and resolves; writes `state.grounded`, `state.groundNormal`, `state.surfaceTag`. Suggested via a small `GroundContact` component the physics system populates and the controller reads. |
545
+ | **First-person arms rig** | Reads `pose.headPitchRad`, `pose.locomotionPhase`, `state.actionState`. Driven by an animation graph keyed to the same stride phase the bob uses, so feet animate in sync with footstep events. |
546
+ | **Third-person body rig** | Reads `pose.rootYawRad`, `pose.locomotionSpeed/Strafe`, `pose.crouchAmount`, `state.actionState`. Built around a standard blend-space. |
547
+ | **Sound** | Subscribes to `signals.onFootStep` (modulated by `surfaceTag`), `onLand` (kind = soft/hard), `onJumpStart`, `onBreathIn/Out` (volume = amplitude). |
548
+ | **Camera shake from external sources** (gunfire, explosions) | Pushes into the `externalShakeStack` on the controller — an additive transform list. The controller's L3 evaluates it; sources only push. |
549
+ | **Stamina / damage / status** | A separate component can write into `controller.config.exertion.*` at runtime — e.g. an injury raises `idleDecayRate` to a negative value (exertion can never recover fully). |
550
+ | **Networking** | Because L1+L2+L4 are deterministic in `fixedUpdate` and the intent surface is small, the controller can be rolled back / replayed by writing intent and re-running fixed updates. No design changes needed. |
551
+
552
+ ---
553
+
554
+ ## 8. File layout
555
+
556
+ ```
557
+ engine/control/first-person/
558
+ DESIGN.md (this file)
559
+ FirstPersonPlayerController.js (component class)
560
+ FirstPersonPlayerControllerSystem.js (system class)
561
+ FirstPersonPlayerControllerSerializationAdapter.js
562
+ FirstPersonPlayerController.spec.js (component contract)
563
+ FirstPersonPlayerControllerSystem.spec.js (system behavior)
564
+ math/
565
+ criticallyDampedSpring.js
566
+ underDampedSpring.js
567
+ computeJumpFromApex.js
568
+ pose/
569
+ FirstPersonPose.js (the pose-channel struct)
570
+ ```
571
+
572
+ Math helpers live as standalone pure functions so they're independently testable and reusable.
573
+
574
+ ---
575
+
576
+ ## 9. Resolved design decisions
577
+
578
+ The open questions from the previous draft are now answered:
579
+
580
+ 1. **Body pitch** — **No pitch on the body, ever.** Body holds only yaw. Pitch lives on the eye. Standard FPS convention; keeps physics integration clean.
581
+ 2. **Eye representation** — **Separate entity** with its own `Transform` + `Camera`, parented to the body via `TransformAttachment`. Holds the door open for vehicle-mount / turret-style reparenting later without refactor.
582
+ 3. **Look intent shape** — **`Vector2` delta** (yaw on `x`, pitch on `y`). Matches mouse semantics; yaw-only sources (turret-style AI) just write `(x, 0)`.
583
+ 4. **Bob curve evaluation** — **Runtime sine.** Cheap on modern CPUs. No LUT.
584
+ 5. **Look processing rate** — **Inside `fixedUpdate`.** Determinism wins. Input bindings accumulate look delta into `intent.look` (a `Vector2`) between fixed updates; the system consumes and zeroes it once per fixed step. Cost: at very low fixed rates (e.g. 30 Hz) mouse feel becomes slightly chunky; mitigation if it ever bites is to raise `fixedUpdateStepSize` from default ~60 Hz to 120 Hz for entities with this controller, *not* to move look back to render rate.
585
+
586
+ ### Jump-button repeat (added requirement)
587
+
588
+ **Holding the jump button must not produce repeated jumps.** A jump must consume an explicit *button-down edge*; holding the button after a jump produces no further jumps until the button is released and pressed again.
589
+
590
+ Implementation:
591
+
592
+ - `intent.jump` is a **hold-state boolean** (true while the button is held). The input binding sets it on key-down and clears it on key-up.
593
+ - The system tracks `_jumpButtonWasDown` internally. A jump is *requested* only on the false→true edge, not while the value remains true.
594
+ - Pressing space then holding it across grounded → airborne → grounded transitions produces exactly **one** jump. The player must release and re-press to jump again.
595
+ - Auto-repeat from the OS is already filtered by `KeyboardDevice` (`event.repeat` check), so the edge fires cleanly once per physical press.
596
+ - Bonus: the same edge gives us **variable jump height** — the true→false edge (release) cuts gravity on the ascending portion of the jump. Two birds, one signal.
597
+
598
+ The same hold-state-plus-edge pattern is used for crouch (when `config.crouch.mode === "toggle"`); when in `"hold"` mode the held value is used directly.
599
+
600
+ ---
601
+
602
+ ## 10. Sources
603
+
604
+ - gamedevpills — [The 3Cs Framework: Character, Camera, Controls](https://www.gamedevpills.com/p/the-3cs-framework-character-camera)
605
+ - Playtank — [First-Person 3Cs: Camera](https://playtank.io/2023/05/12/first-person-3cs-camera/)
606
+ - Roberts Space Industries — [Design Notes: FPS Stances & Breathing](https://robertsspaceindustries.com/en/comm-link/engineering/14653-Design-Notes-FPS-Stances-Breathing)
607
+ - NeoFPS — [Additive Transforms and Effects](https://docs.neofps.com/manual/fpcam-additive-transforms-and-effects.html)
608
+ - Daniel Holden — [Spring-It-On: The Game Developer's Spring-Roll-Call](https://theorangeduck.com/page/spring-roll-call)
609
+ - Allen Chou — [Game Math: Precise Control over Numeric Springing](https://allenchou.net/2015/04/game-math-precise-control-over-numeric-springing/)
610
+ - Ryan Juckett — [Damped Springs](https://www.ryanjuckett.com/damped-springs/)
611
+ - GDC Vault — [Animation Bootcamp: Giving Purpose to First-Person Animation](https://www.gdcvault.com/play/1017633/Animation-Bootcamp-Giving-Purpose-to)
612
+ - GDC Vault — [The Art of First Person Animation for Destiny](https://www.gdcvault.com/play/1022297/The-Art-of-First-Person)
613
+ - Palos Publishing — [Simulating Breathing and Idle Motion Procedurally](https://palospublishing.com/simulating-breathing-and-idle-motion-procedurally/)
614
+ - Ketra Games — [Coyote Time and Jump Buffering](https://www.ketra-games.com/2021/08/coyote-time-and-jump-buffering.html)
615
+ - Opsive — [First Person Lean](https://opsive.com/support/documentation/ultimate-character-controller/character/abilities/included-abilities/first-person-lean/) and [Character Foot Effects](https://opsive.com/support/documentation/ultimate-character-controller/surface-system/character-foot-effects/)
616
+ - CTRL500 — [Mirror's Edge Catalyst: Keeping the camera in your face](https://ctrl500.com/developers-corner/mirrors-edge-catalyst-keeping-the-camera-in-your-face/) (DICE / Erik Söderholm)