@zylem/behaviors 0.4.0 → 0.4.1
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/dist/{behavior-system-DSNek0W9.d.ts → behavior-system-y8SGrGWy.d.ts} +98 -1
- package/dist/{chunk-XLUJDKCT.js → chunk-27UPCP5B.js} +5 -5
- package/dist/{chunk-WXXXJ22R.js → chunk-4L36OPY2.js} +23 -11
- package/dist/chunk-4L36OPY2.js.map +1 -0
- package/dist/{chunk-FRJDQM23.js → chunk-67XLJ26X.js} +45 -15
- package/dist/{chunk-FRJDQM23.js.map → chunk-67XLJ26X.js.map} +1 -1
- package/dist/{chunk-MJFMNRMZ.js → chunk-75FJIUIG.js} +10 -10
- package/dist/{chunk-MJFMNRMZ.js.map → chunk-75FJIUIG.js.map} +1 -1
- package/dist/{chunk-64HKCSXD.js → chunk-A4CA35DQ.js} +3 -3
- package/dist/{chunk-64HKCSXD.js.map → chunk-A4CA35DQ.js.map} +1 -1
- package/dist/{chunk-GTLA6LOJ.js → chunk-F72WX4MH.js} +34 -12
- package/dist/chunk-F72WX4MH.js.map +1 -0
- package/dist/{chunk-4V7TIRO2.js → chunk-Q75LKHDF.js} +14 -2
- package/dist/chunk-Q75LKHDF.js.map +1 -0
- package/dist/{chunk-V74N7D6Q.js → chunk-QDCJLJUV.js} +5 -5
- package/dist/{chunk-RONH2KDI.js → chunk-RDHSHEZM.js} +37 -13
- package/dist/chunk-RDHSHEZM.js.map +1 -0
- package/dist/{chunk-RTRFJVCZ.js → chunk-UIG7MWS2.js} +70 -6
- package/dist/{chunk-RTRFJVCZ.js.map → chunk-UIG7MWS2.js.map} +1 -1
- package/dist/{chunk-PWF757B6.js → chunk-VMQHNJJI.js} +4 -4
- package/dist/{chunk-HNESASBB.js → chunk-Z72F2ENM.js} +4 -4
- package/dist/cooldown.d.ts +1 -1
- package/dist/coordinators/boundary-ricochet-3d.d.ts +2 -2
- package/dist/coordinators/boundary-ricochet.d.ts +2 -2
- package/dist/coordinators/multidirectional-space-shooter.d.ts +1 -1
- package/dist/coordinators/top-down-shooter.d.ts +1 -1
- package/dist/core.d.ts +4 -4
- package/dist/core.js +14 -8
- package/dist/core.js.map +1 -1
- package/dist/destructible-3d.d.ts +1 -1
- package/dist/first-person.d.ts +1 -1
- package/dist/first-person.js +2 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -12
- package/dist/jumper-2d.d.ts +1 -1
- package/dist/jumper-2d.js +2 -1
- package/dist/jumper-3d.d.ts +1 -1
- package/dist/jumper-3d.js +2 -1
- package/dist/{moveable-BLTHO7Jq.d.ts → moveable-Cl9h-_w7.d.ts} +1 -1
- package/dist/particle-emitter.d.ts +1 -1
- package/dist/platformer-2d.d.ts +1 -1
- package/dist/platformer-2d.js +2 -1
- package/dist/platformer-3d.d.ts +1 -1
- package/dist/platformer-3d.js +2 -1
- package/dist/ricochet-2d.d.ts +1 -1
- package/dist/ricochet-2d.js +2 -2
- package/dist/ricochet-3d.d.ts +1 -1
- package/dist/ricochet-3d.js +2 -2
- package/dist/runtime-2d.d.ts +1 -1
- package/dist/screen-visibility.d.ts +1 -1
- package/dist/screen-wrap.d.ts +1 -1
- package/dist/shooter-2d.d.ts +1 -1
- package/dist/thruster.d.ts +1 -1
- package/dist/thruster.js +1 -1
- package/dist/top-down-movement.d.ts +1 -1
- package/dist/top-down-movement.js +2 -2
- package/dist/{use-behavior-h9-T5FSv.d.ts → use-behavior-B85d5u7-.d.ts} +1 -1
- package/dist/world-boundary-2d.d.ts +1 -1
- package/dist/world-boundary-2d.js +2 -2
- package/dist/world-boundary-3d.d.ts +1 -1
- package/dist/world-boundary-3d.js +2 -2
- package/package.json +3 -1
- package/dist/chunk-4V7TIRO2.js.map +0 -1
- package/dist/chunk-GTLA6LOJ.js.map +0 -1
- package/dist/chunk-RONH2KDI.js.map +0 -1
- package/dist/chunk-WXXXJ22R.js.map +0 -1
- /package/dist/{chunk-XLUJDKCT.js.map → chunk-27UPCP5B.js.map} +0 -0
- /package/dist/{chunk-V74N7D6Q.js.map → chunk-QDCJLJUV.js.map} +0 -0
- /package/dist/{chunk-PWF757B6.js.map → chunk-VMQHNJJI.js.map} +0 -0
- /package/dist/{chunk-HNESASBB.js.map → chunk-Z72F2ENM.js.map} +0 -0
|
@@ -326,6 +326,10 @@ interface StageJumper3DOptions {
|
|
|
326
326
|
jumpForce: number;
|
|
327
327
|
maxJumps: number;
|
|
328
328
|
gravity: number;
|
|
329
|
+
/** Coyote time in seconds (default 0.1). */
|
|
330
|
+
coyoteTime?: number;
|
|
331
|
+
/** Jump buffer window in seconds (default 0.08). */
|
|
332
|
+
jumpBufferTime?: number;
|
|
329
333
|
}
|
|
330
334
|
interface StagePlatformer3DOptions {
|
|
331
335
|
halfHeight: number;
|
|
@@ -335,6 +339,55 @@ interface StagePlatformer3DOptions {
|
|
|
335
339
|
jumpForce: number;
|
|
336
340
|
maxJumps: number;
|
|
337
341
|
gravity: number;
|
|
342
|
+
/** Coyote time in seconds (default 0.1). */
|
|
343
|
+
coyoteTime?: number;
|
|
344
|
+
/** Jump buffer window in seconds (default 0.1). */
|
|
345
|
+
jumpBufferTime?: number;
|
|
346
|
+
/** Velocity multiplier applied on early jump release (default 0.5). */
|
|
347
|
+
jumpCutMultiplier?: number;
|
|
348
|
+
/** Seconds before a multi-jump becomes available (default 0.15). */
|
|
349
|
+
multiJumpWindow?: number;
|
|
350
|
+
/** Max slope angle in degrees the KCC can climb (default 50). */
|
|
351
|
+
slopeClimbAngle?: number;
|
|
352
|
+
/** Slope angle in degrees past which the character slides (default 40). */
|
|
353
|
+
slopeSlideAngle?: number;
|
|
354
|
+
/** Autostep max height in world units; 0 disables (default 0.3). */
|
|
355
|
+
autostepHeight?: number;
|
|
356
|
+
/** Minimum unobstructed width for an autostep (default 0.1). */
|
|
357
|
+
autostepMinWidth?: number;
|
|
358
|
+
/** Snap-to-ground distance in world units; 0 disables (default 0.3). */
|
|
359
|
+
snapToGroundDistance?: number;
|
|
360
|
+
/** KCC collision offset between collider and environment (default 0.05). */
|
|
361
|
+
characterOffset?: number;
|
|
362
|
+
}
|
|
363
|
+
interface StagePlatformer2DOptions {
|
|
364
|
+
halfHeight: number;
|
|
365
|
+
radius: number;
|
|
366
|
+
walkSpeed: number;
|
|
367
|
+
runSpeed: number;
|
|
368
|
+
jumpForce: number;
|
|
369
|
+
maxJumps: number;
|
|
370
|
+
gravity: number;
|
|
371
|
+
/** Coyote time in seconds (default 0.1). */
|
|
372
|
+
coyoteTime?: number;
|
|
373
|
+
/** Jump buffer window in seconds (default 0.1). */
|
|
374
|
+
jumpBufferTime?: number;
|
|
375
|
+
/** Velocity multiplier applied on early jump release (default 0.5). */
|
|
376
|
+
jumpCutMultiplier?: number;
|
|
377
|
+
/** Seconds before a multi-jump becomes available (default 0.15). */
|
|
378
|
+
multiJumpWindow?: number;
|
|
379
|
+
/** Max slope angle in degrees the KCC can climb (default 50). */
|
|
380
|
+
slopeClimbAngle?: number;
|
|
381
|
+
/** Slope angle in degrees past which the character slides (default 40). */
|
|
382
|
+
slopeSlideAngle?: number;
|
|
383
|
+
/** Autostep max height in world units; 0 disables (default 0.25). */
|
|
384
|
+
autostepHeight?: number;
|
|
385
|
+
/** Minimum unobstructed width for an autostep (default 0.05). */
|
|
386
|
+
autostepMinWidth?: number;
|
|
387
|
+
/** Snap-to-ground distance in world units; 0 disables (default 0.2). */
|
|
388
|
+
snapToGroundDistance?: number;
|
|
389
|
+
/** KCC collision offset between collider and environment (default 0.05). */
|
|
390
|
+
characterOffset?: number;
|
|
338
391
|
}
|
|
339
392
|
interface StageFirstPersonOptions {
|
|
340
393
|
walkSpeed: number;
|
|
@@ -349,6 +402,42 @@ interface StageFirstPersonInput {
|
|
|
349
402
|
lookPitchDelta: number;
|
|
350
403
|
run: boolean;
|
|
351
404
|
}
|
|
405
|
+
/** Button bits for the batched input buffer's bitmask field. */
|
|
406
|
+
declare const STAGE_INPUT_BUTTON_JUMP = 1;
|
|
407
|
+
declare const STAGE_INPUT_BUTTON_RUN = 2;
|
|
408
|
+
/**
|
|
409
|
+
* Decoded per-slot behavior snapshot written by the runtime after every
|
|
410
|
+
* step: movement fields come from whichever character behavior the entity
|
|
411
|
+
* has (platformer 3D/2D, jumper 3D/2D); first-person fills yaw/pitch.
|
|
412
|
+
* Values reflect the end of the previous `step()` (one-frame read lag).
|
|
413
|
+
*/
|
|
414
|
+
interface StageBehaviorSnapshot {
|
|
415
|
+
grounded: boolean;
|
|
416
|
+
fsmState: number;
|
|
417
|
+
jumpCount: number;
|
|
418
|
+
verticalVelocity: number;
|
|
419
|
+
speed: number;
|
|
420
|
+
yaw: number;
|
|
421
|
+
pitch: number;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Wasm fast-path backrefs a behavior system stores on its `BehaviorRef` every
|
|
425
|
+
* frame so descriptor handles can read the runtime snapshot buffer instead of
|
|
426
|
+
* a duplicate TS FSM (or hardcoded defaults).
|
|
427
|
+
*/
|
|
428
|
+
interface WasmSnapshotBackref {
|
|
429
|
+
__wasmStage?: WasmStageRuntime | null;
|
|
430
|
+
__entity?: {
|
|
431
|
+
runtimeHandle?: number;
|
|
432
|
+
} | null;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Read the behavior snapshot for the entity a `BehaviorRef` is linked to.
|
|
436
|
+
* Returns `null` off the wasm fast path (no stage runtime / no
|
|
437
|
+
* `runtimeHandle` / no snapshot yet), in which case handles should fall back
|
|
438
|
+
* to their TS FSM.
|
|
439
|
+
*/
|
|
440
|
+
declare function readBehaviorSnapshot(ref: object): StageBehaviorSnapshot | null;
|
|
352
441
|
/**
|
|
353
442
|
* Typed FFI surface of the wasm unified Stage runtime that behaviors rely on.
|
|
354
443
|
*
|
|
@@ -358,6 +447,11 @@ interface StageFirstPersonInput {
|
|
|
358
447
|
*/
|
|
359
448
|
interface WasmStageRuntime {
|
|
360
449
|
getPose(handle: number, out?: StagePose): StagePose | null;
|
|
450
|
+
writeInputAxes(handle: number, axisA: number, axisB: number): boolean;
|
|
451
|
+
writeInputLook(handle: number, yawDelta: number, pitchDelta: number): boolean;
|
|
452
|
+
writeInputButton(handle: number, buttonMask: number, pressed: boolean): boolean;
|
|
453
|
+
/** Read the per-slot behavior snapshot written at the end of the previous step. */
|
|
454
|
+
readSnapshot(handle: number, out?: StageBehaviorSnapshot): StageBehaviorSnapshot | null;
|
|
361
455
|
attachThruster(handle: number, opts: StageThrusterOptions): boolean;
|
|
362
456
|
setThrusterInput(handle: number, thrust: number, rotate: number): void;
|
|
363
457
|
attachTopDown(handle: number, opts: StageTopDownOptions): boolean;
|
|
@@ -377,6 +471,9 @@ interface WasmStageRuntime {
|
|
|
377
471
|
attachPlatformer3D(handle: number, opts: StagePlatformer3DOptions): boolean;
|
|
378
472
|
setPlatformer3DInputAxes(handle: number, moveX: number, moveZ: number): void;
|
|
379
473
|
setPlatformer3DInputButtons(handle: number, jump: boolean, run: boolean): void;
|
|
474
|
+
attachPlatformer2D(handle: number, opts: StagePlatformer2DOptions): boolean;
|
|
475
|
+
setPlatformer2DInputAxes(handle: number, moveX: number): void;
|
|
476
|
+
setPlatformer2DInputButtons(handle: number, jump: boolean, run: boolean): void;
|
|
380
477
|
attachFirstPerson(handle: number, opts: StageFirstPersonOptions): boolean;
|
|
381
478
|
setFirstPersonInput(handle: number, input: StageFirstPersonInput): void;
|
|
382
479
|
attachJumper2D(handle: number, opts: StageJumper2DOptions): boolean;
|
|
@@ -480,4 +577,4 @@ interface BehaviorSystemContext {
|
|
|
480
577
|
*/
|
|
481
578
|
type BehaviorSystemFactory<T extends BehaviorSystem = BehaviorSystem> = (stage: BehaviorSystemContext) => T;
|
|
482
579
|
|
|
483
|
-
export {
|
|
580
|
+
export { type StageScreenWrapOptions as A, type BaseBehaviorHandle as B, type CreateEntityFn as C, type DefineBehaviorConfig as D, type StageShooter2DOptions as E, type StageThrusterOptions as F, GameEntity as G, type StageTopDownOptions as H, StageTopDownPlane as I, type StageTopDownPlaneValue as J, type StageWorldBoundaryOptions as K, type VelocityIntentMode as L, type WasmStageRuntime as M, type WasmStageRuntimeOptions as N, createTransformStore as O, defineBehavior as P, readBehaviorSnapshot as Q, STAGE_INPUT_BUTTON_JUMP as S, type TransformState as T, type VelocityIntent as V, type WasmSnapshotBackref as W, type ZylemWorld as Z, type BehaviorDescriptor as a, type BehaviorEntityLink as b, type BehaviorHandle as c, type BehaviorRef as d, type BehaviorSystem as e, type BehaviorSystemContext as f, type BehaviorSystemFactory as g, type GameEntityOptions as h, STAGE_INPUT_BUTTON_RUN as i, type StageBehaviorSnapshot as j, StageBodyKind as k, StageBoundaryDim as l, type StageBoundaryDimValue as m, StageEventType as n, type StageFirstPersonInput as o, type StageFirstPersonOptions as p, type StageJumper2DOptions as q, type StageJumper3DOptions as r, type StagePlatformer2DOptions as s, type StagePlatformer3DOptions as t, type StagePose as u, StageRicochetDim as v, type StageRicochetDimValue as w, type StageRicochetOptions as x, StageRicochetReflection as y, type StageRicochetReflectionValue as z };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
StageRicochetDim,
|
|
3
|
-
StageRicochetReflection
|
|
4
|
-
} from "./chunk-4V7TIRO2.js";
|
|
5
1
|
import {
|
|
6
2
|
SyncStateMachine,
|
|
7
3
|
t
|
|
8
4
|
} from "./chunk-4URF65BR.js";
|
|
5
|
+
import {
|
|
6
|
+
StageRicochetDim,
|
|
7
|
+
StageRicochetReflection
|
|
8
|
+
} from "./chunk-Q75LKHDF.js";
|
|
9
9
|
import {
|
|
10
10
|
defineBehavior
|
|
11
11
|
} from "./chunk-EZNCCO4L.js";
|
|
@@ -428,4 +428,4 @@ export {
|
|
|
428
428
|
Ricochet3DFSM,
|
|
429
429
|
Ricochet3DBehavior
|
|
430
430
|
};
|
|
431
|
-
//# sourceMappingURL=chunk-
|
|
431
|
+
//# sourceMappingURL=chunk-27UPCP5B.js.map
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
SyncStateMachine,
|
|
15
15
|
t
|
|
16
16
|
} from "./chunk-4URF65BR.js";
|
|
17
|
+
import {
|
|
18
|
+
STAGE_INPUT_BUTTON_RUN,
|
|
19
|
+
readBehaviorSnapshot
|
|
20
|
+
} from "./chunk-Q75LKHDF.js";
|
|
17
21
|
import {
|
|
18
22
|
defineBehavior
|
|
19
23
|
} from "./chunk-EZNCCO4L.js";
|
|
@@ -263,15 +267,13 @@ var FirstPersonBehaviorSystem = class {
|
|
|
263
267
|
const handle = gameEntity.runtimeHandle ?? -1;
|
|
264
268
|
if (this.wasmStage && handle >= 0) {
|
|
265
269
|
this.ensureWasmAttached(handle, options);
|
|
270
|
+
fpsRef.__wasmStage = this.wasmStage;
|
|
271
|
+
fpsRef.__entity = gameEntity;
|
|
266
272
|
const input = gameEntity.$fps;
|
|
267
273
|
if (input) {
|
|
268
|
-
this.wasmStage.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
lookYawDelta: input.lookX ?? 0,
|
|
272
|
-
lookPitchDelta: input.lookY ?? 0,
|
|
273
|
-
run: !!input.sprint
|
|
274
|
-
});
|
|
274
|
+
this.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);
|
|
275
|
+
this.wasmStage.writeInputLook(handle, input.lookX ?? 0, input.lookY ?? 0);
|
|
276
|
+
this.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.sprint);
|
|
275
277
|
}
|
|
276
278
|
continue;
|
|
277
279
|
}
|
|
@@ -324,10 +326,20 @@ var FirstPersonBehavior = defineBehavior({
|
|
|
324
326
|
name: "first-person-controller",
|
|
325
327
|
defaultOptions,
|
|
326
328
|
systemFactory: (ctx) => new FirstPersonBehaviorSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),
|
|
329
|
+
// On the wasm fast path the handle reads the runtime snapshot buffer
|
|
330
|
+
// (previous-step values); the TS FSM only backs the no-wasm fallback.
|
|
327
331
|
createHandle: (ref) => ({
|
|
328
|
-
getState: () =>
|
|
329
|
-
|
|
330
|
-
|
|
332
|
+
getState: () => {
|
|
333
|
+
const snapshot = readBehaviorSnapshot(ref);
|
|
334
|
+
if (snapshot) {
|
|
335
|
+
if (snapshot.speed <= 0.01) return "idle" /* Idle */;
|
|
336
|
+
const walkSpeed = ref.options.walkSpeed ?? defaultOptions.walkSpeed;
|
|
337
|
+
return snapshot.speed > walkSpeed ? "running" /* Running */ : "walking" /* Walking */;
|
|
338
|
+
}
|
|
339
|
+
return ref.fsm?.getState() ?? "idle" /* Idle */;
|
|
340
|
+
},
|
|
341
|
+
getYaw: () => readBehaviorSnapshot(ref)?.yaw ?? ref.fsm?.getYaw() ?? 0,
|
|
342
|
+
getPitch: () => readBehaviorSnapshot(ref)?.pitch ?? ref.fsm?.getPitch() ?? 0,
|
|
331
343
|
attachViewmodel: (entity, offset) => {
|
|
332
344
|
if (ref.options) {
|
|
333
345
|
ref.options.viewmodel = { entity, offset };
|
|
@@ -343,4 +355,4 @@ export {
|
|
|
343
355
|
FirstPersonSystem,
|
|
344
356
|
FirstPersonBehavior
|
|
345
357
|
};
|
|
346
|
-
//# sourceMappingURL=chunk-
|
|
358
|
+
//# sourceMappingURL=chunk-4L36OPY2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/first-person/first-person-fsm.ts","../src/lib/behaviors/first-person/first-person.system.ts","../src/lib/behaviors/first-person/first-person.descriptor.ts"],"sourcesContent":["/**\n * First Person Controller Finite State Machine\n *\n * Manages state transitions for first-person movement:\n * - Idle: Standing still\n * - Walking: Moving at walk speed\n * - Running: Moving at run/sprint speed\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { FirstPersonInputComponent, FirstPersonStateComponent } from './components';\n\n/**\n * First-person movement states\n */\nexport enum FirstPersonState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum FirstPersonEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface FirstPersonContext {\n\tinput: FirstPersonInputComponent;\n\tstate: FirstPersonStateComponent;\n}\n\n/**\n * First Person Controller FSM\n */\nexport class FirstPersonFSM {\n\tmachine: SyncStateMachine<FirstPersonState, FirstPersonEvent, never>;\n\n\tconstructor(private ctx: FirstPersonContext) {\n\t\tthis.machine = new SyncStateMachine<FirstPersonState, FirstPersonEvent, never>(\n\t\t\tFirstPersonState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t],\n\t\t);\n\t}\n\n\t/** Get the current state */\n\tgetState(): FirstPersonState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/** Dispatch an event to the FSM */\n\tdispatch(event: FirstPersonEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/** Get the current yaw from context */\n\tgetYaw(): number {\n\t\treturn this.ctx.state.yaw;\n\t}\n\n\t/** Get the current pitch from context */\n\tgetPitch(): number {\n\t\treturn this.ctx.state.pitch;\n\t}\n\n\t/**\n\t * Update FSM based on current input and state.\n\t */\n\tupdate(input: FirstPersonInputComponent, state: FirstPersonStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst hasMovement = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\n\t\tif (hasMovement) {\n\t\t\tif (input.sprint) {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Run);\n\t\t\t} else {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Walk);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.dispatch(FirstPersonEvent.Stop);\n\t\t}\n\t}\n}\n","/**\n * First Person Controller Behavior System\n *\n * Handles first-person camera control and movement:\n * - Reads input component for WASD movement and mouse look deltas\n * - Drives a FirstPersonPerspective (yaw/pitch accumulation)\n * - Writes velocity to transformStore (physics-based movement)\n * - Syncs camera position from the entity's rigid body each frame\n * - Positions an optional viewmodel entity relative to the camera\n */\n\nimport { Vector3, Quaternion, Euler } from 'three';\nimport type { RigidBody } from '@dimforge/rapier3d-compat';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport type { ZylemWorld } from '../../collision/world';\nimport type { TransformState } from '../../capabilities/transform-store';\nimport { setVelocityIntent } from '../../capabilities/velocity-intents';\nimport type {\n\tFirstPersonMovementComponent,\n\tFirstPersonInputComponent,\n\tFirstPersonStateComponent,\n} from './components';\n\n/**\n * Viewmodel configuration for an entity that follows the camera.\n */\nexport interface ViewmodelConfig {\n\t/** The entity whose visual group should be positioned relative to the camera. */\n\tentity: any;\n\t/** Offset from the eye position in camera-local space (right, down, forward). */\n\toffset: Vector3;\n\t/** Additional rotation applied to the viewmodel in local space (Euler angles in radians). */\n\trotation?: Euler;\n}\n\n/**\n * Entity interface for first-person controller.\n * Entities with this behavior have these components attached.\n */\nexport interface FirstPersonEntity {\n\tuuid: string;\n\tbody: RigidBody | null;\n\ttransformStore: TransformState;\n\tfirstPerson: FirstPersonMovementComponent;\n\t$fps: FirstPersonInputComponent;\n\tfirstPersonState: FirstPersonStateComponent;\n}\n\n// Reusable temporaries (avoid per-frame allocations)\nconst _forward = new Vector3();\nconst _right = new Vector3();\nconst _up = new Vector3(0, 1, 0);\nconst _viewmodelQuat = new Quaternion();\nconst _viewmodelLocalQuat = new Quaternion();\nconst _viewmodelEuler = new Euler();\nconst _offset = new Vector3();\n\n/**\n * First Person Controller Behavior\n *\n * Core movement + look system for first-person cameras.\n * Reads from entity.$fps input component and drives a FirstPersonPerspective.\n */\nexport class FirstPersonSystem {\n\tprivate world: ZylemWorld | null;\n\tprivate perspectives = new Map<string, FirstPersonPerspective>();\n\tprivate viewmodels = new Map<string, ViewmodelConfig>();\n\n\tconstructor(world: ZylemWorld | null) {\n\t\tthis.world = world;\n\t}\n\n\t/**\n\t * Associate a FirstPersonPerspective with an entity (by uuid).\n\t * Called once by the descriptor system when the behavior is initialized.\n\t */\n\tsetPerspective(entityUuid: string, perspective: FirstPersonPerspective): void {\n\t\tthis.perspectives.set(entityUuid, perspective);\n\t}\n\n\thasPerspective(entityUuid: string): boolean {\n\t\treturn this.perspectives.has(entityUuid);\n\t}\n\n\t/**\n\t * Attach a viewmodel (weapon/item) to an entity.\n\t * The viewmodel entity's group will be positioned relative to the camera each frame.\n\t */\n\tsetViewmodel(entityUuid: string, config: ViewmodelConfig): void {\n\t\tthis.viewmodels.set(entityUuid, config);\n\t}\n\n\thasViewmodel(entityUuid: string): boolean {\n\t\treturn this.viewmodels.has(entityUuid);\n\t}\n\n\t/**\n\t * Update one first-person entity.\n\t */\n\tupdateEntity(entity: any, _delta: number): void {\n\t\tconst fpEntity = entity as any;\n\t\tif (!fpEntity.firstPerson || !fpEntity.$fps || !fpEntity.firstPersonState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst input: FirstPersonInputComponent = fpEntity.$fps;\n\t\tconst movement: FirstPersonMovementComponent = fpEntity.firstPerson;\n\t\tconst state: FirstPersonStateComponent = fpEntity.firstPersonState;\n\t\tconst perspective = this.perspectives.get(fpEntity.uuid);\n\n\t\t// 1. Sync camera position from physics body\n\t\tif (perspective?.initialPosition && fpEntity.body) {\n\t\t\tconst pos = fpEntity.body.translation();\n\t\t\tperspective.initialPosition.set(\n\t\t\t\tpos.x,\n\t\t\t\tpos.y + movement.eyeHeight,\n\t\t\t\tpos.z,\n\t\t\t);\n\t\t}\n\n\t\t// 2. Apply look\n\t\tif (perspective) {\n\t\t\tperspective.look(\n\t\t\t\t-input.lookX * movement.lookSensitivity,\n\t\t\t\t-input.lookY * movement.lookSensitivity,\n\t\t\t);\n\n\t\t\tstate.yaw = perspective.yaw;\n\t\t\tstate.pitch = perspective.pitch;\n\t\t}\n\n\t\t// 3. Compute velocity-based movement and write to transformStore\n\t\tconst speed = input.sprint ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\n\t\tconst hasMovement = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\n\t\tif (fpEntity.transformStore) {\n\t\t\tif (hasMovement) {\n\t\t\t\t_forward.set(0, 0, -1).applyAxisAngle(_up, state.yaw);\n\t\t\t\t_right.set(1, 0, 0).applyAxisAngle(_up, state.yaw);\n\n\t\t\t\tconst vx =\n\t\t\t\t\t_right.x * input.moveX * speed +\n\t\t\t\t\t_forward.x * (-input.moveZ) * speed;\n\t\t\t\tconst vz =\n\t\t\t\t\t_right.z * input.moveX * speed +\n\t\t\t\t\t_forward.z * (-input.moveZ) * speed;\n\t\t\t\tsetVelocityIntent(\n\t\t\t\t\tfpEntity.transformStore,\n\t\t\t\t\t'first-person',\n\t\t\t\t\t{ x: vx, z: vz },\n\t\t\t\t\t{ mode: 'replace', priority: 10 },\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tsetVelocityIntent(\n\t\t\t\t\tfpEntity.transformStore,\n\t\t\t\t\t'first-person',\n\t\t\t\t\t{ x: 0, z: 0 },\n\t\t\t\t\t{ mode: 'replace', priority: 10 },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// 4. Position viewmodel\n\t\tconst vm = this.viewmodels.get(fpEntity.uuid);\n\t\tif (vm && perspective?.initialPosition) {\n\t\t\tthis.positionViewmodel(vm, perspective, state);\n\t\t}\n\t}\n\n\t/**\n\t * Update all first-person entities.\n\t */\n\tupdate(delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tthis.updateEntity(entity, delta);\n\t\t}\n\t}\n\n\t/**\n\t * Position a viewmodel entity relative to the camera's eye position and rotation.\n\t *\n\t * The physics transform system overwrites group.position from the rigid body\n\t * each frame, so we must also move the body to keep them in sync.\n\t */\n\tprivate positionViewmodel(\n\t\tvm: ViewmodelConfig,\n\t\tperspective: FirstPersonPerspective,\n\t\tstate: FirstPersonStateComponent,\n\t): void {\n\t\tconst eyePos = perspective.initialPosition;\n\t\tif (!eyePos) return;\n\n\t\t// Build camera rotation quaternion\n\t\t_viewmodelQuat.setFromEuler(\n\t\t\t_viewmodelEuler.set(state.pitch, state.yaw, 0, 'YXZ'),\n\t\t);\n\n\t\t// Compose with the viewmodel's local rotation (model orientation fix)\n\t\tif (vm.rotation) {\n\t\t\t_viewmodelLocalQuat.setFromEuler(vm.rotation);\n\t\t\t_viewmodelQuat.multiply(_viewmodelLocalQuat);\n\t\t}\n\n\t\t// Transform the local offset into world space (use camera rotation, not the composed one)\n\t\t_offset.copy(vm.offset).applyQuaternion(_viewmodelQuat);\n\n\t\tconst wx = eyePos.x + _offset.x;\n\t\tconst wy = eyePos.y + _offset.y;\n\t\tconst wz = eyePos.z + _offset.z;\n\n\t\tconst entity = vm.entity;\n\n\t\t// Move the physics body so the transform system stays in sync\n\t\tif (entity?.body) {\n\t\t\tentity.body.setTranslation({ x: wx, y: wy, z: wz }, true);\n\t\t\tentity.body.setRotation(\n\t\t\t\t{ x: _viewmodelQuat.x, y: _viewmodelQuat.y, z: _viewmodelQuat.z, w: _viewmodelQuat.w },\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\n\t\t// Also set the group directly for immediate visual update\n\t\tif (entity?.group) {\n\t\t\tentity.group.position.set(wx, wy, wz);\n\t\t\tentity.group.quaternion.copy(_viewmodelQuat);\n\t\t}\n\t}\n\n\t/** Cleanup */\n\tdestroy(): void {\n\t\tthis.perspectives.clear();\n\t\tthis.viewmodels.clear();\n\t}\n}\n","/**\n * First Person Controller Behavior Descriptor\n *\n * Type-safe descriptor for the first-person controller behavior system.\n * Provides entity.use() API for FPS movement and camera control.\n */\n\nimport { Vector3, Euler } from 'three';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport { Vec3Input, VEC3_ZERO, toThreeVector3 } from '../../core/vector';\nimport {\n\tFirstPersonSystem,\n\ttype FirstPersonEntity,\n\ttype ViewmodelConfig,\n} from './first-person.system';\nimport { FirstPersonFSM, FirstPersonState } from './first-person-fsm';\nimport {\n\tcreateFirstPersonMovementComponent,\n\tcreateFirstPersonInputComponent,\n\tcreateFirstPersonStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\n/**\n * First-person controller behavior options (typed for entity.use() autocomplete).\n */\nexport interface FirstPersonBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 16) */\n\trunSpeed?: number;\n\t/** Look sensitivity multiplier (default: 2) */\n\tlookSensitivity?: number;\n\t/** Eye height offset above entity position (default: 1.7) */\n\teyeHeight?: number;\n\t/** The FirstPersonPerspective to drive (from camera.getPerspective()) */\n\tperspective?: FirstPersonPerspective;\n\t/** Optional viewmodel (weapon/item) to position relative to the camera */\n\tviewmodel?: { entity: any; offset: Vec3Input; rotation?: Euler };\n}\n\nconst defaultOptions: FirstPersonBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 16,\n\tlookSensitivity: 2,\n\teyeHeight: 1.7,\n};\n\nconst FIRST_PERSON_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:first-person-controller',\n);\n\n/**\n * Adapter that wraps FirstPersonSystem as a BehaviorSystem.\n */\nclass FirstPersonBehaviorSystem implements BehaviorSystem {\n\tprivate behavior: FirstPersonSystem;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tworld: import('../../collision/world').ZylemWorld | null,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.behavior = new FirstPersonSystem(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(FIRST_PERSON_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 fpsRef = link.ref as any;\n\n\t\t\tconst options = fpsRef.options as FirstPersonBehaviorOptions;\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\tfpsRef.__wasmStage = this.wasmStage;\n\t\t\t\tfpsRef.__entity = gameEntity;\n\t\t\t\tconst input = gameEntity.$fps;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\t\tthis.wasmStage.writeInputLook(handle, input.lookX ?? 0, input.lookY ?? 0);\n\t\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.sprint);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Initialize components (once)\n\t\t\tif (!gameEntity.firstPerson) {\n\t\t\t\tgameEntity.firstPerson = createFirstPersonMovementComponent(options);\n\t\t\t}\n\n\t\t\tif (!gameEntity.$fps) {\n\t\t\t\tgameEntity.$fps = createFirstPersonInputComponent();\n\t\t\t}\n\n\t\t\tif (!gameEntity.firstPersonState) {\n\t\t\t\tgameEntity.firstPersonState = createFirstPersonStateComponent();\n\t\t\t}\n\n\t\t\t// Wire up perspective (once)\n\t\t\tif (options.perspective && !this.behavior.hasPerspective(gameEntity.uuid)) {\n\t\t\t\tthis.behavior.setPerspective(gameEntity.uuid, options.perspective);\n\t\t\t}\n\n\t\t\t// Wire up viewmodel (once)\n\t\t\tif (options.viewmodel && !this.behavior.hasViewmodel(gameEntity.uuid)) {\n\t\t\t\tthis.behavior.setViewmodel(gameEntity.uuid, {\n\t\t\t\t\t...options.viewmodel,\n\t\t\t\t\toffset: toThreeVector3(options.viewmodel.offset, VEC3_ZERO),\n\t\t\t\t} as ViewmodelConfig);\n\t\t\t}\n\n\t\t\t// Create FSM lazily\n\t\t\tif (!fpsRef.fsm && gameEntity.$fps && gameEntity.firstPersonState) {\n\t\t\t\tfpsRef.fsm = new FirstPersonFSM({\n\t\t\t\t\tinput: gameEntity.$fps,\n\t\t\t\t\tstate: gameEntity.firstPersonState,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Update FSM\n\t\t\tif (fpsRef.fsm && gameEntity.$fps && gameEntity.firstPersonState) {\n\t\t\t\tfpsRef.fsm.update(gameEntity.$fps, gameEntity.firstPersonState);\n\t\t\t}\n\n\t\t\tthis.behavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: FirstPersonBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachFirstPerson(handle, {\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\teyeHeight: options.eyeHeight ?? defaultOptions.eyeHeight!,\n\t\t\tlookSensitivity: options.lookSensitivity ?? defaultOptions.lookSensitivity!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.behavior.destroy();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * FirstPersonBehavior - typed descriptor for first-person movement and camera control.\n *\n * Provides complete first-person controller including:\n * - WASD movement relative to camera yaw\n * - Mouse look driving a FirstPersonPerspective\n * - Optional viewmodel positioning (weapon/item following camera)\n * - Walk/run state tracking via FSM\n *\n * @example\n * ```typescript\n * import { FirstPersonBehavior } from \"@zylem/game-lib/behavior\";\n * import { createCamera, Perspectives, FirstPersonPerspective } from \"@zylem/game-lib/core\";\n * import { createActor } from \"@zylem/game-lib/entity\";\n *\n * const fpsCamera = createCamera({ perspective: Perspectives.FirstPerson, ... });\n * const fps = fpsCamera.getPerspective<FirstPersonPerspective>();\n *\n * const player = createActor({ name: 'player', ... });\n * const controller = player.use(FirstPersonBehavior, {\n * perspective: fps,\n * walkSpeed: 8,\n * runSpeed: 16,\n * lookSensitivity: 2,\n * });\n *\n * // In update loop - write input, behavior handles the rest\n * player.onUpdate(({ inputs }) => {\n * player.$fps.moveX = inputs.p1.axes.Horizontal.value;\n * player.$fps.moveZ = inputs.p1.axes.Vertical.value;\n * player.$fps.lookX = inputs.p1.axes.SecondaryHorizontal.value;\n * player.$fps.lookY = inputs.p1.axes.SecondaryVertical.value;\n * player.$fps.sprint = inputs.p1.shoulders.LTrigger.held > 0;\n * });\n * ```\n */\nexport const FirstPersonBehavior = defineBehavior<\n\tFirstPersonBehaviorOptions,\n\t{\n\t\tgetState: () => FirstPersonState;\n\t\tgetYaw: () => number;\n\t\tgetPitch: () => number;\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => void;\n\t},\n\tFirstPersonEntity\n>({\n\tname: 'first-person-controller',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew FirstPersonBehaviorSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\t// On the wasm fast path the handle reads the runtime snapshot buffer\n\t// (previous-step values); the TS FSM only backs the no-wasm fallback.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) {\n\t\t\t\tif (snapshot.speed <= 0.01) return FirstPersonState.Idle;\n\t\t\t\tconst walkSpeed = ref.options.walkSpeed ?? defaultOptions.walkSpeed!;\n\t\t\t\treturn snapshot.speed > walkSpeed\n\t\t\t\t\t? FirstPersonState.Running\n\t\t\t\t\t: FirstPersonState.Walking;\n\t\t\t}\n\t\t\treturn ref.fsm?.getState() ?? FirstPersonState.Idle;\n\t\t},\n\t\tgetYaw: () => readBehaviorSnapshot(ref)?.yaw ?? ref.fsm?.getYaw() ?? 0,\n\t\tgetPitch: () => readBehaviorSnapshot(ref)?.pitch ?? ref.fsm?.getPitch() ?? 0,\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => {\n\t\t\tif (ref.options) {\n\t\t\t\tref.options.viewmodel = { entity, offset };\n\t\t\t}\n\t\t},\n\t}),\n});\n"],"mappings":"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","names":["FirstPersonState","FirstPersonEvent"]}
|
|
@@ -2,6 +2,11 @@ import {
|
|
|
2
2
|
SyncStateMachine,
|
|
3
3
|
t
|
|
4
4
|
} from "./chunk-4URF65BR.js";
|
|
5
|
+
import {
|
|
6
|
+
STAGE_INPUT_BUTTON_JUMP,
|
|
7
|
+
STAGE_INPUT_BUTTON_RUN,
|
|
8
|
+
readBehaviorSnapshot
|
|
9
|
+
} from "./chunk-Q75LKHDF.js";
|
|
5
10
|
import {
|
|
6
11
|
defineBehavior
|
|
7
12
|
} from "./chunk-EZNCCO4L.js";
|
|
@@ -397,6 +402,14 @@ var Platformer3DSystem = class {
|
|
|
397
402
|
};
|
|
398
403
|
|
|
399
404
|
// src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts
|
|
405
|
+
var FSM_STATE_BY_INDEX = [
|
|
406
|
+
"idle" /* Idle */,
|
|
407
|
+
"walking" /* Walking */,
|
|
408
|
+
"running" /* Running */,
|
|
409
|
+
"jumping" /* Jumping */,
|
|
410
|
+
"falling" /* Falling */,
|
|
411
|
+
"landing" /* Landing */
|
|
412
|
+
];
|
|
400
413
|
var defaultOptions = {
|
|
401
414
|
walkSpeed: 12,
|
|
402
415
|
runSpeed: 24,
|
|
@@ -436,6 +449,19 @@ var Platformer3DBehaviorSystem = class {
|
|
|
436
449
|
if (!gameEntity.platformerState) {
|
|
437
450
|
gameEntity.platformerState = createPlatformer3DStateComponent();
|
|
438
451
|
}
|
|
452
|
+
const handle = gameEntity.runtimeHandle ?? -1;
|
|
453
|
+
if (this.wasmStage && handle >= 0) {
|
|
454
|
+
this.ensureWasmAttached(handle, options);
|
|
455
|
+
platformerRef.__wasmStage = this.wasmStage;
|
|
456
|
+
platformerRef.__entity = gameEntity;
|
|
457
|
+
const input = gameEntity.$platformer;
|
|
458
|
+
if (input) {
|
|
459
|
+
this.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);
|
|
460
|
+
this.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);
|
|
461
|
+
this.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);
|
|
462
|
+
}
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
439
465
|
if (!platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
440
466
|
platformerRef.fsm = new Platformer3DFSM({
|
|
441
467
|
input: gameEntity.$platformer,
|
|
@@ -445,22 +471,13 @@ var Platformer3DBehaviorSystem = class {
|
|
|
445
471
|
if (platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
446
472
|
platformerRef.fsm.update(gameEntity.$platformer, gameEntity.platformerState);
|
|
447
473
|
}
|
|
448
|
-
const handle = gameEntity.runtimeHandle ?? -1;
|
|
449
|
-
if (this.wasmStage && handle >= 0) {
|
|
450
|
-
this.ensureWasmAttached(handle, options);
|
|
451
|
-
const input = gameEntity.$platformer;
|
|
452
|
-
if (input) {
|
|
453
|
-
this.wasmStage.setPlatformer3DInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);
|
|
454
|
-
this.wasmStage.setPlatformer3DInputButtons(handle, !!input.jump, !!input.run);
|
|
455
|
-
}
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
474
|
if (!gameEntity.body) continue;
|
|
459
475
|
this.movementBehavior.updateEntity(gameEntity, delta);
|
|
460
476
|
}
|
|
461
477
|
}
|
|
462
478
|
ensureWasmAttached(handle, options) {
|
|
463
479
|
if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
|
|
480
|
+
const autostep = options.autostep ?? defaultOptions.autostep;
|
|
464
481
|
this.wasmStage.attachPlatformer3D(handle, {
|
|
465
482
|
halfHeight: 0.9,
|
|
466
483
|
radius: 0.4,
|
|
@@ -468,7 +485,13 @@ var Platformer3DBehaviorSystem = class {
|
|
|
468
485
|
runSpeed: options.runSpeed ?? defaultOptions.runSpeed,
|
|
469
486
|
jumpForce: options.jumpForce ?? defaultOptions.jumpForce,
|
|
470
487
|
maxJumps: options.maxJumps ?? defaultOptions.maxJumps,
|
|
471
|
-
gravity: options.gravity ?? defaultOptions.gravity
|
|
488
|
+
gravity: options.gravity ?? defaultOptions.gravity,
|
|
489
|
+
slopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle,
|
|
490
|
+
slopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle,
|
|
491
|
+
autostepHeight: autostep ? autostep.maxHeight : 0,
|
|
492
|
+
autostepMinWidth: autostep ? autostep.minWidth : 0.1,
|
|
493
|
+
snapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance,
|
|
494
|
+
characterOffset: options.characterOffset ?? defaultOptions.characterOffset
|
|
472
495
|
});
|
|
473
496
|
this.attachedRuntimeSlots.add(handle);
|
|
474
497
|
}
|
|
@@ -485,10 +508,17 @@ var Platformer3DBehavior = defineBehavior({
|
|
|
485
508
|
ctx.wasmStage ?? null,
|
|
486
509
|
ctx.getBehaviorLinks
|
|
487
510
|
),
|
|
511
|
+
// On the wasm fast path the handle reads the runtime snapshot buffer
|
|
512
|
+
// (values are from the end of the previous step — one-frame lag); the TS
|
|
513
|
+
// FSM only backs the no-wasm fallback path.
|
|
488
514
|
createHandle: (ref) => ({
|
|
489
|
-
getState: () =>
|
|
490
|
-
|
|
491
|
-
|
|
515
|
+
getState: () => {
|
|
516
|
+
const snapshot = readBehaviorSnapshot(ref);
|
|
517
|
+
if (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? "idle" /* Idle */;
|
|
518
|
+
return ref.fsm?.getState() ?? "idle" /* Idle */;
|
|
519
|
+
},
|
|
520
|
+
isGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? ref.fsm?.isGrounded() ?? false,
|
|
521
|
+
getJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? ref.fsm?.getJumpCount() ?? 0,
|
|
492
522
|
onPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx)
|
|
493
523
|
})
|
|
494
524
|
});
|
|
@@ -503,4 +533,4 @@ export {
|
|
|
503
533
|
Platformer3DSystem,
|
|
504
534
|
Platformer3DBehavior
|
|
505
535
|
};
|
|
506
|
-
//# sourceMappingURL=chunk-
|
|
536
|
+
//# sourceMappingURL=chunk-67XLJ26X.js.map
|
|
@@ -1 +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.system.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/** 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/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for small ledges, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\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\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\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.3, minWidth: 0.1 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.3,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\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, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: 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\tverticalVelocity: 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 System\n *\n * Kinematic character controller (N64-style): the controller owns all motion\n * and physics is only a collision database.\n *\n * - The entity's body is converted to `KinematicPositionBased` on first\n * update; Rapier never applies forces to it.\n * - Each tick, desired displacement (horizontal input + integrated vertical\n * velocity) is run through Rapier's `KinematicCharacterController`\n * (collide-and-slide with autostep, slope limits, and snap-to-ground) and\n * the corrected movement is applied via `setNextKinematicTranslation`.\n * - Grounded comes from `controller.computedGrounded()` — no contact-pair or\n * raycast probing.\n * - Vertical velocity lives in `state.verticalVelocity` (`linvel()` is\n * meaningless on kinematic bodies). Jump buffering, coyote time,\n * multi-jump, and the jump-cut ramp all operate on that field.\n */\n\nimport { RigidBodyType, QueryFilterFlags } from '@dimforge/rapier3d-compat';\nimport type { Collider, KinematicCharacterController, RigidBody } from '@dimforge/rapier3d-compat';\nimport type { ZylemWorld } from '../../collision/world';\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 vertical velocity in a single frame (which produces a\n * visible camera pop), we lerp toward the cut velocity across roughly 3\n * physics steps at 60 Hz.\n */\nconst JUMP_CUT_RAMP_DURATION = 0.05;\n\n/**\n * Constant downward speed applied while grounded (units/second). Keeps the\n * controller pressed into the ground so `computedGrounded()` stays engaged on\n * flat ground and going downhill (snap-to-ground handles larger drops).\n */\nconst GROUND_STICK_SPEED = 2;\n\n/** Vertical speed below which the airborne state flips from jumping to falling. */\nconst FALL_VELOCITY_THRESHOLD = -0.1;\n\nconst DEG_TO_RAD = Math.PI / 180;\n\n/**\n * Entity with platformer components\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tbody: RigidBody;\n\tcollider?: Collider | null;\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n}\n\n/** Scratch vector reused for the desired per-tick displacement. */\nconst _desired = { x: 0, y: 0, z: 0 };\n\n/**\n * Platformer 3D movement system.\n *\n * Core kinematic controller for 3D platformer movement. Requires a live\n * `ZylemWorld` with the raw Rapier `World` exposed on `.world` — without it\n * the system is a no-op (the WASM fast path handles that case upstream).\n */\nexport class Platformer3DSystem {\n\tprivate world: ZylemWorld | null;\n\t/** Per-entity character controllers, keyed by entity uuid. */\n\tprivate controllers = new Map<string, KinematicCharacterController>();\n\n\tconstructor(world: ZylemWorld | null) {\n\t\tthis.world = world;\n\t}\n\n\t/**\n\t * One-time per-entity physics setup: convert the body to a\n\t * position-based kinematic body and create its character controller.\n\t * Done lazily on the first update because the body isn't always\n\t * available at construction time.\n\t */\n\tprivate ensurePhysicsConfigured(entity: Platformer3DEntity): KinematicCharacterController | null {\n\t\tconst existing = this.controllers.get(entity.uuid);\n\t\tif (existing) return existing;\n\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (!physicsWorld || !entity.body) return null;\n\n\t\tif (entity.body.bodyType() !== RigidBodyType.KinematicPositionBased) {\n\t\t\tentity.body.setBodyType(RigidBodyType.KinematicPositionBased, true);\n\t\t}\n\n\t\tconst movement = entity.platformer;\n\t\tconst controller = physicsWorld.createCharacterController(movement.characterOffset);\n\t\tcontroller.setUp({ x: 0, y: 1, z: 0 });\n\t\tcontroller.setMaxSlopeClimbAngle(movement.slopeClimbAngle * DEG_TO_RAD);\n\t\tcontroller.setMinSlopeSlideAngle(movement.slopeSlideAngle * DEG_TO_RAD);\n\t\tif (movement.autostep) {\n\t\t\tcontroller.enableAutostep(movement.autostep.maxHeight, movement.autostep.minWidth, true);\n\t\t} else {\n\t\t\tcontroller.disableAutostep();\n\t\t}\n\t\tcontroller.enableSnapToGround(movement.snapToGroundDistance);\n\t\tcontroller.setApplyImpulsesToDynamicBodies(true);\n\n\t\tthis.controllers.set(entity.uuid, controller);\n\t\treturn controller;\n\t}\n\n\t/**\n\t * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut\n\t * ramp. All vertical motion is written to `state.verticalVelocity`.\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\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 verticalVelocity toward verticalVelocity * jumpCutMultiplier\n\t\t// across the ramp window so the change isn't a single-frame discontinuity.\n\t\tif (state.jumpCutRampTimer > 0) {\n\t\t\tif (state.verticalVelocity > 0) {\n\t\t\t\tconst stepT = Math.min(delta / state.jumpCutRampTimer, 1);\n\t\t\t\tconst targetY = state.verticalVelocity * movement.jumpCutMultiplier;\n\t\t\t\tstate.verticalVelocity += (targetY - state.verticalVelocity) * stepT;\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\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\n\t\t\tstate.verticalVelocity = movement.jumpForce;\n\n\t\t\t// Flag this frame so applyGravity skips exactly one grounded\n\t\t\t// clamp/gravity step (the jump velocity is authoritative).\n\t\t\tstate.jumpedThisFrame = true;\n\t\t}\n\t}\n\n\t/**\n\t * Integrate gravity into `state.verticalVelocity`.\n\t *\n\t * While grounded (and not on the jump frame) vertical velocity is held at\n\t * a small constant downward speed so the controller stays pressed into\n\t * the ground. Skipped entirely on the frame a jump was applied — the\n\t * grounded flag is still stale-true then and would clobber the jump.\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) {\n\t\t\tstate.verticalVelocity = -GROUND_STICK_SPEED;\n\t\t\treturn;\n\t\t}\n\n\t\tstate.verticalVelocity -= movement.gravity * delta;\n\t}\n\n\t/**\n\t * Update entity state from the controller's grounded result.\n\t */\n\tprivate updateState(entity: Platformer3DEntity, delta: number, computedGrounded: boolean): void {\n\t\tconst state = entity.platformerState;\n\t\tconst wasGrounded = state.grounded;\n\n\t\tconst isAirborne = state.jumping || state.falling;\n\t\tlet isGrounded: boolean;\n\t\tif (isAirborne) {\n\t\t\t// Never land while still moving upward in a jump arc.\n\t\t\tconst canLand = state.falling && !state.jumping;\n\t\t\tisGrounded = computedGrounded && canLand;\n\t\t} else {\n\t\t\tisGrounded = computedGrounded;\n\t\t}\n\n\t\tstate.grounded = isGrounded;\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\tif (state.verticalVelocity < 0) {\n\t\t\t\tstate.verticalVelocity = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (state.verticalVelocity < FALL_VELOCITY_THRESHOLD && !state.grounded) {\n\t\t\tif (state.jumping) {\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: Platformer3DEntity, delta: number): void {\n\t\tif (!entity.platformer || !entity.$platformer || !entity.platformerState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst controller = this.ensurePhysicsConfigured(entity);\n\t\tconst collider = entity.collider;\n\t\tif (!controller || !collider) return;\n\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\t// 1. Jump logic (may set verticalVelocity = jumpForce this frame)\n\t\tthis.handleJump(entity, delta);\n\n\t\t// 2. Gravity / grounded stick on verticalVelocity\n\t\tthis.applyGravity(entity, delta);\n\n\t\t// 3. Desired displacement for this tick\n\t\tconst speed = input.run ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\t\t_desired.x = input.moveX * speed * delta;\n\t\t_desired.y = state.verticalVelocity * delta;\n\t\t_desired.z = input.moveZ * speed * delta;\n\n\t\t// 4. Collide-and-slide\n\t\tcontroller.computeColliderMovement(collider, _desired, QueryFilterFlags.EXCLUDE_SENSORS);\n\t\tconst corrected = controller.computedMovement();\n\n\t\t// Head bump: upward motion was blocked, so kill the remaining ascent\n\t\t// instead of hanging under the ceiling for the rest of the arc.\n\t\tif (_desired.y > 1e-9 && corrected.y < _desired.y * 0.5) {\n\t\t\tstate.verticalVelocity = 0;\n\t\t}\n\n\t\tconst translation = entity.body.translation();\n\t\tentity.body.setNextKinematicTranslation({\n\t\t\tx: translation.x + corrected.x,\n\t\t\ty: translation.y + corrected.y,\n\t\t\tz: translation.z + corrected.z,\n\t\t});\n\n\t\t// 5. State transitions from the controller's grounded result\n\t\tthis.updateState(entity, delta, controller.computedGrounded());\n\t}\n\n\tdestroy(): void {\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (physicsWorld) {\n\t\t\tfor (const controller of this.controllers.values()) {\n\t\t\t\tphysicsWorld.removeCharacterController(controller);\n\t\t\t}\n\t\t}\n\t\tthis.controllers.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 { Platformer3DSystem, Platformer3DEntity } from './platformer-3d.system';\nimport type { ZylemWorld } from '../../collision/world';\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/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small ledges, or false to disable (default: { maxHeight: 0.3, minWidth: 0.1 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.3) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.3, minWidth: 0.1 },\n\tsnapToGroundDistance: 0.3,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\n/**\n * Adapter that wraps Platformer3DSystem as a BehaviorSystem\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: Platformer3DSystem;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tworld: ZylemWorld | null,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new Platformer3DSystem(world);\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 ?? null,\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"]}
|
|
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.system.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/** 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/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for small ledges, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\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\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\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.3, minWidth: 0.1 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.3,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\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, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: 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\tverticalVelocity: 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 System\n *\n * Kinematic character controller (N64-style): the controller owns all motion\n * and physics is only a collision database.\n *\n * - The entity's body is converted to `KinematicPositionBased` on first\n * update; Rapier never applies forces to it.\n * - Each tick, desired displacement (horizontal input + integrated vertical\n * velocity) is run through Rapier's `KinematicCharacterController`\n * (collide-and-slide with autostep, slope limits, and snap-to-ground) and\n * the corrected movement is applied via `setNextKinematicTranslation`.\n * - Grounded comes from `controller.computedGrounded()` — no contact-pair or\n * raycast probing.\n * - Vertical velocity lives in `state.verticalVelocity` (`linvel()` is\n * meaningless on kinematic bodies). Jump buffering, coyote time,\n * multi-jump, and the jump-cut ramp all operate on that field.\n */\n\nimport { RigidBodyType, QueryFilterFlags } from '@dimforge/rapier3d-compat';\nimport type { Collider, KinematicCharacterController, RigidBody } from '@dimforge/rapier3d-compat';\nimport type { ZylemWorld } from '../../collision/world';\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 vertical velocity in a single frame (which produces a\n * visible camera pop), we lerp toward the cut velocity across roughly 3\n * physics steps at 60 Hz.\n */\nconst JUMP_CUT_RAMP_DURATION = 0.05;\n\n/**\n * Constant downward speed applied while grounded (units/second). Keeps the\n * controller pressed into the ground so `computedGrounded()` stays engaged on\n * flat ground and going downhill (snap-to-ground handles larger drops).\n */\nconst GROUND_STICK_SPEED = 2;\n\n/** Vertical speed below which the airborne state flips from jumping to falling. */\nconst FALL_VELOCITY_THRESHOLD = -0.1;\n\nconst DEG_TO_RAD = Math.PI / 180;\n\n/**\n * Entity with platformer components\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tbody: RigidBody;\n\tcollider?: Collider | null;\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n}\n\n/** Scratch vector reused for the desired per-tick displacement. */\nconst _desired = { x: 0, y: 0, z: 0 };\n\n/**\n * Platformer 3D movement system.\n *\n * Core kinematic controller for 3D platformer movement. Requires a live\n * `ZylemWorld` with the raw Rapier `World` exposed on `.world` — without it\n * the system is a no-op (the WASM fast path handles that case upstream).\n */\nexport class Platformer3DSystem {\n\tprivate world: ZylemWorld | null;\n\t/** Per-entity character controllers, keyed by entity uuid. */\n\tprivate controllers = new Map<string, KinematicCharacterController>();\n\n\tconstructor(world: ZylemWorld | null) {\n\t\tthis.world = world;\n\t}\n\n\t/**\n\t * One-time per-entity physics setup: convert the body to a\n\t * position-based kinematic body and create its character controller.\n\t * Done lazily on the first update because the body isn't always\n\t * available at construction time.\n\t */\n\tprivate ensurePhysicsConfigured(entity: Platformer3DEntity): KinematicCharacterController | null {\n\t\tconst existing = this.controllers.get(entity.uuid);\n\t\tif (existing) return existing;\n\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (!physicsWorld || !entity.body) return null;\n\n\t\tif (entity.body.bodyType() !== RigidBodyType.KinematicPositionBased) {\n\t\t\tentity.body.setBodyType(RigidBodyType.KinematicPositionBased, true);\n\t\t}\n\n\t\tconst movement = entity.platformer;\n\t\tconst controller = physicsWorld.createCharacterController(movement.characterOffset);\n\t\tcontroller.setUp({ x: 0, y: 1, z: 0 });\n\t\tcontroller.setMaxSlopeClimbAngle(movement.slopeClimbAngle * DEG_TO_RAD);\n\t\tcontroller.setMinSlopeSlideAngle(movement.slopeSlideAngle * DEG_TO_RAD);\n\t\tif (movement.autostep) {\n\t\t\tcontroller.enableAutostep(movement.autostep.maxHeight, movement.autostep.minWidth, true);\n\t\t} else {\n\t\t\tcontroller.disableAutostep();\n\t\t}\n\t\tcontroller.enableSnapToGround(movement.snapToGroundDistance);\n\t\tcontroller.setApplyImpulsesToDynamicBodies(true);\n\n\t\tthis.controllers.set(entity.uuid, controller);\n\t\treturn controller;\n\t}\n\n\t/**\n\t * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut\n\t * ramp. All vertical motion is written to `state.verticalVelocity`.\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\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 verticalVelocity toward verticalVelocity * jumpCutMultiplier\n\t\t// across the ramp window so the change isn't a single-frame discontinuity.\n\t\tif (state.jumpCutRampTimer > 0) {\n\t\t\tif (state.verticalVelocity > 0) {\n\t\t\t\tconst stepT = Math.min(delta / state.jumpCutRampTimer, 1);\n\t\t\t\tconst targetY = state.verticalVelocity * movement.jumpCutMultiplier;\n\t\t\t\tstate.verticalVelocity += (targetY - state.verticalVelocity) * stepT;\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\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\n\t\t\tstate.verticalVelocity = movement.jumpForce;\n\n\t\t\t// Flag this frame so applyGravity skips exactly one grounded\n\t\t\t// clamp/gravity step (the jump velocity is authoritative).\n\t\t\tstate.jumpedThisFrame = true;\n\t\t}\n\t}\n\n\t/**\n\t * Integrate gravity into `state.verticalVelocity`.\n\t *\n\t * While grounded (and not on the jump frame) vertical velocity is held at\n\t * a small constant downward speed so the controller stays pressed into\n\t * the ground. Skipped entirely on the frame a jump was applied — the\n\t * grounded flag is still stale-true then and would clobber the jump.\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) {\n\t\t\tstate.verticalVelocity = -GROUND_STICK_SPEED;\n\t\t\treturn;\n\t\t}\n\n\t\tstate.verticalVelocity -= movement.gravity * delta;\n\t}\n\n\t/**\n\t * Update entity state from the controller's grounded result.\n\t */\n\tprivate updateState(entity: Platformer3DEntity, delta: number, computedGrounded: boolean): void {\n\t\tconst state = entity.platformerState;\n\t\tconst wasGrounded = state.grounded;\n\n\t\tconst isAirborne = state.jumping || state.falling;\n\t\tlet isGrounded: boolean;\n\t\tif (isAirborne) {\n\t\t\t// Never land while still moving upward in a jump arc.\n\t\t\tconst canLand = state.falling && !state.jumping;\n\t\t\tisGrounded = computedGrounded && canLand;\n\t\t} else {\n\t\t\tisGrounded = computedGrounded;\n\t\t}\n\n\t\tstate.grounded = isGrounded;\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\tif (state.verticalVelocity < 0) {\n\t\t\t\tstate.verticalVelocity = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (state.verticalVelocity < FALL_VELOCITY_THRESHOLD && !state.grounded) {\n\t\t\tif (state.jumping) {\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: Platformer3DEntity, delta: number): void {\n\t\tif (!entity.platformer || !entity.$platformer || !entity.platformerState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst controller = this.ensurePhysicsConfigured(entity);\n\t\tconst collider = entity.collider;\n\t\tif (!controller || !collider) return;\n\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\t// 1. Jump logic (may set verticalVelocity = jumpForce this frame)\n\t\tthis.handleJump(entity, delta);\n\n\t\t// 2. Gravity / grounded stick on verticalVelocity\n\t\tthis.applyGravity(entity, delta);\n\n\t\t// 3. Desired displacement for this tick\n\t\tconst speed = input.run ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\t\t_desired.x = input.moveX * speed * delta;\n\t\t_desired.y = state.verticalVelocity * delta;\n\t\t_desired.z = input.moveZ * speed * delta;\n\n\t\t// 4. Collide-and-slide\n\t\tcontroller.computeColliderMovement(collider, _desired, QueryFilterFlags.EXCLUDE_SENSORS);\n\t\tconst corrected = controller.computedMovement();\n\n\t\t// Head bump: upward motion was blocked, so kill the remaining ascent\n\t\t// instead of hanging under the ceiling for the rest of the arc.\n\t\tif (_desired.y > 1e-9 && corrected.y < _desired.y * 0.5) {\n\t\t\tstate.verticalVelocity = 0;\n\t\t}\n\n\t\tconst translation = entity.body.translation();\n\t\tentity.body.setNextKinematicTranslation({\n\t\t\tx: translation.x + corrected.x,\n\t\t\ty: translation.y + corrected.y,\n\t\t\tz: translation.z + corrected.z,\n\t\t});\n\n\t\t// 5. State transitions from the controller's grounded result\n\t\tthis.updateState(entity, delta, controller.computedGrounded());\n\t}\n\n\tdestroy(): void {\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (physicsWorld) {\n\t\t\tfor (const controller of this.controllers.values()) {\n\t\t\t\tphysicsWorld.removeCharacterController(controller);\n\t\t\t}\n\t\t}\n\t\tthis.controllers.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 { Platformer3DSystem, Platformer3DEntity } from './platformer-3d.system';\nimport type { ZylemWorld } from '../../collision/world';\nimport { Platformer3DFSM, Platformer3DState, PlatformerCollisionContext } from './platformer-3d-fsm';\nimport {\n\tcreatePlatformer3DMovementComponent,\n\tcreatePlatformer3DInputComponent,\n\tcreatePlatformer3DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust `Platformer3DFsmState` index → TS state (see `platformer_3d/fsm.rs`). */\nconst FSM_STATE_BY_INDEX: readonly Platformer3DState[] = [\n\tPlatformer3DState.Idle,\n\tPlatformer3DState.Walking,\n\tPlatformer3DState.Running,\n\tPlatformer3DState.Jumping,\n\tPlatformer3DState.Falling,\n\tPlatformer3DState.Landing,\n];\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/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small ledges, or false to disable (default: { maxHeight: 0.3, minWidth: 0.1 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.3) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.3, minWidth: 0.1 },\n\tsnapToGroundDistance: 0.3,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\n/**\n * Adapter that wraps Platformer3DSystem as a BehaviorSystem\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: Platformer3DSystem;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tworld: ZylemWorld | null,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new Platformer3DSystem(world);\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\t// Wasm fast path: forward input into the batched buffer and let\n\t\t\t// handles read the Rust FSM via the snapshot buffer — the TS FSM\n\t\t\t// is never created (single source of truth in the runtime).\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\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\t\tconst input = gameEntity.$platformer;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t\t}\n\t\t\t\tcontinue;\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\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\tconst autostep = options.autostep ?? defaultOptions.autostep!;\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\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.1,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\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 ?? null,\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// On the wasm fast path the handle reads the runtime snapshot buffer\n\t// (values are from the end of the previous step — one-frame lag); the TS\n\t// FSM only backs the no-wasm fallback path.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer3DState.Idle;\n\t\t\treturn ref.fsm?.getState() ?? Platformer3DState.Idle;\n\t\t},\n\t\tisGrounded: () =>\n\t\t\treadBehaviorSnapshot(ref)?.grounded ?? ref.fsm?.isGrounded() ?? false,\n\t\tgetJumpCount: () =>\n\t\t\treadBehaviorSnapshot(ref)?.jumpCount ?? ref.fsm?.getJumpCount() ?? 0,\n\t\tonPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx),\n\t}),\n});\n"],"mappings":"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","names":["Platformer3DState","Platformer3DEvent"]}
|