@zylem/behaviors 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/behavior-system-DYbx-x_-.d.ts +307 -0
- package/dist/chunk-2C3Z5GE7.js +176 -0
- package/dist/chunk-2C3Z5GE7.js.map +1 -0
- package/dist/chunk-2CLT36VB.js +241 -0
- package/dist/chunk-2CLT36VB.js.map +1 -0
- package/dist/chunk-3PYGRHAL.js +35 -0
- package/dist/chunk-3PYGRHAL.js.map +1 -0
- package/dist/chunk-4I6E6DGV.js +212 -0
- package/dist/chunk-4I6E6DGV.js.map +1 -0
- package/dist/chunk-4URF65BR.js +44 -0
- package/dist/chunk-4URF65BR.js.map +1 -0
- package/dist/chunk-6C6NXNEW.js +38 -0
- package/dist/chunk-6C6NXNEW.js.map +1 -0
- package/dist/chunk-ARJWUNRE.js +431 -0
- package/dist/chunk-ARJWUNRE.js.map +1 -0
- package/dist/chunk-BXP4K7RF.js +1205 -0
- package/dist/chunk-BXP4K7RF.js.map +1 -0
- package/dist/chunk-DYLH6K7B.js +44 -0
- package/dist/chunk-DYLH6K7B.js.map +1 -0
- package/dist/chunk-EIWQM6U3.js +27 -0
- package/dist/chunk-EIWQM6U3.js.map +1 -0
- package/dist/chunk-EZNCCO4L.js +14 -0
- package/dist/chunk-EZNCCO4L.js.map +1 -0
- package/dist/chunk-F3KRX2V4.js +457 -0
- package/dist/chunk-F3KRX2V4.js.map +1 -0
- package/dist/chunk-FPTWVCPN.js +53 -0
- package/dist/chunk-FPTWVCPN.js.map +1 -0
- package/dist/chunk-GCNRXAKH.js +37 -0
- package/dist/chunk-GCNRXAKH.js.map +1 -0
- package/dist/chunk-GYURWCIQ.js +1985 -0
- package/dist/chunk-GYURWCIQ.js.map +1 -0
- package/dist/chunk-HZMY3TT4.js +244 -0
- package/dist/chunk-HZMY3TT4.js.map +1 -0
- package/dist/chunk-ID5QJYF5.js +50 -0
- package/dist/chunk-ID5QJYF5.js.map +1 -0
- package/dist/chunk-JAN34G6S.js +552 -0
- package/dist/chunk-JAN34G6S.js.map +1 -0
- package/dist/chunk-K3UNLQ7W.js +118 -0
- package/dist/chunk-K3UNLQ7W.js.map +1 -0
- package/dist/chunk-KDY5N6J4.js +82 -0
- package/dist/chunk-KDY5N6J4.js.map +1 -0
- package/dist/chunk-KOZUNIIE.js +413 -0
- package/dist/chunk-KOZUNIIE.js.map +1 -0
- package/dist/chunk-KS72RZHX.js +30 -0
- package/dist/chunk-KS72RZHX.js.map +1 -0
- package/dist/chunk-LD5SMIB3.js +32 -0
- package/dist/chunk-LD5SMIB3.js.map +1 -0
- package/dist/chunk-OHCPV2NT.js +86 -0
- package/dist/chunk-OHCPV2NT.js.map +1 -0
- package/dist/chunk-OYWFPIHF.js +115 -0
- package/dist/chunk-OYWFPIHF.js.map +1 -0
- package/dist/chunk-POZER3MQ.js +407 -0
- package/dist/chunk-POZER3MQ.js.map +1 -0
- package/dist/chunk-RTRVBEIB.js +56 -0
- package/dist/chunk-RTRVBEIB.js.map +1 -0
- package/dist/chunk-RTVRUNYR.js +29 -0
- package/dist/chunk-RTVRUNYR.js.map +1 -0
- package/dist/chunk-SS7ELMJ7.js +406 -0
- package/dist/chunk-SS7ELMJ7.js.map +1 -0
- package/dist/chunk-STCCNLPC.js +160 -0
- package/dist/chunk-STCCNLPC.js.map +1 -0
- package/dist/chunk-SVLNF434.js +261 -0
- package/dist/chunk-SVLNF434.js.map +1 -0
- package/dist/chunk-UYFOLI2X.js +24 -0
- package/dist/chunk-UYFOLI2X.js.map +1 -0
- package/dist/chunk-VAN4WW3F.js +67 -0
- package/dist/chunk-VAN4WW3F.js.map +1 -0
- package/dist/chunk-WOMHL23C.js +369 -0
- package/dist/chunk-WOMHL23C.js.map +1 -0
- package/dist/chunk-WWVD5DR5.js +120 -0
- package/dist/chunk-WWVD5DR5.js.map +1 -0
- package/dist/chunk-XG2YMCJW.js +201 -0
- package/dist/chunk-XG2YMCJW.js.map +1 -0
- package/dist/chunk-XIJNE67Y.js +210 -0
- package/dist/chunk-XIJNE67Y.js.map +1 -0
- package/dist/chunk-Y6UJOUE5.js +44 -0
- package/dist/chunk-Y6UJOUE5.js.map +1 -0
- package/dist/chunk-YUO6SKJD.js +348 -0
- package/dist/chunk-YUO6SKJD.js.map +1 -0
- package/dist/components--hjBMTnz.d.ts +120 -0
- package/dist/components-B29WHGQN.d.ts +39 -0
- package/dist/components-BWG1GkZA.d.ts +20 -0
- package/dist/components-XCpL6IYt.d.ts +18 -0
- package/dist/components-qWwOsvwh.d.ts +43 -0
- package/dist/cooldown.d.ts +116 -0
- package/dist/cooldown.js +20 -0
- package/dist/cooldown.js.map +1 -0
- package/dist/coordinators/boundary-ricochet-3d.d.ts +19 -0
- package/dist/coordinators/boundary-ricochet-3d.js +8 -0
- package/dist/coordinators/boundary-ricochet-3d.js.map +1 -0
- package/dist/coordinators/boundary-ricochet.d.ts +34 -0
- package/dist/coordinators/boundary-ricochet.js +8 -0
- package/dist/coordinators/boundary-ricochet.js.map +1 -0
- package/dist/coordinators/first-person-shooter.d.ts +35 -0
- package/dist/coordinators/first-person-shooter.js +9 -0
- package/dist/coordinators/first-person-shooter.js.map +1 -0
- package/dist/coordinators/multidirectional-space-shooter.d.ts +27 -0
- package/dist/coordinators/multidirectional-space-shooter.js +10 -0
- package/dist/coordinators/multidirectional-space-shooter.js.map +1 -0
- package/dist/coordinators/top-down-shooter.d.ts +27 -0
- package/dist/coordinators/top-down-shooter.js +10 -0
- package/dist/coordinators/top-down-shooter.js.map +1 -0
- package/dist/core.d.ts +25 -0
- package/dist/core.js +327 -0
- package/dist/core.js.map +1 -0
- package/dist/destructible-3d.d.ts +42 -0
- package/dist/destructible-3d.js +13 -0
- package/dist/destructible-3d.js.map +1 -0
- package/dist/destructible-prebake.d.ts +28 -0
- package/dist/destructible-prebake.js +23 -0
- package/dist/destructible-prebake.js.map +1 -0
- package/dist/entity-types-CmtQ-IBp.d.ts +15 -0
- package/dist/first-person-perspective-C9Mptl0-.d.ts +23 -0
- package/dist/first-person.d.ts +204 -0
- package/dist/first-person.js +27 -0
- package/dist/first-person.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +263 -0
- package/dist/index.js.map +1 -0
- package/dist/jumper-2d.d.ts +113 -0
- package/dist/jumper-2d.js +27 -0
- package/dist/jumper-2d.js.map +1 -0
- package/dist/jumper-3d.d.ts +164 -0
- package/dist/jumper-3d.js +29 -0
- package/dist/jumper-3d.js.map +1 -0
- package/dist/moveable-BCRpjAQb.d.ts +176 -0
- package/dist/particle-emitter/presets.d.ts +46 -0
- package/dist/particle-emitter/presets.js +15 -0
- package/dist/particle-emitter/presets.js.map +1 -0
- package/dist/particle-emitter.d.ts +78 -0
- package/dist/particle-emitter.js +15 -0
- package/dist/particle-emitter.js.map +1 -0
- package/dist/payload-BQzu-oY_.d.ts +89 -0
- package/dist/platformer-3d.d.ts +347 -0
- package/dist/platformer-3d.js +23 -0
- package/dist/platformer-3d.js.map +1 -0
- package/dist/ricochet-2d.d.ts +274 -0
- package/dist/ricochet-2d.js +16 -0
- package/dist/ricochet-2d.js.map +1 -0
- package/dist/ricochet-3d.d.ts +116 -0
- package/dist/ricochet-3d.js +16 -0
- package/dist/ricochet-3d.js.map +1 -0
- package/dist/runtime-2d.d.ts +2 -0
- package/dist/runtime-2d.js +32 -0
- package/dist/runtime-2d.js.map +1 -0
- package/dist/runtime-pong.d.ts +92 -0
- package/dist/runtime-pong.js +38 -0
- package/dist/runtime-pong.js.map +1 -0
- package/dist/screen-visibility.d.ts +74 -0
- package/dist/screen-visibility.js +10 -0
- package/dist/screen-visibility.js.map +1 -0
- package/dist/screen-wrap.d.ts +86 -0
- package/dist/screen-wrap.js +16 -0
- package/dist/screen-wrap.js.map +1 -0
- package/dist/shooter-2d.d.ts +77 -0
- package/dist/shooter-2d.js +12 -0
- package/dist/shooter-2d.js.map +1 -0
- package/dist/spark-DDlIg1nM.d.ts +148 -0
- package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
- package/dist/thruster.d.ts +132 -0
- package/dist/thruster.js +26 -0
- package/dist/thruster.js.map +1 -0
- package/dist/top-down-movement.d.ts +40 -0
- package/dist/top-down-movement.js +20 -0
- package/dist/top-down-movement.js.map +1 -0
- package/dist/use-behavior-D_82jAL3.d.ts +13 -0
- package/dist/vector-Bm65-FOG.d.ts +45 -0
- package/dist/world-boundary-2d.d.ts +141 -0
- package/dist/world-boundary-2d.js +21 -0
- package/dist/world-boundary-2d.js.map +1 -0
- package/dist/world-boundary-3d.d.ts +75 -0
- package/dist/world-boundary-3d.js +21 -0
- package/dist/world-boundary-3d.js.map +1 -0
- package/package.json +161 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SyncStateMachine,
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-4URF65BR.js";
|
|
5
|
+
import {
|
|
6
|
+
defineBehavior
|
|
7
|
+
} from "./chunk-EZNCCO4L.js";
|
|
8
|
+
|
|
9
|
+
// src/lib/behaviors/platformer-3d/components.ts
|
|
10
|
+
function createPlatformer3DMovementComponent(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
walkSpeed: options.walkSpeed ?? 12,
|
|
13
|
+
runSpeed: options.runSpeed ?? 24,
|
|
14
|
+
jumpForce: options.jumpForce ?? 12,
|
|
15
|
+
maxJumps: options.maxJumps ?? 1,
|
|
16
|
+
gravity: options.gravity ?? 9.82,
|
|
17
|
+
groundRayLength: options.groundRayLength ?? 1,
|
|
18
|
+
coyoteTime: options.coyoteTime ?? 0.1,
|
|
19
|
+
jumpBufferTime: options.jumpBufferTime ?? 0.1,
|
|
20
|
+
jumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,
|
|
21
|
+
multiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,
|
|
22
|
+
// 150ms default
|
|
23
|
+
debugGroundProbe: options.debugGroundProbe ?? false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createPlatformer3DInputComponent() {
|
|
27
|
+
return {
|
|
28
|
+
moveX: 0,
|
|
29
|
+
moveZ: 0,
|
|
30
|
+
jump: false,
|
|
31
|
+
run: false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createPlatformer3DStateComponent() {
|
|
35
|
+
return {
|
|
36
|
+
grounded: false,
|
|
37
|
+
jumping: false,
|
|
38
|
+
falling: false,
|
|
39
|
+
jumpCount: 0,
|
|
40
|
+
jumpStartHeight: 0,
|
|
41
|
+
currentSpeed: 0,
|
|
42
|
+
lastGroundedY: 0,
|
|
43
|
+
jumpPressedLastFrame: false,
|
|
44
|
+
collisionGrounded: false,
|
|
45
|
+
groundedCollisionTime: 0,
|
|
46
|
+
timeSinceGrounded: 0,
|
|
47
|
+
jumpBuffered: false,
|
|
48
|
+
jumpBufferTimer: 0,
|
|
49
|
+
jumpHeld: false,
|
|
50
|
+
jumpCutApplied: false,
|
|
51
|
+
jumpReleasedSinceLastJump: true,
|
|
52
|
+
timeSinceJump: 0,
|
|
53
|
+
jumpedThisFrame: false,
|
|
54
|
+
jumpCutRampTimer: 0,
|
|
55
|
+
groundedGraceTimer: 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts
|
|
60
|
+
var Platformer3DState = /* @__PURE__ */ ((Platformer3DState2) => {
|
|
61
|
+
Platformer3DState2["Idle"] = "idle";
|
|
62
|
+
Platformer3DState2["Walking"] = "walking";
|
|
63
|
+
Platformer3DState2["Running"] = "running";
|
|
64
|
+
Platformer3DState2["Jumping"] = "jumping";
|
|
65
|
+
Platformer3DState2["Falling"] = "falling";
|
|
66
|
+
Platformer3DState2["Landing"] = "landing";
|
|
67
|
+
return Platformer3DState2;
|
|
68
|
+
})(Platformer3DState || {});
|
|
69
|
+
var Platformer3DEvent = /* @__PURE__ */ ((Platformer3DEvent2) => {
|
|
70
|
+
Platformer3DEvent2["Walk"] = "walk";
|
|
71
|
+
Platformer3DEvent2["Run"] = "run";
|
|
72
|
+
Platformer3DEvent2["Jump"] = "jump";
|
|
73
|
+
Platformer3DEvent2["Fall"] = "fall";
|
|
74
|
+
Platformer3DEvent2["Land"] = "land";
|
|
75
|
+
Platformer3DEvent2["Stop"] = "stop";
|
|
76
|
+
return Platformer3DEvent2;
|
|
77
|
+
})(Platformer3DEvent || {});
|
|
78
|
+
var Platformer3DFSM = class {
|
|
79
|
+
constructor(ctx) {
|
|
80
|
+
this.ctx = ctx;
|
|
81
|
+
this.machine = new SyncStateMachine(
|
|
82
|
+
"idle" /* Idle */,
|
|
83
|
+
[
|
|
84
|
+
// Idle transitions
|
|
85
|
+
t("idle" /* Idle */, "walk" /* Walk */, "walking" /* Walking */),
|
|
86
|
+
t("idle" /* Idle */, "run" /* Run */, "running" /* Running */),
|
|
87
|
+
t("idle" /* Idle */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
88
|
+
t("idle" /* Idle */, "fall" /* Fall */, "falling" /* Falling */),
|
|
89
|
+
// Walking transitions
|
|
90
|
+
t("walking" /* Walking */, "run" /* Run */, "running" /* Running */),
|
|
91
|
+
t("walking" /* Walking */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
92
|
+
t("walking" /* Walking */, "stop" /* Stop */, "idle" /* Idle */),
|
|
93
|
+
t("walking" /* Walking */, "fall" /* Fall */, "falling" /* Falling */),
|
|
94
|
+
// Running transitions
|
|
95
|
+
t("running" /* Running */, "walk" /* Walk */, "walking" /* Walking */),
|
|
96
|
+
t("running" /* Running */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
97
|
+
t("running" /* Running */, "stop" /* Stop */, "idle" /* Idle */),
|
|
98
|
+
t("running" /* Running */, "fall" /* Fall */, "falling" /* Falling */),
|
|
99
|
+
// Jumping transitions
|
|
100
|
+
t("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
|
|
101
|
+
t("jumping" /* Jumping */, "land" /* Land */, "landing" /* Landing */),
|
|
102
|
+
t("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
103
|
+
// Multi-jump
|
|
104
|
+
// Falling transitions
|
|
105
|
+
t("falling" /* Falling */, "land" /* Land */, "landing" /* Landing */),
|
|
106
|
+
// Landing transitions
|
|
107
|
+
t("landing" /* Landing */, "walk" /* Walk */, "walking" /* Walking */),
|
|
108
|
+
t("landing" /* Landing */, "run" /* Run */, "running" /* Running */),
|
|
109
|
+
t("landing" /* Landing */, "stop" /* Stop */, "idle" /* Idle */),
|
|
110
|
+
// Self-transitions (no-ops)
|
|
111
|
+
t("idle" /* Idle */, "stop" /* Stop */, "idle" /* Idle */)
|
|
112
|
+
]
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
ctx;
|
|
116
|
+
machine;
|
|
117
|
+
/**
|
|
118
|
+
* Get the current state
|
|
119
|
+
*/
|
|
120
|
+
getState() {
|
|
121
|
+
return this.machine.getState();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Dispatch an event to the FSM
|
|
125
|
+
*/
|
|
126
|
+
dispatch(event) {
|
|
127
|
+
if (this.machine.can(event)) {
|
|
128
|
+
this.machine.syncDispatch(event);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if grounded
|
|
133
|
+
*/
|
|
134
|
+
isGrounded() {
|
|
135
|
+
const state = this.getState();
|
|
136
|
+
return state === "idle" /* Idle */ || state === "walking" /* Walking */ || state === "running" /* Running */ || state === "landing" /* Landing */;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get current jump count from context
|
|
140
|
+
*/
|
|
141
|
+
getJumpCount() {
|
|
142
|
+
return this.ctx.state.jumpCount;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Handle collision event to update ground state
|
|
146
|
+
*/
|
|
147
|
+
handleCollision(ctx) {
|
|
148
|
+
if (ctx.contact.normal.y > 0.5) {
|
|
149
|
+
this.ctx.state.collisionGrounded = true;
|
|
150
|
+
this.ctx.state.groundedCollisionTime = performance.now();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update FSM based on current state
|
|
155
|
+
*/
|
|
156
|
+
update(input, state) {
|
|
157
|
+
this.ctx.input = input;
|
|
158
|
+
this.ctx.state = state;
|
|
159
|
+
const currentState = this.getState();
|
|
160
|
+
const hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;
|
|
161
|
+
const isRunning = input.run;
|
|
162
|
+
if (currentState === "falling" /* Falling */ && state.grounded) {
|
|
163
|
+
this.dispatch("land" /* Land */);
|
|
164
|
+
}
|
|
165
|
+
if (state.grounded) {
|
|
166
|
+
if (hasInput) {
|
|
167
|
+
if (isRunning) {
|
|
168
|
+
this.dispatch("run" /* Run */);
|
|
169
|
+
} else {
|
|
170
|
+
this.dispatch("walk" /* Walk */);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
this.dispatch("stop" /* Stop */);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
if (state.falling) {
|
|
177
|
+
this.dispatch("fall" /* Fall */);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (input.jump && !state.jumpPressedLastFrame) {
|
|
181
|
+
this.dispatch("jump" /* Jump */);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/lib/behaviors/platformer-3d/platformer-3d.behavior.ts
|
|
187
|
+
var JUMP_CUT_RAMP_DURATION = 0.05;
|
|
188
|
+
var GROUNDED_GRACE_WINDOW = 0.06;
|
|
189
|
+
var Platformer3DBehavior = class {
|
|
190
|
+
world;
|
|
191
|
+
scene;
|
|
192
|
+
/**
|
|
193
|
+
* Tracks entities whose Rapier `gravityScale` has been zeroed so that
|
|
194
|
+
* `applyGravity` is the sole writer of vertical velocity. We do this
|
|
195
|
+
* lazily on the first update because the body isn't always available at
|
|
196
|
+
* construction time.
|
|
197
|
+
*/
|
|
198
|
+
gravityConfiguredUUIDs = /* @__PURE__ */ new Set();
|
|
199
|
+
constructor(world, scene) {
|
|
200
|
+
this.world = world;
|
|
201
|
+
this.scene = scene;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Detect "grounded" using the actual capsule collider's contact pairs.
|
|
205
|
+
*
|
|
206
|
+
* A contact is treated as ground when its solver point lies below the
|
|
207
|
+
* body center (i.e., it sits on the bottom hemisphere/cylinder). We
|
|
208
|
+
* deliberately don't filter by normal direction: the contact point's
|
|
209
|
+
* position alone is robust against the various normal-flip conventions
|
|
210
|
+
* Rapier uses for `flipped` manifolds and against rolling-bottom edges
|
|
211
|
+
* on heightmap terrain.
|
|
212
|
+
*/
|
|
213
|
+
detectGroundedFromContacts(entity) {
|
|
214
|
+
const collider = entity.collider;
|
|
215
|
+
const physicsWorld = this.world?.world;
|
|
216
|
+
if (!collider || !physicsWorld?.contactsWith) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
let grounded = false;
|
|
220
|
+
physicsWorld.contactsWith(collider, (other) => {
|
|
221
|
+
if (grounded) return;
|
|
222
|
+
physicsWorld.contactPair(collider, other, (manifold, flipped) => {
|
|
223
|
+
if (grounded) return;
|
|
224
|
+
const num = manifold.numContacts();
|
|
225
|
+
for (let i = 0; i < num; i++) {
|
|
226
|
+
const local = flipped ? manifold.localContactPoint2(i) : manifold.localContactPoint1(i);
|
|
227
|
+
if (!local) continue;
|
|
228
|
+
if (local.y < 0) {
|
|
229
|
+
grounded = true;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
return grounded;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One-time per-entity physics setup: take over gravity from Rapier's
|
|
239
|
+
* world so that `applyGravity` controls the entire airborne fall curve.
|
|
240
|
+
* Without this, gravity was effectively doubled (Rapier's world gravity
|
|
241
|
+
* + this behavior's explicit gravity were both pulling the body down).
|
|
242
|
+
*/
|
|
243
|
+
ensurePhysicsConfigured(entity) {
|
|
244
|
+
if (this.gravityConfiguredUUIDs.has(entity.uuid)) return;
|
|
245
|
+
if (typeof entity.body?.setGravityScale === "function") {
|
|
246
|
+
entity.body.setGravityScale(0, true);
|
|
247
|
+
}
|
|
248
|
+
this.gravityConfiguredUUIDs.add(entity.uuid);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Apply horizontal movement based on input.
|
|
252
|
+
*
|
|
253
|
+
* Only writes X/Z axes — Y is left untouched so Rapier's contact resolution
|
|
254
|
+
* can damp vertical motion correctly while grounded. Jump and gravity are
|
|
255
|
+
* the sole writers of Y velocity (see handleJump / applyGravity).
|
|
256
|
+
*/
|
|
257
|
+
applyMovement(entity) {
|
|
258
|
+
const input = entity.$platformer;
|
|
259
|
+
const movement = entity.platformer;
|
|
260
|
+
const state = entity.platformerState;
|
|
261
|
+
const speed = input.run ? movement.runSpeed : movement.walkSpeed;
|
|
262
|
+
state.currentSpeed = speed;
|
|
263
|
+
const moveX = input.moveX * speed;
|
|
264
|
+
const moveZ = input.moveZ * speed;
|
|
265
|
+
entity.transformStore.velocity.x = moveX;
|
|
266
|
+
entity.transformStore.velocity.z = moveZ;
|
|
267
|
+
entity.transformStore.dirty.velocityX = true;
|
|
268
|
+
entity.transformStore.dirty.velocityZ = true;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Handle jump logic with multi-jump, coyote time, buffering, and jump-cut ramp.
|
|
272
|
+
*/
|
|
273
|
+
handleJump(entity, delta) {
|
|
274
|
+
const input = entity.$platformer;
|
|
275
|
+
const movement = entity.platformer;
|
|
276
|
+
const state = entity.platformerState;
|
|
277
|
+
if (state.jumping || state.falling) {
|
|
278
|
+
state.timeSinceJump += delta;
|
|
279
|
+
}
|
|
280
|
+
if (!input.jump && state.jumpHeld) {
|
|
281
|
+
state.jumpReleasedSinceLastJump = true;
|
|
282
|
+
}
|
|
283
|
+
if (input.jump && !state.jumpPressedLastFrame) {
|
|
284
|
+
state.jumpBuffered = true;
|
|
285
|
+
state.jumpBufferTimer = movement.jumpBufferTime;
|
|
286
|
+
}
|
|
287
|
+
state.jumpPressedLastFrame = input.jump;
|
|
288
|
+
state.jumpHeld = input.jump;
|
|
289
|
+
if (state.jumpBuffered) {
|
|
290
|
+
state.jumpBufferTimer -= delta;
|
|
291
|
+
if (state.jumpBufferTimer <= 0) {
|
|
292
|
+
state.jumpBuffered = false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const minTimeBeforeCut = 0.1;
|
|
296
|
+
const canApplyCut = state.timeSinceJump >= minTimeBeforeCut;
|
|
297
|
+
if (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {
|
|
298
|
+
state.jumpCutApplied = true;
|
|
299
|
+
state.jumpCutRampTimer = JUMP_CUT_RAMP_DURATION;
|
|
300
|
+
}
|
|
301
|
+
if (state.jumpCutRampTimer > 0) {
|
|
302
|
+
const velocity = entity.body.linvel();
|
|
303
|
+
if (velocity.y > 0) {
|
|
304
|
+
const stepT = Math.min(delta / state.jumpCutRampTimer, 1);
|
|
305
|
+
const targetY = velocity.y * movement.jumpCutMultiplier;
|
|
306
|
+
entity.transformStore.velocity.y = velocity.y + (targetY - velocity.y) * stepT;
|
|
307
|
+
entity.transformStore.dirty.velocityY = true;
|
|
308
|
+
}
|
|
309
|
+
state.jumpCutRampTimer = Math.max(0, state.jumpCutRampTimer - delta);
|
|
310
|
+
}
|
|
311
|
+
if (!state.jumpBuffered) return;
|
|
312
|
+
const inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;
|
|
313
|
+
const isFirstJump = state.grounded || inCoyoteWindow && state.jumpCount === 0;
|
|
314
|
+
const hasJumpsRemaining = state.jumpCount < movement.maxJumps;
|
|
315
|
+
const buttonReleased = state.jumpReleasedSinceLastJump;
|
|
316
|
+
const inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;
|
|
317
|
+
const canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;
|
|
318
|
+
if (isFirstJump || canMultiJump) {
|
|
319
|
+
state.jumpBuffered = false;
|
|
320
|
+
state.jumpCount++;
|
|
321
|
+
state.jumpReleasedSinceLastJump = false;
|
|
322
|
+
state.timeSinceJump = 0;
|
|
323
|
+
state.jumpStartHeight = entity.body.translation().y;
|
|
324
|
+
state.jumping = true;
|
|
325
|
+
state.falling = false;
|
|
326
|
+
state.jumpCutApplied = false;
|
|
327
|
+
state.jumpCutRampTimer = 0;
|
|
328
|
+
state.groundedGraceTimer = 0;
|
|
329
|
+
entity.transformStore.velocity.y = movement.jumpForce;
|
|
330
|
+
entity.transformStore.dirty.velocityY = true;
|
|
331
|
+
state.jumpedThisFrame = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Apply gravity when not grounded.
|
|
336
|
+
*
|
|
337
|
+
* Skipped on the single frame immediately after a jump impulse is applied
|
|
338
|
+
* (tracked via `state.jumpedThisFrame`) so gravity never overwrites a
|
|
339
|
+
* just-applied jump velocity. The flag is cleared here regardless, so it
|
|
340
|
+
* only suppresses exactly one gravity step — independent of frame rate.
|
|
341
|
+
*/
|
|
342
|
+
applyGravity(entity, delta) {
|
|
343
|
+
const movement = entity.platformer;
|
|
344
|
+
const state = entity.platformerState;
|
|
345
|
+
if (state.jumpedThisFrame) {
|
|
346
|
+
state.jumpedThisFrame = false;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (state.grounded) return;
|
|
350
|
+
const currentVel = entity.body.linvel();
|
|
351
|
+
const newYVelocity = currentVel.y - movement.gravity * delta;
|
|
352
|
+
entity.transformStore.velocity.y = newYVelocity;
|
|
353
|
+
entity.transformStore.dirty.velocityY = true;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Update entity state based on physics.
|
|
357
|
+
*
|
|
358
|
+
* Grounded is determined purely by Rapier's contact pairs on the entity's
|
|
359
|
+
* collider. This removes a whole class of slope/heightmap jitter caused
|
|
360
|
+
* by the previous shape probe disagreeing with the contact resolver — the
|
|
361
|
+
* probe could report grounded while a contact impulse had just nudged the
|
|
362
|
+
* body off the ground, producing visible micro-bouncing.
|
|
363
|
+
*
|
|
364
|
+
* While grounded, vertical velocity is held at 0 each frame. This stops
|
|
365
|
+
* residual upward kicks from slope-contact resolution from accumulating
|
|
366
|
+
* across frames (which previously caused the character to "ride up"
|
|
367
|
+
* slopes and lose contact briefly, then fall back, then re-contact, in a
|
|
368
|
+
* tight oscillation).
|
|
369
|
+
*/
|
|
370
|
+
updateState(entity, delta) {
|
|
371
|
+
const state = entity.platformerState;
|
|
372
|
+
const wasGrounded = state.grounded;
|
|
373
|
+
const velocity = entity.body.linvel();
|
|
374
|
+
const hasContact = this.detectGroundedFromContacts(entity);
|
|
375
|
+
const isAirborne = state.jumping || state.falling;
|
|
376
|
+
let isGrounded;
|
|
377
|
+
if (isAirborne) {
|
|
378
|
+
const canLand = state.falling && !state.jumping;
|
|
379
|
+
isGrounded = hasContact && canLand;
|
|
380
|
+
} else {
|
|
381
|
+
isGrounded = hasContact;
|
|
382
|
+
}
|
|
383
|
+
if (!isGrounded && wasGrounded) {
|
|
384
|
+
if (state.groundedGraceTimer <= 0) {
|
|
385
|
+
state.groundedGraceTimer = GROUNDED_GRACE_WINDOW;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (state.groundedGraceTimer > 0) {
|
|
389
|
+
if (!isGrounded && wasGrounded && !isAirborne) {
|
|
390
|
+
isGrounded = true;
|
|
391
|
+
state.groundedGraceTimer = Math.max(0, state.groundedGraceTimer - delta);
|
|
392
|
+
} else {
|
|
393
|
+
state.groundedGraceTimer = 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
state.grounded = isGrounded;
|
|
397
|
+
if (state.grounded) {
|
|
398
|
+
entity.transformStore.velocity.y = 0;
|
|
399
|
+
entity.transformStore.dirty.velocityY = true;
|
|
400
|
+
}
|
|
401
|
+
if (state.grounded) {
|
|
402
|
+
state.timeSinceGrounded = 0;
|
|
403
|
+
state.lastGroundedY = entity.body.translation().y;
|
|
404
|
+
} else {
|
|
405
|
+
state.timeSinceGrounded += delta;
|
|
406
|
+
}
|
|
407
|
+
if (!wasGrounded && state.grounded) {
|
|
408
|
+
state.jumpCount = 0;
|
|
409
|
+
state.jumping = false;
|
|
410
|
+
state.falling = false;
|
|
411
|
+
state.jumpCutApplied = false;
|
|
412
|
+
state.jumpCutRampTimer = 0;
|
|
413
|
+
}
|
|
414
|
+
if (velocity.y < -0.1 && !state.grounded) {
|
|
415
|
+
if (state.jumping && velocity.y < 0) {
|
|
416
|
+
state.jumping = false;
|
|
417
|
+
state.falling = true;
|
|
418
|
+
} else if (!state.jumping) {
|
|
419
|
+
state.falling = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Update one platformer entity. Invoked per linked entity by the
|
|
425
|
+
* Platformer3DBehaviorSystem adapter.
|
|
426
|
+
*/
|
|
427
|
+
updateEntity(entity, delta) {
|
|
428
|
+
if (!entity.platformer || !entity.$platformer || !entity.platformerState) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const platformerEntity = entity;
|
|
432
|
+
this.ensurePhysicsConfigured(platformerEntity);
|
|
433
|
+
this.updateState(platformerEntity, delta);
|
|
434
|
+
this.applyMovement(platformerEntity);
|
|
435
|
+
this.handleJump(platformerEntity, delta);
|
|
436
|
+
this.applyGravity(platformerEntity, delta);
|
|
437
|
+
}
|
|
438
|
+
destroy() {
|
|
439
|
+
this.gravityConfiguredUUIDs.clear();
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts
|
|
444
|
+
var defaultOptions = {
|
|
445
|
+
walkSpeed: 12,
|
|
446
|
+
runSpeed: 24,
|
|
447
|
+
jumpForce: 12,
|
|
448
|
+
maxJumps: 1,
|
|
449
|
+
gravity: 9.82,
|
|
450
|
+
groundRayLength: 1,
|
|
451
|
+
debugGroundProbe: false
|
|
452
|
+
};
|
|
453
|
+
var PLATFORMER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-3d");
|
|
454
|
+
var Platformer3DBehaviorSystem = class {
|
|
455
|
+
constructor(world, scene, wasmStage, getBehaviorLinks) {
|
|
456
|
+
this.world = world;
|
|
457
|
+
this.scene = scene;
|
|
458
|
+
this.wasmStage = wasmStage;
|
|
459
|
+
this.getBehaviorLinks = getBehaviorLinks;
|
|
460
|
+
this.movementBehavior = new Platformer3DBehavior(world, scene);
|
|
461
|
+
}
|
|
462
|
+
world;
|
|
463
|
+
scene;
|
|
464
|
+
wasmStage;
|
|
465
|
+
getBehaviorLinks;
|
|
466
|
+
movementBehavior;
|
|
467
|
+
attachedRuntimeSlots = /* @__PURE__ */ new Set();
|
|
468
|
+
update(_ecs, delta) {
|
|
469
|
+
const links = this.getBehaviorLinks?.(PLATFORMER_BEHAVIOR_KEY);
|
|
470
|
+
if (!links) return;
|
|
471
|
+
for (const link of links) {
|
|
472
|
+
const gameEntity = link.entity;
|
|
473
|
+
const platformerRef = link.ref;
|
|
474
|
+
const options = platformerRef.options;
|
|
475
|
+
if (!gameEntity.platformer) {
|
|
476
|
+
gameEntity.platformer = createPlatformer3DMovementComponent(options);
|
|
477
|
+
}
|
|
478
|
+
if (!gameEntity.$platformer) {
|
|
479
|
+
gameEntity.$platformer = createPlatformer3DInputComponent();
|
|
480
|
+
}
|
|
481
|
+
if (!gameEntity.platformerState) {
|
|
482
|
+
gameEntity.platformerState = createPlatformer3DStateComponent();
|
|
483
|
+
}
|
|
484
|
+
if (!platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
485
|
+
platformerRef.fsm = new Platformer3DFSM({
|
|
486
|
+
input: gameEntity.$platformer,
|
|
487
|
+
state: gameEntity.platformerState
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
if (platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
491
|
+
platformerRef.fsm.update(gameEntity.$platformer, gameEntity.platformerState);
|
|
492
|
+
}
|
|
493
|
+
const handle = gameEntity.runtimeHandle ?? -1;
|
|
494
|
+
if (this.wasmStage && handle >= 0) {
|
|
495
|
+
this.ensureWasmAttached(handle, options);
|
|
496
|
+
const input = gameEntity.$platformer;
|
|
497
|
+
if (input) {
|
|
498
|
+
this.wasmStage.setPlatformer3DInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);
|
|
499
|
+
this.wasmStage.setPlatformer3DInputButtons(handle, !!input.jump, !!input.run);
|
|
500
|
+
}
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (!gameEntity.body) continue;
|
|
504
|
+
this.movementBehavior.updateEntity(gameEntity, delta);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
ensureWasmAttached(handle, options) {
|
|
508
|
+
if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
|
|
509
|
+
this.wasmStage.attachPlatformer3D(handle, {
|
|
510
|
+
halfHeight: 0.9,
|
|
511
|
+
radius: 0.4,
|
|
512
|
+
walkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed,
|
|
513
|
+
runSpeed: options.runSpeed ?? defaultOptions.runSpeed,
|
|
514
|
+
jumpForce: options.jumpForce ?? defaultOptions.jumpForce,
|
|
515
|
+
maxJumps: options.maxJumps ?? defaultOptions.maxJumps,
|
|
516
|
+
gravity: options.gravity ?? defaultOptions.gravity
|
|
517
|
+
});
|
|
518
|
+
this.attachedRuntimeSlots.add(handle);
|
|
519
|
+
}
|
|
520
|
+
destroy(_ecs) {
|
|
521
|
+
this.movementBehavior.destroy();
|
|
522
|
+
this.attachedRuntimeSlots.clear();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
var Platformer3DBehavior2 = defineBehavior({
|
|
526
|
+
name: "platformer-3d",
|
|
527
|
+
defaultOptions,
|
|
528
|
+
systemFactory: (ctx) => new Platformer3DBehaviorSystem(
|
|
529
|
+
ctx.world,
|
|
530
|
+
ctx.scene,
|
|
531
|
+
ctx.wasmStage ?? null,
|
|
532
|
+
ctx.getBehaviorLinks
|
|
533
|
+
),
|
|
534
|
+
createHandle: (ref) => ({
|
|
535
|
+
getState: () => ref.fsm?.getState() ?? "idle" /* Idle */,
|
|
536
|
+
isGrounded: () => ref.fsm?.isGrounded() ?? false,
|
|
537
|
+
getJumpCount: () => ref.fsm?.getJumpCount() ?? 0,
|
|
538
|
+
onPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx)
|
|
539
|
+
})
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
export {
|
|
543
|
+
createPlatformer3DMovementComponent,
|
|
544
|
+
createPlatformer3DInputComponent,
|
|
545
|
+
createPlatformer3DStateComponent,
|
|
546
|
+
Platformer3DState,
|
|
547
|
+
Platformer3DEvent,
|
|
548
|
+
Platformer3DFSM,
|
|
549
|
+
Platformer3DBehavior,
|
|
550
|
+
Platformer3DBehavior2
|
|
551
|
+
};
|
|
552
|
+
//# sourceMappingURL=chunk-JAN34G6S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/platformer-3d/components.ts","../src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts","../src/lib/behaviors/platformer-3d/platformer-3d.behavior.ts","../src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 3D ECS Components\n * \n * Components for 3D platformer movement system including walking, running,\n * jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DMovementComponent (capability / configuration)\n// Defines the movement capabilities and physics parameters\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force (optional override) */\n\tgravity: number;\n\t/** Ray length for ground detection */\n\tgroundRayLength: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Enable ground-ray debug visualization */\n\tdebugGroundProbe: boolean;\n}\n\nexport function createPlatformer3DMovementComponent(\n\toptions: Partial<Platformer3DMovementComponent> = {}\n): Platformer3DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 12,\n\t\trunSpeed: options.runSpeed ?? 24,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 9.82,\n\t\tgroundRayLength: options.groundRayLength ?? 1.0,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15, // 150ms default\n\t\tdebugGroundProbe: options.debugGroundProbe ?? false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer3DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Forward/backward movement input (-1 to 1) */\n\tmoveZ: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer3DInputComponent(): Platformer3DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tmoveZ: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DStateComponent (runtime state)\n// Tracks the current state of the platformer entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t\n\t// -- New fields --\n\t/** Is currently colliding with ground (from physics collision callback) */\n\tcollisionGrounded: boolean;\n\t/** Timestamp of last ground collision */\n\tgroundedCollisionTime: number;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, velocity.y lerps toward velocity.y * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/** Remaining seconds of grounded-hysteresis grace (keeps `grounded` true briefly after ground probe misses, to absorb edge/ray flicker). */\n\tgroundedGraceTimer: number;\n}\n\nexport function createPlatformer3DStateComponent(): Platformer3DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\tcollisionGrounded: false,\n\t\tgroundedCollisionTime: 0,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tgroundedGraceTimer: 0,\n\t};\n}\n","/**\n * Platformer 3D Finite State Machine\n * \n * Manages state transitions for 3D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer3DInputComponent, Platformer3DStateComponent } from './components';\n\n/**\n * Simplified Collision Context interface\n */\nexport interface PlatformerCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number; z?: number };\n\t};\n}\n\n/**\n * Platformer states\n */\nexport enum Platformer3DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum Platformer3DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface Platformer3DContext {\n\tinput: Platformer3DInputComponent;\n\tstate: Platformer3DStateComponent;\n}\n\n/**\n * Platformer 3D FSM\n */\nexport class Platformer3DFSM {\n\tmachine: SyncStateMachine<Platformer3DState, Platformer3DEvent, never>;\n\n\tconstructor(private ctx: Platformer3DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer3DState, Platformer3DEvent, never>(\n\t\t\tPlatformer3DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Jump, Platformer3DState.Jumping), // Multi-jump\n\t\t\t\t\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer3DState.Falling, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\t\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\t\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get the current state\n\t */\n\tgetState(): Platformer3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to the FSM\n\t */\n\tdispatch(event: Platformer3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Check if grounded\n\t */\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn state === Platformer3DState.Idle || \n\t\t state === Platformer3DState.Walking || \n\t\t state === Platformer3DState.Running || \n\t\t state === Platformer3DState.Landing;\n\t}\n\n\t/**\n\t * Get current jump count from context\n\t */\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Handle collision event to update ground state\n\t */\n\thandleCollision(ctx: PlatformerCollisionContext): void {\n\t\t// Check if collision is ground (normal pointing up)\n\t\t// Y normal > 0.5 means roughly within 60 degrees of up\n\t\tif (ctx.contact.normal.y > 0.5) {\n\t\t\tthis.ctx.state.collisionGrounded = true;\n\t\t\tthis.ctx.state.groundedCollisionTime = performance.now();\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on current state\n\t */\n\tupdate(input: Platformer3DInputComponent, state: Platformer3DStateComponent): void {\n\t\t// Update context\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\t// Get current FSM state\n\t\tconst currentState = this.getState();\n\n\t\t// Determine movement state\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer3DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer3DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\t// Send appropriate events based on state\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Stop);\n\t\t\t}\n\t\t} else {\n\t\t\t// Not grounded - check if we should be falling\n\t\t\t// Transition to falling if: We're in jumping state and now falling, \n\t\t\t// OR we walked off a ledge (idle/walking/running and now falling)\n\t\t\tif (state.falling) {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Fall);\n\t\t\t}\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in behavior)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer3DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 3D Behavior System\n *\n * Handles 3D platformer physics including:\n * - Contact-based ground detection (uses the actual capsule collider — no\n * raycast/shapecast probe). The bottom of the body's collider is the\n * single source of truth for \"is grounded\".\n * - Horizontal movement (walk/run)\n * - Jumping with multi-jump support\n * - Gravity application (Rapier's internal gravity is disabled per-body so\n * `movement.gravity` is the only fall acceleration — previously the two\n * stacked, doubling the airborne fall rate).\n * - State synchronization with FSM\n */\n\nimport type {\n\tPlatformer3DMovementComponent,\n\tPlatformer3DInputComponent,\n\tPlatformer3DStateComponent,\n} from './components';\n\n/**\n * Duration of the jump-cut velocity ramp in seconds.\n * Instead of halving velocity.y in a single frame (which produces a visible\n * camera pop), we lerp toward the cut velocity across roughly 3 physics steps\n * at 60 Hz. Short enough to still feel responsive, long enough to be smooth.\n */\nconst JUMP_CUT_RAMP_DURATION = 0.05;\n\n/**\n * Hysteresis window for the grounded state (seconds).\n *\n * When the ground probe misses for a frame but we were grounded the previous\n * frame, remain grounded for this brief window before flipping to airborne.\n * Prevents rapid grounded/airborne toggling at platform edges where a single\n * corner ray may miss intermittently, and absorbs sub-step probe noise.\n */\nconst GROUNDED_GRACE_WINDOW = 0.06;\n\n/**\n * Entity with platformer components\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tbody: any; // RigidBody\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n\ttransformStore?: any;\n}\n\n/**\n * Platformer 3D Movement Behavior\n *\n * Core physics system for 3D platformer movement.\n *\n * Ground detection uses Rapier's narrow-phase contacts on the entity's\n * collider (`world.contactsWith` + `contactPair`). This eliminates the old\n * shape/ray probe and aligns \"grounded\" with the same surface the body\n * physically rests on, which removes the jitter that happened on heightmap\n * terrain (the probe and the contact resolver could disagree by a frame).\n */\nexport class Platformer3DBehavior {\n\tprivate world: any;\n\tprivate scene: any;\n\t/**\n\t * Tracks entities whose Rapier `gravityScale` has been zeroed so that\n\t * `applyGravity` is the sole writer of vertical velocity. We do this\n\t * lazily on the first update because the body isn't always available at\n\t * construction time.\n\t */\n\tprivate gravityConfiguredUUIDs = new Set<string>();\n\n\tconstructor(world: any, scene: any) {\n\t\tthis.world = world;\n\t\tthis.scene = scene;\n\t}\n\n\t/**\n\t * Detect \"grounded\" using the actual capsule collider's contact pairs.\n\t *\n\t * A contact is treated as ground when its solver point lies below the\n\t * body center (i.e., it sits on the bottom hemisphere/cylinder). We\n\t * deliberately don't filter by normal direction: the contact point's\n\t * position alone is robust against the various normal-flip conventions\n\t * Rapier uses for `flipped` manifolds and against rolling-bottom edges\n\t * on heightmap terrain.\n\t */\n\tprivate detectGroundedFromContacts(entity: Platformer3DEntity): boolean {\n\t\tconst collider = (entity as any).collider;\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (!collider || !physicsWorld?.contactsWith) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet grounded = false;\n\n\t\tphysicsWorld.contactsWith(collider, (other: any) => {\n\t\t\tif (grounded) return;\n\t\t\tphysicsWorld.contactPair(collider, other, (manifold: any, flipped: boolean) => {\n\t\t\t\tif (grounded) return;\n\t\t\t\tconst num = manifold.numContacts();\n\t\t\t\tfor (let i = 0; i < num; i++) {\n\t\t\t\t\tconst local = flipped\n\t\t\t\t\t\t? manifold.localContactPoint2(i)\n\t\t\t\t\t\t: manifold.localContactPoint1(i);\n\t\t\t\t\tif (!local) continue;\n\t\t\t\t\tif (local.y < 0) {\n\t\t\t\t\t\tgrounded = true;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\treturn grounded;\n\t}\n\n\t/**\n\t * One-time per-entity physics setup: take over gravity from Rapier's\n\t * world so that `applyGravity` controls the entire airborne fall curve.\n\t * Without this, gravity was effectively doubled (Rapier's world gravity\n\t * + this behavior's explicit gravity were both pulling the body down).\n\t */\n\tprivate ensurePhysicsConfigured(entity: Platformer3DEntity): void {\n\t\tif (this.gravityConfiguredUUIDs.has(entity.uuid)) return;\n\t\tif (typeof entity.body?.setGravityScale === 'function') {\n\t\t\tentity.body.setGravityScale(0, true);\n\t\t}\n\t\tthis.gravityConfiguredUUIDs.add(entity.uuid);\n\t}\n\n\t/**\n\t * Apply horizontal movement based on input.\n\t *\n\t * Only writes X/Z axes — Y is left untouched so Rapier's contact resolution\n\t * can damp vertical motion correctly while grounded. Jump and gravity are\n\t * the sole writers of Y velocity (see handleJump / applyGravity).\n\t */\n\tprivate applyMovement(entity: Platformer3DEntity): void {\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\tconst speed = input.run ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\n\t\tconst moveX = input.moveX * speed;\n\t\tconst moveZ = input.moveZ * speed;\n\n\t\tentity.transformStore.velocity.x = moveX;\n\t\tentity.transformStore.velocity.z = moveZ;\n\t\tentity.transformStore.dirty.velocityX = true;\n\t\tentity.transformStore.dirty.velocityZ = true;\n\t}\n\n\t/**\n\t * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut ramp.\n\t */\n\tprivate handleJump(entity: Platformer3DEntity, delta: number): void {\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\t// Track time since last jump (for multi-jump window)\n\t\tif (state.jumping || state.falling) {\n\t\t\tstate.timeSinceJump += delta;\n\t\t}\n\n\t\t// Track jump button release (required for multi-jump)\n\t\tif (!input.jump && state.jumpHeld) {\n\t\t\tstate.jumpReleasedSinceLastJump = true;\n\t\t}\n\n\t\t// 1. Jump Buffering - queue on new press\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tstate.jumpBuffered = true;\n\t\t\tstate.jumpBufferTimer = movement.jumpBufferTime;\n\t\t}\n\t\t\n\t\tstate.jumpPressedLastFrame = input.jump;\n\t\tstate.jumpHeld = input.jump;\n\n\t\t// Decrement buffer timer\n\t\tif (state.jumpBuffered) {\n\t\t\tstate.jumpBufferTimer -= delta;\n\t\t\tif (state.jumpBufferTimer <= 0) {\n\t\t\t\tstate.jumpBuffered = false;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Variable Jump Height (Jump Cut)\n\t\t// Only trigger a cut if we've been jumping for a bit (prevents cutting multi-jump immediately).\n\t\tconst minTimeBeforeCut = 0.1;\n\t\tconst canApplyCut = state.timeSinceJump >= minTimeBeforeCut;\n\t\tif (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {\n\t\t\tstate.jumpCutApplied = true;\n\t\t\tstate.jumpCutRampTimer = JUMP_CUT_RAMP_DURATION;\n\t\t}\n\n\t\t// 2a. Ramp velocity.y toward velocity.y * jumpCutMultiplier across the\n\t\t// ramp window so the change isn't a single-frame discontinuity.\n\t\tif (state.jumpCutRampTimer > 0) {\n\t\t\tconst velocity = entity.body.linvel();\n\t\t\tif (velocity.y > 0) {\n\t\t\t\tconst stepT = Math.min(delta / state.jumpCutRampTimer, 1);\n\t\t\t\tconst targetY = velocity.y * movement.jumpCutMultiplier;\n\t\t\t\tentity.transformStore.velocity.y = velocity.y + (targetY - velocity.y) * stepT;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t}\n\t\t\tstate.jumpCutRampTimer = Math.max(0, state.jumpCutRampTimer - delta);\n\t\t}\n\n\t\t// Execute Jump (if buffered input exists)\n\t\tif (!state.jumpBuffered) return;\n\n\t\t// 3. Jump eligibility check\n\t\tconst inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;\n\t\tconst isFirstJump = state.grounded || (inCoyoteWindow && state.jumpCount === 0);\n\t\t\n\t\t// Multi-jump requirements:\n\t\t// 1. Not grounded\n\t\t// 2. Have jumps remaining\n\t\t// 3. Button was released since last jump (no holding for double jump)\n\t\t// 4. Within the jump window (after multiJumpWindowTime has passed)\n\t\tconst hasJumpsRemaining = state.jumpCount < movement.maxJumps;\n\t\tconst buttonReleased = state.jumpReleasedSinceLastJump;\n\t\tconst inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;\n\t\tconst canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;\n\n\t\tif (isFirstJump || canMultiJump) {\n\t\t\t// Consume buffered input\n\t\t\tstate.jumpBuffered = false;\n\n\t\t\t// Increment jump count and reset tracking\n\t\t\tstate.jumpCount++;\n\t\t\tstate.jumpReleasedSinceLastJump = false; // Must release again for next jump\n\t\t\tstate.timeSinceJump = 0; // Reset window timer\n\n\t\t\t// Record jump start height\n\t\t\tstate.jumpStartHeight = entity.body.translation().y;\n\n\t\t\t// Apply jump force\n\t\t\tstate.jumping = true;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\t\t\tstate.groundedGraceTimer = 0;\n\n\t\t\t// Apply jump force via transform store (Y-axis only)\n\t\t\tentity.transformStore.velocity.y = movement.jumpForce;\n\t\t\tentity.transformStore.dirty.velocityY = true;\n\n\t\t\t// Flag this frame so applyGravity skips exactly one gravity step\n\t\t\t// (the jump impulse we just wrote is the authoritative Y velocity).\n\t\t\tstate.jumpedThisFrame = true;\n\t\t}\n\t}\n\n\t/**\n\t * Apply gravity when not grounded.\n\t *\n\t * Skipped on the single frame immediately after a jump impulse is applied\n\t * (tracked via `state.jumpedThisFrame`) so gravity never overwrites a\n\t * just-applied jump velocity. The flag is cleared here regardless, so it\n\t * only suppresses exactly one gravity step — independent of frame rate.\n\t */\n\tprivate applyGravity(entity: Platformer3DEntity, delta: number): void {\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\tif (state.jumpedThisFrame) {\n\t\t\tstate.jumpedThisFrame = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (state.grounded) return;\n\n\t\tconst currentVel = entity.body.linvel();\n\t\tconst newYVelocity = currentVel.y - movement.gravity * delta;\n\n\t\tentity.transformStore.velocity.y = newYVelocity;\n\t\tentity.transformStore.dirty.velocityY = true;\n\t}\n\n\t/**\n\t * Update entity state based on physics.\n\t *\n\t * Grounded is determined purely by Rapier's contact pairs on the entity's\n\t * collider. This removes a whole class of slope/heightmap jitter caused\n\t * by the previous shape probe disagreeing with the contact resolver — the\n\t * probe could report grounded while a contact impulse had just nudged the\n\t * body off the ground, producing visible micro-bouncing.\n\t *\n\t * While grounded, vertical velocity is held at 0 each frame. This stops\n\t * residual upward kicks from slope-contact resolution from accumulating\n\t * across frames (which previously caused the character to \"ride up\"\n\t * slopes and lose contact briefly, then fall back, then re-contact, in a\n\t * tight oscillation).\n\t */\n\tprivate updateState(entity: Platformer3DEntity, delta: number): void {\n\t\tconst state = entity.platformerState;\n\t\tconst wasGrounded = state.grounded;\n\t\tconst velocity = entity.body.linvel();\n\t\tconst hasContact = this.detectGroundedFromContacts(entity);\n\n\t\tconst isAirborne = state.jumping || state.falling;\n\t\tlet isGrounded: boolean;\n\t\tif (isAirborne) {\n\t\t\t// While in a jump arc the contact still exists for one frame after\n\t\t\t// liftoff; require the falling state before allowing a landing.\n\t\t\tconst canLand = state.falling && !state.jumping;\n\t\t\tisGrounded = hasContact && canLand;\n\t\t} else {\n\t\t\tisGrounded = hasContact;\n\t\t}\n\n\t\t// Hysteresis: if the contact briefly drops over a heightmap seam,\n\t\t// stay grounded for a short grace window.\n\t\tif (!isGrounded && wasGrounded) {\n\t\t\tif (state.groundedGraceTimer <= 0) {\n\t\t\t\tstate.groundedGraceTimer = GROUNDED_GRACE_WINDOW;\n\t\t\t}\n\t\t}\n\t\tif (state.groundedGraceTimer > 0) {\n\t\t\tif (!isGrounded && wasGrounded && !isAirborne) {\n\t\t\t\tisGrounded = true;\n\t\t\t\tstate.groundedGraceTimer = Math.max(0, state.groundedGraceTimer - delta);\n\t\t\t} else {\n\t\t\t\tstate.groundedGraceTimer = 0;\n\t\t\t}\n\t\t}\n\n\t\tstate.grounded = isGrounded;\n\n\t\t// Hold vy = 0 while grounded so slope-impulse residual doesn't drift\n\t\t// the body upward across frames. handleJump runs after this and will\n\t\t// overwrite velocity.y with jumpForce on a jump frame.\n\t\tif (state.grounded) {\n\t\t\tentity.transformStore.velocity.y = 0;\n\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tstate.timeSinceGrounded = 0;\n\t\t\tstate.lastGroundedY = entity.body.translation().y;\n\t\t} else {\n\t\t\tstate.timeSinceGrounded += delta;\n\t\t}\n\n\t\tif (!wasGrounded && state.grounded) {\n\t\t\tstate.jumpCount = 0;\n\t\t\tstate.jumping = false;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\t\t}\n\n\t\tif (velocity.y < -0.1 && !state.grounded) {\n\t\t\tif (state.jumping && velocity.y < 0) {\n\t\t\t\tstate.jumping = false;\n\t\t\t\tstate.falling = true;\n\t\t\t} else if (!state.jumping) {\n\t\t\t\tstate.falling = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update one platformer entity. Invoked per linked entity by the\n\t * Platformer3DBehaviorSystem adapter.\n\t */\n\tupdateEntity(entity: any, delta: number): void {\n\t\tif (!entity.platformer || !entity.$platformer || !entity.platformerState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst platformerEntity = entity as Platformer3DEntity;\n\t\tthis.ensurePhysicsConfigured(platformerEntity);\n\t\tthis.updateState(platformerEntity, delta);\n\t\tthis.applyMovement(platformerEntity);\n\t\tthis.handleJump(platformerEntity, delta);\n\t\tthis.applyGravity(platformerEntity, delta);\n\t}\n\n\tdestroy(): void {\n\t\tthis.gravityConfiguredUUIDs.clear();\n\t}\n}\n","/**\n * Platformer 3D Behavior Descriptor\n * \n * Type-safe descriptor for the platformer 3D behavior system.\n * Provides entity.use() API for 3D platformer movement.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer3DBehavior as Platformer3DMovementBehavior, Platformer3DEntity } from './platformer-3d.behavior';\nimport { Platformer3DFSM, Platformer3DState, PlatformerCollisionContext } from './platformer-3d-fsm';\nimport {\n\tcreatePlatformer3DMovementComponent,\n\tcreatePlatformer3DInputComponent,\n\tcreatePlatformer3DStateComponent,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Platformer behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer3DBehaviorOptions {\n\t/** Base walking speed (default: 12) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 24) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 9.82) */\n\tgravity?: number;\n\t/** Ray length for ground detection (default: 1.0) */\n\tgroundRayLength?: number;\n\t/** Enable debug visualization for ground probe rays (default: false) */\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tgroundRayLength: 1.0,\n\tdebugGroundProbe: false,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\n/**\n * Adapter that wraps Platformer3DBehavior as a BehaviorSystem\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: Platformer3DMovementBehavior;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate scene: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new Platformer3DMovementBehavior(world, scene);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer3DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer) {\n\t\t\t\tgameEntity.platformer = createPlatformer3DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer) {\n\t\t\t\tgameEntity.$platformer = createPlatformer3DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformerState) {\n\t\t\t\tgameEntity.platformerState = createPlatformer3DStateComponent();\n\t\t\t}\n\n\t\t\tif (!platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {\n\t\t\t\tplatformerRef.fsm = new Platformer3DFSM({\n\t\t\t\t\tinput: gameEntity.$platformer,\n\t\t\t\t\tstate: gameEntity.platformerState,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {\n\t\t\t\tplatformerRef.fsm.update(gameEntity.$platformer, gameEntity.platformerState);\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst input = gameEntity.$platformer;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.setPlatformer3DInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\t\tthis.wasmStage.setPlatformer3DInputButtons(handle, !!input.jump, !!input.run);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tthis.movementBehavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Platformer3DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachPlatformer3D(handle, {\n\t\t\thalfHeight: 0.9,\n\t\t\tradius: 0.4,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.movementBehavior.destroy();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer3DBehavior - typed descriptor for 3D platformer movement\n * \n * Provides complete 3D platformer physics including:\n * - Walking and running\n * - Jumping with multi-jump support\n * - Gravity and falling\n * - Ground detection\n * \n * @example\n * ```typescript\n * import { Platformer3DBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const player = await playgroundActor('player');\n * const platformer = player.use(Platformer3DBehavior, {\n * walkSpeed: 12,\n * runSpeed: 24,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n * \n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer.moveZ = inputs.p1.axes.Vertical.value;\n * player.$platformer.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer.run = inputs.p1.shoulders.LTrigger.held > 0;\n * \n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer3DBehavior = defineBehavior<\n\tPlatformer3DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer3DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t\tonPlatformCollision: (ctx: PlatformerCollisionContext) => void;\n\t},\n\tPlatformer3DEntity\n>({\n\tname: 'platformer-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer3DBehaviorSystem(\n\t\t\tctx.world,\n\t\t\tctx.scene,\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => ref.fsm?.getState() ?? Platformer3DState.Idle,\n\t\tisGrounded: () => ref.fsm?.isGrounded() ?? false,\n\t\tgetJumpCount: () => ref.fsm?.getJumpCount() ?? 0,\n\t\tonPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx),\n\t}),\n});\n"],"mappings":"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","names":["Platformer3DState","Platformer3DEvent","Platformer3DBehavior"]}
|