@jgengine/core 0.5.0 → 0.7.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 (244) hide show
  1. package/CHANGELOG.md +191 -2
  2. package/dist/ai/crowd.d.ts +36 -0
  3. package/dist/ai/crowd.js +213 -0
  4. package/dist/ai/jobBoard.d.ts +49 -0
  5. package/dist/ai/jobBoard.js +155 -0
  6. package/dist/ai/spawnDirector.d.ts +56 -0
  7. package/dist/ai/spawnDirector.js +150 -0
  8. package/dist/ai/threat.d.ts +25 -0
  9. package/dist/ai/threat.js +67 -0
  10. package/dist/audio/audioFalloff.d.ts +38 -0
  11. package/dist/audio/audioFalloff.js +32 -0
  12. package/dist/board/laneBoard.d.ts +63 -0
  13. package/dist/board/laneBoard.js +114 -0
  14. package/dist/board/timelineBoard.d.ts +36 -0
  15. package/dist/board/timelineBoard.js +70 -0
  16. package/dist/cards/cardPile.d.ts +63 -0
  17. package/dist/cards/cardPile.js +166 -0
  18. package/dist/cards/modifierPipeline.d.ts +33 -0
  19. package/dist/cards/modifierPipeline.js +48 -0
  20. package/dist/combat/abilityKit.d.ts +42 -0
  21. package/dist/combat/abilityKit.js +129 -0
  22. package/dist/combat/animationState.d.ts +47 -0
  23. package/dist/combat/animationState.js +101 -0
  24. package/dist/combat/attackTags.d.ts +14 -0
  25. package/dist/combat/attackTags.js +26 -0
  26. package/dist/combat/breakMeters.d.ts +32 -0
  27. package/dist/combat/breakMeters.js +51 -0
  28. package/dist/combat/comboString.d.ts +35 -0
  29. package/dist/combat/comboString.js +59 -0
  30. package/dist/combat/death.d.ts +8 -0
  31. package/dist/combat/death.js +5 -2
  32. package/dist/combat/defensiveWindow.d.ts +34 -0
  33. package/dist/combat/defensiveWindow.js +66 -0
  34. package/dist/combat/downed.d.ts +35 -0
  35. package/dist/combat/downed.js +104 -0
  36. package/dist/combat/effects.d.ts +11 -0
  37. package/dist/combat/effects.js +22 -13
  38. package/dist/combat/hitReaction.d.ts +23 -0
  39. package/dist/combat/hitReaction.js +20 -0
  40. package/dist/combat/resistance.d.ts +13 -0
  41. package/dist/combat/resistance.js +46 -0
  42. package/dist/combat/telegraph.d.ts +27 -0
  43. package/dist/combat/telegraph.js +37 -0
  44. package/dist/crafting/crop.d.ts +59 -0
  45. package/dist/crafting/crop.js +181 -0
  46. package/dist/crafting/production.d.ts +64 -0
  47. package/dist/crafting/production.js +130 -0
  48. package/dist/crafting/recipe.d.ts +62 -0
  49. package/dist/crafting/recipe.js +101 -0
  50. package/dist/economy/sharedWallet.d.ts +28 -0
  51. package/dist/economy/sharedWallet.js +62 -0
  52. package/dist/economy/techTree.d.ts +40 -0
  53. package/dist/economy/techTree.js +66 -0
  54. package/dist/game/cosmetics.d.ts +25 -0
  55. package/dist/game/cosmetics.js +51 -0
  56. package/dist/game/defineGame.d.ts +5 -0
  57. package/dist/game/events.d.ts +62 -0
  58. package/dist/game/lootFilter.d.ts +33 -0
  59. package/dist/game/lootFilter.js +48 -0
  60. package/dist/game/ping.d.ts +67 -0
  61. package/dist/game/ping.js +86 -0
  62. package/dist/game/playableGame.d.ts +291 -5
  63. package/dist/game/race.d.ts +95 -0
  64. package/dist/game/race.js +216 -0
  65. package/dist/game/runDraft.d.ts +34 -0
  66. package/dist/game/runDraft.js +97 -0
  67. package/dist/game/social.d.ts +27 -0
  68. package/dist/game/social.js +16 -0
  69. package/dist/game/worldItem.d.ts +92 -0
  70. package/dist/game/worldItem.js +132 -0
  71. package/dist/input/axisInput.d.ts +43 -0
  72. package/dist/input/axisInput.js +70 -0
  73. package/dist/input/pointer.d.ts +24 -0
  74. package/dist/input/pointer.js +17 -0
  75. package/dist/interaction/contextMenu.d.ts +30 -0
  76. package/dist/interaction/contextMenu.js +22 -0
  77. package/dist/interaction/qte.d.ts +20 -0
  78. package/dist/interaction/qte.js +24 -0
  79. package/dist/interaction/skillCheck.d.ts +20 -0
  80. package/dist/interaction/skillCheck.js +26 -0
  81. package/dist/inventory/shapedGrid.d.ts +56 -0
  82. package/dist/inventory/shapedGrid.js +176 -0
  83. package/dist/inventory/storageTier.d.ts +52 -0
  84. package/dist/inventory/storageTier.js +77 -0
  85. package/dist/item/affix.d.ts +54 -0
  86. package/dist/item/affix.js +133 -0
  87. package/dist/item/durability.d.ts +51 -0
  88. package/dist/item/durability.js +78 -0
  89. package/dist/item/modularItem.d.ts +46 -0
  90. package/dist/item/modularItem.js +75 -0
  91. package/dist/meta/changelog.d.ts +1 -1
  92. package/dist/meta/changelog.js +53 -1
  93. package/dist/movement/dash.d.ts +37 -0
  94. package/dist/movement/dash.js +65 -0
  95. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  96. package/dist/multiplayer/combatSnapshot.js +89 -0
  97. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  98. package/dist/multiplayer/lagCompensation.js +154 -0
  99. package/dist/multiplayer/matchmaking.d.ts +33 -0
  100. package/dist/multiplayer/matchmaking.js +60 -0
  101. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  102. package/dist/multiplayer/simultaneousCommit.js +53 -0
  103. package/dist/nav/navGrid.d.ts +46 -0
  104. package/dist/nav/navGrid.js +248 -0
  105. package/dist/nav/pathFollow.d.ts +29 -0
  106. package/dist/nav/pathFollow.js +67 -0
  107. package/dist/physics/buoyancy.d.ts +49 -0
  108. package/dist/physics/buoyancy.js +112 -0
  109. package/dist/physics/carryable.d.ts +46 -0
  110. package/dist/physics/carryable.js +110 -0
  111. package/dist/physics/damageZones.d.ts +56 -0
  112. package/dist/physics/damageZones.js +107 -0
  113. package/dist/physics/forceVolume.d.ts +48 -0
  114. package/dist/physics/forceVolume.js +119 -0
  115. package/dist/physics/physicsWorld.d.ts +219 -0
  116. package/dist/physics/physicsWorld.js +749 -0
  117. package/dist/physics/ragdoll.d.ts +49 -0
  118. package/dist/physics/ragdoll.js +103 -0
  119. package/dist/physics/spatialGrid.d.ts +41 -0
  120. package/dist/physics/spatialGrid.js +138 -0
  121. package/dist/physics/structure.d.ts +83 -0
  122. package/dist/physics/structure.js +0 -0
  123. package/dist/physics/traversal.d.ts +75 -0
  124. package/dist/physics/traversal.js +138 -0
  125. package/dist/physics/vehicleBody.d.ts +93 -0
  126. package/dist/physics/vehicleBody.js +234 -0
  127. package/dist/runtime/gameContext.d.ts +66 -2
  128. package/dist/runtime/gameContext.js +157 -4
  129. package/dist/runtime/hostPersistence.d.ts +18 -0
  130. package/dist/runtime/hostPersistence.js +4 -0
  131. package/dist/runtime/persistenceScope.d.ts +32 -0
  132. package/dist/runtime/persistenceScope.js +64 -0
  133. package/dist/scene/assetCatalog.d.ts +13 -0
  134. package/dist/scene/autoTarget.d.ts +15 -0
  135. package/dist/scene/autoTarget.js +54 -0
  136. package/dist/scene/behaviors.d.ts +13 -1
  137. package/dist/scene/behaviors.js +3 -0
  138. package/dist/scene/captureCheck.d.ts +7 -0
  139. package/dist/scene/captureCheck.js +11 -0
  140. package/dist/scene/entityStore.d.ts +7 -1
  141. package/dist/scene/entityStore.js +15 -1
  142. package/dist/scene/form.d.ts +40 -0
  143. package/dist/scene/form.js +56 -0
  144. package/dist/scene/mount.d.ts +62 -0
  145. package/dist/scene/mount.js +107 -0
  146. package/dist/scene/possession.d.ts +30 -0
  147. package/dist/scene/possession.js +51 -0
  148. package/dist/scene/roster.d.ts +25 -0
  149. package/dist/scene/roster.js +57 -0
  150. package/dist/scene/selection.d.ts +30 -0
  151. package/dist/scene/selection.js +38 -0
  152. package/dist/scene/stationClaim.d.ts +43 -0
  153. package/dist/scene/stationClaim.js +109 -0
  154. package/dist/sensor/frustumSensor.d.ts +60 -0
  155. package/dist/sensor/frustumSensor.js +104 -0
  156. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  157. package/dist/sensor/hiddenStateProbe.js +40 -0
  158. package/dist/sensor/recordingBuffer.d.ts +29 -0
  159. package/dist/sensor/recordingBuffer.js +61 -0
  160. package/dist/sensor/revealQuery.d.ts +22 -0
  161. package/dist/sensor/revealQuery.js +36 -0
  162. package/dist/session/contestedChannel.d.ts +36 -0
  163. package/dist/session/contestedChannel.js +100 -0
  164. package/dist/session/extraction.d.ts +54 -0
  165. package/dist/session/extraction.js +108 -0
  166. package/dist/session/ring.d.ts +36 -0
  167. package/dist/session/ring.js +88 -0
  168. package/dist/session/roundState.d.ts +55 -0
  169. package/dist/session/roundState.js +129 -0
  170. package/dist/stats/accumulatorMeter.d.ts +31 -0
  171. package/dist/stats/accumulatorMeter.js +81 -0
  172. package/dist/stats/eventMeter.d.ts +34 -0
  173. package/dist/stats/eventMeter.js +76 -0
  174. package/dist/stats/rollCheck.d.ts +15 -0
  175. package/dist/stats/rollCheck.js +15 -0
  176. package/dist/survival/decayMeter.d.ts +59 -0
  177. package/dist/survival/decayMeter.js +95 -0
  178. package/dist/survival/moodle.d.ts +48 -0
  179. package/dist/survival/moodle.js +91 -0
  180. package/dist/survival/regionHealth.d.ts +72 -0
  181. package/dist/survival/regionHealth.js +129 -0
  182. package/dist/tactics/predictiveQuery.d.ts +34 -0
  183. package/dist/tactics/predictiveQuery.js +44 -0
  184. package/dist/tactics/snapshot.d.ts +18 -0
  185. package/dist/tactics/snapshot.js +65 -0
  186. package/dist/tactics/surface.d.ts +52 -0
  187. package/dist/tactics/surface.js +135 -0
  188. package/dist/tactics/tacticalGrid.d.ts +57 -0
  189. package/dist/tactics/tacticalGrid.js +244 -0
  190. package/dist/time/beatClock.d.ts +40 -0
  191. package/dist/time/beatClock.js +73 -0
  192. package/dist/time/simClock.d.ts +62 -0
  193. package/dist/time/simClock.js +132 -0
  194. package/dist/turn/commit.d.ts +27 -0
  195. package/dist/turn/commit.js +72 -0
  196. package/dist/turn/turnLoop.d.ts +58 -0
  197. package/dist/turn/turnLoop.js +216 -0
  198. package/dist/world/buildPermissions.d.ts +47 -0
  199. package/dist/world/buildPermissions.js +134 -0
  200. package/dist/world/buildingIndex.d.ts +16 -0
  201. package/dist/world/buildingIndex.js +63 -0
  202. package/dist/world/buildings.d.ts +98 -0
  203. package/dist/world/buildings.js +320 -0
  204. package/dist/world/carve.d.ts +105 -0
  205. package/dist/world/carve.js +175 -0
  206. package/dist/world/connectors.d.ts +40 -0
  207. package/dist/world/connectors.js +67 -0
  208. package/dist/world/envField.d.ts +71 -0
  209. package/dist/world/envField.js +96 -0
  210. package/dist/world/features.d.ts +105 -1
  211. package/dist/world/features.js +95 -0
  212. package/dist/world/fog.d.ts +40 -0
  213. package/dist/world/fog.js +137 -0
  214. package/dist/world/markers.d.ts +52 -0
  215. package/dist/world/markers.js +96 -0
  216. package/dist/world/minimap.d.ts +42 -0
  217. package/dist/world/minimap.js +72 -0
  218. package/dist/world/placedStructureStore.d.ts +39 -0
  219. package/dist/world/placedStructureStore.js +95 -0
  220. package/dist/world/placementController.d.ts +48 -0
  221. package/dist/world/placementController.js +97 -0
  222. package/dist/world/realm.d.ts +61 -0
  223. package/dist/world/realm.js +66 -0
  224. package/dist/world/regions.d.ts +66 -0
  225. package/dist/world/regions.js +158 -0
  226. package/dist/world/scatter.d.ts +24 -0
  227. package/dist/world/scatter.js +126 -0
  228. package/dist/world/scatterItems.d.ts +38 -0
  229. package/dist/world/scatterItems.js +85 -0
  230. package/dist/world/support.d.ts +27 -0
  231. package/dist/world/support.js +75 -0
  232. package/dist/world/terraform.d.ts +54 -0
  233. package/dist/world/terraform.js +186 -0
  234. package/dist/world/terrain.d.ts +54 -0
  235. package/dist/world/terrain.js +181 -0
  236. package/dist/world/walls.d.ts +61 -0
  237. package/dist/world/walls.js +221 -0
  238. package/dist/world/water.d.ts +27 -0
  239. package/dist/world/water.js +125 -0
  240. package/dist/world/weather.d.ts +92 -0
  241. package/dist/world/weather.js +169 -0
  242. package/dist/world/wind.d.ts +17 -0
  243. package/dist/world/wind.js +40 -0
  244. package/package.json +1 -1
@@ -0,0 +1,93 @@
1
+ import type { PhysicsWorld } from "./physicsWorld.js";
2
+ import type { AxisInput } from "../input/axisInput.js";
3
+ export interface GripCurve {
4
+ points: readonly (readonly [number, number])[];
5
+ }
6
+ /**
7
+ * Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
8
+ * the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
9
+ * from a drift. Points are read in ascending slip order; ends clamp.
10
+ */
11
+ export declare function sampleGripCurve(curve: GripCurve, slip: number): number;
12
+ export declare const DEFAULT_GRIP_CURVE: GripCurve;
13
+ export interface WheelSpec {
14
+ offset: readonly [number, number, number];
15
+ radius?: number;
16
+ restLength?: number;
17
+ steered?: boolean;
18
+ powered?: boolean;
19
+ handbrakeLocks?: boolean;
20
+ }
21
+ export interface WheelState {
22
+ worldX: number;
23
+ worldY: number;
24
+ worldZ: number;
25
+ grounded: boolean;
26
+ compression: number;
27
+ steerAngle: number;
28
+ }
29
+ export interface VehicleBodyConfig {
30
+ position: readonly [number, number, number];
31
+ heading?: number;
32
+ chassisHalfExtents?: readonly [number, number, number];
33
+ mass?: number;
34
+ wheels?: readonly WheelSpec[];
35
+ engineAccel?: number;
36
+ brakeAccel?: number;
37
+ topSpeed?: number;
38
+ reverseSpeed?: number;
39
+ turnRate?: number;
40
+ turnSpeedRef?: number;
41
+ grip?: GripCurve;
42
+ gripStrength?: number;
43
+ handbrakeGrip?: number;
44
+ rollingResistance?: number;
45
+ suspensionRest?: number;
46
+ suspensionStiffness?: number;
47
+ suspensionDamping?: number;
48
+ groundHeight?: (x: number, z: number) => number;
49
+ surfaceFriction?: (x: number, z: number) => number;
50
+ }
51
+ /**
52
+ * Arcade vehicle over the {@link PhysicsWorld} rigid-body sim. The chassis is a single box body driven
53
+ * by an {@link AxisInput}: per-wheel suspension is a spring-damper held with G3's `springJoint` against
54
+ * the sampled ground, drive/brake push along the heading, and a tire-grip curve bleeds lateral velocity
55
+ * (cornering, and drift under handbrake). Because the chassis is a physics body it still collides — that
56
+ * contact feeds crash damage. Call `update(dt, input)` before the shared `world.step(dt)`.
57
+ */
58
+ export declare class VehicleBody {
59
+ heading: number;
60
+ readonly chassis: number;
61
+ private readonly world;
62
+ private readonly wheels;
63
+ private readonly springs;
64
+ private readonly wheelStates;
65
+ private readonly engineAccel;
66
+ private readonly brakeAccel;
67
+ private readonly topSpeed;
68
+ private readonly reverseSpeed;
69
+ private readonly turnRate;
70
+ private readonly turnSpeedRef;
71
+ private readonly grip;
72
+ private readonly gripStrength;
73
+ private readonly handbrakeGrip;
74
+ private readonly rollingResistance;
75
+ private readonly suspensionRest;
76
+ private readonly groundHeight;
77
+ private readonly surfaceFriction;
78
+ private groundedCount;
79
+ constructor(world: PhysicsWorld, config: VehicleBodyConfig);
80
+ private wheelMount;
81
+ get position(): [number, number, number];
82
+ get forward(): [number, number];
83
+ /** Signed forward speed along the heading (negative = reversing). */
84
+ get speed(): number;
85
+ get grounded(): boolean;
86
+ wheelState(index: number): WheelState | null;
87
+ get wheelCount(): number;
88
+ resetTo(position: readonly [number, number, number], heading: number): void;
89
+ update(dt: number, input: AxisInput): void;
90
+ private updateSuspension;
91
+ remove(): void;
92
+ }
93
+ export declare function createVehicleBody(world: PhysicsWorld, config: VehicleBodyConfig): VehicleBody;
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
3
+ * the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
4
+ * from a drift. Points are read in ascending slip order; ends clamp.
5
+ */
6
+ export function sampleGripCurve(curve, slip) {
7
+ const pts = curve.points;
8
+ if (pts.length === 0)
9
+ return 1;
10
+ const s = Math.abs(slip);
11
+ if (s <= pts[0][0])
12
+ return pts[0][1];
13
+ for (let i = 1; i < pts.length; i += 1) {
14
+ const [x1, y1] = pts[i];
15
+ if (s <= x1) {
16
+ const [x0, y0] = pts[i - 1];
17
+ const span = x1 - x0;
18
+ const t = span <= 0 ? 0 : (s - x0) / span;
19
+ return y0 + (y1 - y0) * t;
20
+ }
21
+ }
22
+ return pts[pts.length - 1][1];
23
+ }
24
+ export const DEFAULT_GRIP_CURVE = {
25
+ points: [
26
+ [0, 1],
27
+ [0.2, 1],
28
+ [0.45, 0.78],
29
+ [1, 0.55],
30
+ ],
31
+ };
32
+ function cornerWheels(half, rest, radius) {
33
+ const x = half[0];
34
+ const z = half[2];
35
+ const y = -half[1];
36
+ return [
37
+ { offset: [-x, y, z], steered: true, restLength: rest, radius },
38
+ { offset: [x, y, z], steered: true, restLength: rest, radius },
39
+ { offset: [-x, y, -z], powered: true, handbrakeLocks: true, restLength: rest, radius },
40
+ { offset: [x, y, -z], powered: true, handbrakeLocks: true, restLength: rest, radius },
41
+ ];
42
+ }
43
+ /**
44
+ * Arcade vehicle over the {@link PhysicsWorld} rigid-body sim. The chassis is a single box body driven
45
+ * by an {@link AxisInput}: per-wheel suspension is a spring-damper held with G3's `springJoint` against
46
+ * the sampled ground, drive/brake push along the heading, and a tire-grip curve bleeds lateral velocity
47
+ * (cornering, and drift under handbrake). Because the chassis is a physics body it still collides — that
48
+ * contact feeds crash damage. Call `update(dt, input)` before the shared `world.step(dt)`.
49
+ */
50
+ export class VehicleBody {
51
+ heading;
52
+ chassis;
53
+ world;
54
+ wheels;
55
+ springs;
56
+ wheelStates;
57
+ engineAccel;
58
+ brakeAccel;
59
+ topSpeed;
60
+ reverseSpeed;
61
+ turnRate;
62
+ turnSpeedRef;
63
+ grip;
64
+ gripStrength;
65
+ handbrakeGrip;
66
+ rollingResistance;
67
+ suspensionRest;
68
+ groundHeight;
69
+ surfaceFriction;
70
+ groundedCount = 0;
71
+ constructor(world, config) {
72
+ this.world = world;
73
+ this.heading = config.heading ?? 0;
74
+ const half = config.chassisHalfExtents ?? [0.9, 0.35, 1.8];
75
+ this.suspensionRest = config.suspensionRest ?? 0.55;
76
+ const radius = 0.35;
77
+ this.wheels = config.wheels ?? cornerWheels(half, this.suspensionRest, radius);
78
+ this.engineAccel = config.engineAccel ?? 18;
79
+ this.brakeAccel = config.brakeAccel ?? 26;
80
+ this.topSpeed = config.topSpeed ?? 28;
81
+ this.reverseSpeed = config.reverseSpeed ?? 10;
82
+ this.turnRate = config.turnRate ?? 1.6;
83
+ this.turnSpeedRef = config.turnSpeedRef ?? 8;
84
+ this.grip = config.grip ?? DEFAULT_GRIP_CURVE;
85
+ this.gripStrength = config.gripStrength ?? 8;
86
+ this.handbrakeGrip = config.handbrakeGrip ?? 0.35;
87
+ this.rollingResistance = config.rollingResistance ?? 0.6;
88
+ const stiffness = config.suspensionStiffness ?? 60;
89
+ const damping = config.suspensionDamping ?? 8;
90
+ this.groundHeight = config.groundHeight ?? (() => 0);
91
+ this.surfaceFriction = config.surfaceFriction ?? (() => 1);
92
+ this.chassis = world.addBody({
93
+ position: config.position,
94
+ halfExtents: half,
95
+ mass: config.mass ?? 4,
96
+ });
97
+ this.springs = [];
98
+ this.wheelStates = [];
99
+ for (const wheel of this.wheels) {
100
+ const worldPos = this.wheelMount(wheel);
101
+ const rest = wheel.restLength ?? this.suspensionRest;
102
+ const id = world.springJoint({
103
+ bodyA: this.chassis,
104
+ anchorA: [wheel.offset[0], wheel.offset[1], wheel.offset[2]],
105
+ anchorB: [worldPos[0], worldPos[1] - rest, worldPos[2]],
106
+ restLength: rest,
107
+ stiffness,
108
+ damping,
109
+ });
110
+ this.springs.push(id);
111
+ this.wheelStates.push({
112
+ worldX: worldPos[0],
113
+ worldY: worldPos[1],
114
+ worldZ: worldPos[2],
115
+ grounded: false,
116
+ compression: 0,
117
+ steerAngle: 0,
118
+ });
119
+ }
120
+ }
121
+ wheelMount(wheel) {
122
+ const sin = Math.sin(this.heading);
123
+ const cos = Math.cos(this.heading);
124
+ const [ox, oy, oz] = wheel.offset;
125
+ return [
126
+ this.world.posX[this.chassis] + ox * cos + oz * sin,
127
+ this.world.posY[this.chassis] + oy,
128
+ this.world.posZ[this.chassis] - ox * sin + oz * cos,
129
+ ];
130
+ }
131
+ get position() {
132
+ return [this.world.posX[this.chassis], this.world.posY[this.chassis], this.world.posZ[this.chassis]];
133
+ }
134
+ get forward() {
135
+ return [Math.sin(this.heading), Math.cos(this.heading)];
136
+ }
137
+ /** Signed forward speed along the heading (negative = reversing). */
138
+ get speed() {
139
+ const [fx, fz] = this.forward;
140
+ return this.world.velX[this.chassis] * fx + this.world.velZ[this.chassis] * fz;
141
+ }
142
+ get grounded() {
143
+ return this.groundedCount > 0;
144
+ }
145
+ wheelState(index) {
146
+ return this.wheelStates[index] ?? null;
147
+ }
148
+ get wheelCount() {
149
+ return this.wheels.length;
150
+ }
151
+ resetTo(position, heading) {
152
+ const w = this.world;
153
+ const c = this.chassis;
154
+ w.posX[c] = position[0];
155
+ w.posY[c] = position[1];
156
+ w.posZ[c] = position[2];
157
+ w.velX[c] = 0;
158
+ w.velY[c] = 0;
159
+ w.velZ[c] = 0;
160
+ this.heading = heading;
161
+ w.wake(c);
162
+ }
163
+ update(dt, input) {
164
+ const w = this.world;
165
+ const c = this.chassis;
166
+ this.updateSuspension(input.steer);
167
+ const speed = this.speed;
168
+ const grounded = this.groundedCount > 0;
169
+ if (grounded) {
170
+ const steerScale = Math.min(1, Math.abs(speed) / this.turnSpeedRef);
171
+ const dir = speed >= 0 ? 1 : -1;
172
+ this.heading += input.steer * this.turnRate * steerScale * dir * dt;
173
+ }
174
+ const [fx, fz] = this.forward;
175
+ if (grounded) {
176
+ let accel = 0;
177
+ if (input.throttle > 0 && speed < this.topSpeed)
178
+ accel += input.throttle * this.engineAccel;
179
+ if (input.brake > 0) {
180
+ if (speed > 0.2)
181
+ accel -= input.brake * this.brakeAccel;
182
+ else if (speed > -this.reverseSpeed)
183
+ accel -= input.brake * this.engineAccel;
184
+ }
185
+ w.velX[c] += fx * accel * dt;
186
+ w.velZ[c] += fz * accel * dt;
187
+ const roll = Math.max(0, 1 - this.rollingResistance * dt * (input.throttle > 0 ? 0 : 1));
188
+ const forwardSpeed = w.velX[c] * fx + w.velZ[c] * fz;
189
+ const rolled = forwardSpeed * roll;
190
+ const lateralSpeed = -w.velX[c] * fz + w.velZ[c] * fx;
191
+ const surface = this.surfaceFriction(w.posX[c], w.posZ[c]);
192
+ const slip = Math.abs(lateralSpeed) / (Math.abs(forwardSpeed) + 1);
193
+ const handbrake = 1 - input.handbrake * (1 - this.handbrakeGrip);
194
+ const grip = sampleGripCurve(this.grip, slip) * surface * handbrake;
195
+ const keep = Math.max(0, 1 - grip * this.gripStrength * dt);
196
+ const newLateral = lateralSpeed * keep;
197
+ w.velX[c] = fx * rolled - fz * newLateral;
198
+ w.velZ[c] = fz * rolled + fx * newLateral;
199
+ w.wake(c);
200
+ }
201
+ }
202
+ updateSuspension(steer) {
203
+ this.groundedCount = 0;
204
+ const w = this.world;
205
+ for (let i = 0; i < this.wheels.length; i += 1) {
206
+ const wheel = this.wheels[i];
207
+ const state = this.wheelStates[i];
208
+ const mount = this.wheelMount(wheel);
209
+ const radius = wheel.radius ?? 0.35;
210
+ const rest = wheel.restLength ?? this.suspensionRest;
211
+ const groundY = this.groundHeight(mount[0], mount[2]);
212
+ const gap = mount[1] - (groundY + radius);
213
+ const maxTravel = rest + radius;
214
+ const grounded = gap <= maxTravel;
215
+ w.setJointAnchor(this.springs[i], mount[0], groundY + radius, mount[2]);
216
+ state.worldX = mount[0];
217
+ state.worldY = groundY + radius;
218
+ state.worldZ = mount[2];
219
+ state.grounded = grounded;
220
+ state.compression = Math.max(0, rest - gap);
221
+ state.steerAngle = wheel.steered === true ? steer : 0;
222
+ if (grounded)
223
+ this.groundedCount += 1;
224
+ }
225
+ }
226
+ remove() {
227
+ for (const id of this.springs)
228
+ this.world.removeJoint(id);
229
+ this.springs.length = 0;
230
+ }
231
+ }
232
+ export function createVehicleBody(world, config) {
233
+ return new VehicleBody(world, config);
234
+ }
@@ -1,7 +1,10 @@
1
1
  import { type OnDeathSpec } from "../combat/death.js";
2
- import { type EffectInput, type EffectResult, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
2
+ import { type EffectInput, type EffectResult, type EffectVia, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
3
3
  import { type ProjectileSystem } from "../combat/projectiles.js";
4
+ import { type HitReaction, type HitReactionConfig } from "../combat/hitReaction.js";
5
+ import { type TelegraphShape } from "../combat/telegraph.js";
4
6
  import { type CommandDefinition, type CommandResult } from "../commands/commandRegistry.js";
7
+ import { type Cosmetics } from "../game/cosmetics.js";
5
8
  import type { GameDefinition } from "../game/defineGame.js";
6
9
  import { type GameEventMap, type GameEvents } from "../game/events.js";
7
10
  import { type GameFeed } from "../game/feed.js";
@@ -9,25 +12,35 @@ import { type Leaderboard } from "../game/leaderboard.js";
9
12
  import { type Loadouts } from "../game/loadout.js";
10
13
  import { type Drop, type LootTableDef } from "../game/lootTable.js";
11
14
  import { type QuestJournal } from "../game/quest.js";
15
+ import { type WorldItemRecord, type WorldItemSpawnInput } from "../game/worldItem.js";
12
16
  import { type Social } from "../game/social.js";
13
17
  import { type TradeField, type TradeSystem } from "../game/trade.js";
14
18
  import { type Unlocks } from "../game/unlocks.js";
15
19
  import { type InventoryLayout, type InventorySet } from "../inventory/inventoryModel.js";
20
+ import type { ContextVerb } from "../interaction/contextMenu.js";
16
21
  import type { ProximityPrompt } from "../interaction/proximityPrompt.js";
17
22
  import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type ItemUseResult } from "../item/use.js";
18
23
  import { type WeaponStats } from "../item/weapon.js";
19
24
  import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
20
25
  import type { ModelAssetRef } from "../scene/assetCatalog.js";
21
26
  import { type EntityStatsApi, type StatCatalog } from "../scene/entityStats.js";
22
- import type { EntityPose, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
27
+ import type { EntityPose, EntityPosition, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
28
+ import { type Forms } from "../scene/form.js";
23
29
  import { type ObjectStore } from "../scene/objectStore.js";
30
+ import { type Roster } from "../scene/roster.js";
31
+ import { type Possession } from "../scene/possession.js";
24
32
  import { type SpatialApi } from "../scene/spatial.js";
25
33
  import { type CycleTargetOptions } from "../scene/targeting.js";
26
34
  import { type Stats } from "../stats/statModifiers.js";
35
+ import { type SimClock } from "../time/simClock.js";
27
36
  export interface GameContextItemEntry {
28
37
  use?: string;
29
38
  weapon?: Record<string, unknown>;
30
39
  trade?: TradeField;
40
+ /** Rarity id read by the `worldItem` loot-filter/render binding when this item drops to the ground (#32/#33). */
41
+ rarity?: string;
42
+ /** Base item type read by the loot-filter rule evaluator (#33); defaults to the item id when absent. */
43
+ baseType?: string;
31
44
  }
32
45
  export type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle";
33
46
  export interface GameContextEntityEntry {
@@ -38,6 +51,8 @@ export interface GameContextEntityEntry {
38
51
  walkSpeed?: number;
39
52
  };
40
53
  role?: CatalogEntityRole;
54
+ /** Right-click context-menu verbs for this entity (#31). */
55
+ verbs?: readonly ContextVerb[];
41
56
  }
42
57
  export interface GameContextObjectEntry {
43
58
  proximityPrompt?: ProximityPrompt;
@@ -45,6 +60,8 @@ export interface GameContextObjectEntry {
45
60
  baseBreakTime: number;
46
61
  };
47
62
  slotInventory?: InventoryLayout;
63
+ /** Right-click context-menu verbs for this object (#31). */
64
+ verbs?: readonly ContextVerb[];
48
65
  }
49
66
  export interface GameContextContent {
50
67
  itemById?(itemId: string): GameContextItemEntry | null | undefined;
@@ -69,6 +86,31 @@ export interface FloatTextInput {
69
86
  text?: string;
70
87
  kind?: string;
71
88
  amount?: number;
89
+ hitType?: string;
90
+ element?: string;
91
+ crit?: boolean;
92
+ scale?: number;
93
+ }
94
+ export interface TelegraphInput {
95
+ from: string;
96
+ shape: TelegraphShape;
97
+ at: [number, number, number];
98
+ dir?: number;
99
+ windupMs: number;
100
+ kind?: string;
101
+ effect?: {
102
+ effect: string;
103
+ via?: EffectVia;
104
+ radius?: number;
105
+ falloff?: "linear" | "none";
106
+ los?: boolean;
107
+ };
108
+ }
109
+ export interface HitReactionInput {
110
+ from: string;
111
+ to: string;
112
+ config: HitReactionConfig;
113
+ power?: number;
72
114
  }
73
115
  export interface SceneEntityContext {
74
116
  spawn(name: string, options?: SpawnOptions): string;
@@ -78,6 +120,8 @@ export interface SceneEntityContext {
78
120
  list(): readonly SceneEntity[];
79
121
  stats: EntityStatsApi;
80
122
  floatText(input: FloatTextInput): void;
123
+ telegraph(input: TelegraphInput): () => void;
124
+ hitReaction(input: HitReactionInput): HitReaction | null;
81
125
  setTarget(fromId: string, toId: string | null): void;
82
126
  getTarget(fromId: string): string | null;
83
127
  cycleTarget(fromId: string, options?: CycleTargetOptions): string | null;
@@ -92,6 +136,21 @@ export interface SceneEntityContext {
92
136
  hasLineOfSight: SpatialApi["hasLineOfSight"];
93
137
  queryArc: SpatialApi["queryArc"];
94
138
  moveToward: SpatialApi["moveToward"];
139
+ form: Forms;
140
+ }
141
+ export type WorldItemPickupResult = {
142
+ status: "ok";
143
+ record: WorldItemRecord;
144
+ } | {
145
+ status: "rejected";
146
+ reason: string;
147
+ };
148
+ export interface SceneWorldItemContext {
149
+ spawn(input: WorldItemSpawnInput): WorldItemRecord;
150
+ get(instanceId: string): WorldItemRecord | null;
151
+ list(): readonly WorldItemRecord[];
152
+ nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
153
+ pickup(instanceId: string, userId: string): WorldItemPickupResult;
95
154
  }
96
155
  export interface GameContextCommands {
97
156
  define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void;
@@ -125,6 +184,7 @@ export interface GameContext {
125
184
  scene: {
126
185
  object: SceneObjectContext;
127
186
  entity: SceneEntityContext;
187
+ worldItem: SceneWorldItemContext;
128
188
  };
129
189
  game: {
130
190
  commands: GameContextCommands;
@@ -137,6 +197,7 @@ export interface GameContext {
137
197
  unlocks: Unlocks;
138
198
  economy: GameContextEconomy;
139
199
  leaderboard: Leaderboard;
200
+ roster: Roster;
140
201
  };
141
202
  player: {
142
203
  userId: string;
@@ -148,11 +209,14 @@ export interface GameContext {
148
209
  reason: string;
149
210
  } | null;
150
211
  movement: PoseState;
212
+ possession: Possession;
213
+ cosmetics: Cosmetics;
151
214
  };
152
215
  item: {
153
216
  use: GameContextItemUse;
154
217
  weapon: WeaponStats;
155
218
  };
219
+ time: SimClock;
156
220
  subscribe(listener: () => void): () => void;
157
221
  version(): number;
158
222
  }