@zylem/behaviors 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{behavior-system-DYbx-x_-.d.ts → behavior-system-DSNek0W9.d.ts} +205 -29
- package/dist/{chunk-Y6UJOUE5.js → chunk-4V7TIRO2.js} +1 -1
- package/dist/chunk-4V7TIRO2.js.map +1 -0
- package/dist/{chunk-DYLH6K7B.js → chunk-4WH2ZN7P.js} +1 -1
- package/dist/{chunk-DYLH6K7B.js.map → chunk-4WH2ZN7P.js.map} +1 -1
- package/dist/{chunk-ZPSAXX73.js → chunk-5SM5SH5O.js} +26 -26
- package/dist/chunk-5SM5SH5O.js.map +1 -0
- package/dist/{chunk-STCCNLPC.js → chunk-5XZWTFGF.js} +5 -3
- package/dist/chunk-5XZWTFGF.js.map +1 -0
- package/dist/{chunk-OYWFPIHF.js → chunk-64HKCSXD.js} +19 -16
- package/dist/chunk-64HKCSXD.js.map +1 -0
- package/dist/{chunk-JAN34G6S.js → chunk-FRJDQM23.js} +108 -154
- package/dist/chunk-FRJDQM23.js.map +1 -0
- package/dist/{chunk-RTRVBEIB.js → chunk-FTDZAKZW.js} +5 -18
- package/dist/chunk-FTDZAKZW.js.map +1 -0
- package/dist/{chunk-XIJNE67Y.js → chunk-GFARAJV2.js} +3 -5
- package/dist/{chunk-XIJNE67Y.js.map → chunk-GFARAJV2.js.map} +1 -1
- package/dist/{chunk-SS7ELMJ7.js → chunk-GTLA6LOJ.js} +16 -22
- package/dist/chunk-GTLA6LOJ.js.map +1 -0
- package/dist/{chunk-VAN4WW3F.js → chunk-H4TJ7X4O.js} +4 -7
- package/dist/chunk-H4TJ7X4O.js.map +1 -0
- package/dist/{chunk-4I6E6DGV.js → chunk-HNESASBB.js} +4 -6
- package/dist/{chunk-4I6E6DGV.js.map → chunk-HNESASBB.js.map} +1 -1
- package/dist/{chunk-KS72RZHX.js → chunk-IZJFUAXB.js} +1 -1
- package/dist/{chunk-KS72RZHX.js.map → chunk-IZJFUAXB.js.map} +1 -1
- package/dist/{chunk-SVLNF434.js → chunk-JXMF4NUI.js} +51 -3
- package/dist/chunk-JXMF4NUI.js.map +1 -0
- package/dist/chunk-KFFETUAL.js +40 -0
- package/dist/chunk-KFFETUAL.js.map +1 -0
- package/dist/chunk-LL4FLHIY.js +12 -0
- package/dist/chunk-LL4FLHIY.js.map +1 -0
- package/dist/{chunk-2CLT36VB.js → chunk-MJFMNRMZ.js} +13 -14
- package/dist/chunk-MJFMNRMZ.js.map +1 -0
- package/dist/{chunk-RTVRUNYR.js → chunk-OTLSUYQQ.js} +2 -2
- package/dist/chunk-OTLSUYQQ.js.map +1 -0
- package/dist/{chunk-XG2YMCJW.js → chunk-PWF757B6.js} +4 -6
- package/dist/{chunk-XG2YMCJW.js.map → chunk-PWF757B6.js.map} +1 -1
- package/dist/{chunk-LD5SMIB3.js → chunk-QVCMJ4Q2.js} +1 -1
- package/dist/{chunk-LD5SMIB3.js.map → chunk-QVCMJ4Q2.js.map} +1 -1
- package/dist/{chunk-F3KRX2V4.js → chunk-RONH2KDI.js} +10 -12
- package/dist/chunk-RONH2KDI.js.map +1 -0
- package/dist/chunk-RTRFJVCZ.js +454 -0
- package/dist/chunk-RTRFJVCZ.js.map +1 -0
- package/dist/{chunk-6C6NXNEW.js → chunk-UL6Q5HDP.js} +25 -3
- package/dist/chunk-UL6Q5HDP.js.map +1 -0
- package/dist/{chunk-KOZUNIIE.js → chunk-V74N7D6Q.js} +7 -7
- package/dist/{chunk-KOZUNIIE.js.map → chunk-V74N7D6Q.js.map} +1 -1
- package/dist/{chunk-YUO6SKJD.js → chunk-WXXXJ22R.js} +11 -13
- package/dist/chunk-WXXXJ22R.js.map +1 -0
- package/dist/{chunk-ARJWUNRE.js → chunk-XLUJDKCT.js} +7 -7
- package/dist/{chunk-ARJWUNRE.js.map → chunk-XLUJDKCT.js.map} +1 -1
- package/dist/{chunk-GCNRXAKH.js → chunk-XP3ASVGJ.js} +13 -10
- package/dist/chunk-XP3ASVGJ.js.map +1 -0
- package/dist/{chunk-ID5QJYF5.js → chunk-Y7YIAWPW.js} +11 -1
- package/dist/chunk-Y7YIAWPW.js.map +1 -0
- package/dist/{chunk-FPTWVCPN.js → chunk-ZA43LPRM.js} +4 -24
- package/dist/chunk-ZA43LPRM.js.map +1 -0
- package/dist/{chunk-K3UNLQ7W.js → chunk-ZRQWSURL.js} +2 -2
- package/dist/chunk-ZRQWSURL.js.map +1 -0
- package/dist/cooldown.d.ts +2 -1
- package/dist/cooldown.js +1 -1
- package/dist/coordinators/boundary-ricochet-3d.d.ts +12 -5
- package/dist/coordinators/boundary-ricochet-3d.js +2 -1
- package/dist/coordinators/boundary-ricochet.d.ts +9 -7
- package/dist/coordinators/boundary-ricochet.js +2 -1
- package/dist/coordinators/first-person-shooter.d.ts +9 -8
- package/dist/coordinators/first-person-shooter.js +3 -3
- package/dist/coordinators/multidirectional-space-shooter.d.ts +12 -9
- package/dist/coordinators/multidirectional-space-shooter.js +4 -4
- package/dist/coordinators/top-down-shooter.d.ts +9 -8
- package/dist/coordinators/top-down-shooter.js +3 -3
- package/dist/core.d.ts +4 -4
- package/dist/core.js +4 -32
- package/dist/core.js.map +1 -1
- package/dist/destructible-3d.d.ts +2 -1
- package/dist/first-person.d.ts +12 -12
- package/dist/first-person.js +8 -8
- package/dist/index.d.ts +37 -14
- package/dist/index.js +73 -44
- package/dist/jumper-2d.d.ts +7 -6
- package/dist/jumper-2d.js +5 -5
- package/dist/jumper-3d.d.ts +12 -11
- package/dist/jumper-3d.js +6 -6
- package/dist/{moveable-BCRpjAQb.d.ts → moveable-BLTHO7Jq.d.ts} +2 -78
- package/dist/particle-emitter.d.ts +1 -1
- package/dist/particle-emitter.js +1 -1
- package/dist/platformer-2d.d.ts +306 -0
- package/dist/platformer-2d.js +23 -0
- package/dist/platformer-3d.d.ts +74 -87
- package/dist/platformer-3d.js +4 -4
- 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 +93 -2
- package/dist/runtime-2d.js +145 -22
- package/dist/runtime-2d.js.map +1 -1
- package/dist/screen-visibility.d.ts +2 -1
- package/dist/screen-wrap.d.ts +2 -1
- package/dist/screen-wrap.js +1 -1
- package/dist/shooter-2d.d.ts +5 -3
- package/dist/shooter-2d.js +2 -2
- package/dist/shooter-fire-DGripl0z.d.ts +55 -0
- package/dist/thruster.d.ts +8 -18
- package/dist/thruster.js +6 -6
- package/dist/top-down-movement.d.ts +7 -4
- package/dist/top-down-movement.js +5 -5
- package/dist/{use-behavior-D_82jAL3.d.ts → use-behavior-h9-T5FSv.d.ts} +1 -1
- package/dist/world-boundary-2d.d.ts +2 -1
- package/dist/world-boundary-2d.js +2 -2
- package/dist/world-boundary-3d.d.ts +2 -1
- package/dist/world-boundary-3d.js +2 -2
- package/package.json +5 -5
- package/dist/chunk-2C3Z5GE7.js +0 -176
- package/dist/chunk-2C3Z5GE7.js.map +0 -1
- package/dist/chunk-2CLT36VB.js.map +0 -1
- package/dist/chunk-3PYGRHAL.js +0 -35
- package/dist/chunk-3PYGRHAL.js.map +0 -1
- package/dist/chunk-6C6NXNEW.js.map +0 -1
- package/dist/chunk-F3KRX2V4.js.map +0 -1
- package/dist/chunk-FPTWVCPN.js.map +0 -1
- package/dist/chunk-GCNRXAKH.js.map +0 -1
- package/dist/chunk-ID5QJYF5.js.map +0 -1
- package/dist/chunk-JAN34G6S.js.map +0 -1
- package/dist/chunk-K3UNLQ7W.js.map +0 -1
- package/dist/chunk-OYWFPIHF.js.map +0 -1
- package/dist/chunk-RTRVBEIB.js.map +0 -1
- package/dist/chunk-RTVRUNYR.js.map +0 -1
- package/dist/chunk-SS7ELMJ7.js.map +0 -1
- package/dist/chunk-STCCNLPC.js.map +0 -1
- package/dist/chunk-SVLNF434.js.map +0 -1
- package/dist/chunk-VAN4WW3F.js.map +0 -1
- package/dist/chunk-Y6UJOUE5.js.map +0 -1
- package/dist/chunk-YUO6SKJD.js.map +0 -1
- package/dist/chunk-ZPSAXX73.js.map +0 -1
- package/dist/runtime-pong.d.ts +0 -92
- package/dist/runtime-pong.js +0 -38
- /package/dist/{runtime-pong.js.map → platformer-2d.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zylem/behaviors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Tree-shakable Zylem behavior descriptors, systems, coordinators, and runtime-agnostic behavior helpers.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"types": "./dist/particle-emitter/presets.d.ts",
|
|
58
58
|
"import": "./dist/particle-emitter/presets.js"
|
|
59
59
|
},
|
|
60
|
+
"./platformer-2d": {
|
|
61
|
+
"types": "./dist/platformer-2d.d.ts",
|
|
62
|
+
"import": "./dist/platformer-2d.js"
|
|
63
|
+
},
|
|
60
64
|
"./platformer-3d": {
|
|
61
65
|
"types": "./dist/platformer-3d.d.ts",
|
|
62
66
|
"import": "./dist/platformer-3d.js"
|
|
@@ -73,10 +77,6 @@
|
|
|
73
77
|
"types": "./dist/runtime-2d.d.ts",
|
|
74
78
|
"import": "./dist/runtime-2d.js"
|
|
75
79
|
},
|
|
76
|
-
"./runtime-pong": {
|
|
77
|
-
"types": "./dist/runtime-pong.d.ts",
|
|
78
|
-
"import": "./dist/runtime-pong.js"
|
|
79
|
-
},
|
|
80
80
|
"./screen-visibility": {
|
|
81
81
|
"types": "./dist/screen-visibility.d.ts",
|
|
82
82
|
"import": "./dist/screen-visibility.js"
|
package/dist/chunk-2C3Z5GE7.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineBehavior
|
|
3
|
-
} from "./chunk-EZNCCO4L.js";
|
|
4
|
-
|
|
5
|
-
// src/lib/behaviors/runtime-pong/runtime-pong.descriptors.ts
|
|
6
|
-
var runtimeNoopSystemFactory = () => ({
|
|
7
|
-
update(_ecs, _delta) {
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
var RUNTIME_PONG_BALL_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-pong-ball");
|
|
11
|
-
var RUNTIME_PADDLE_INPUT_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-paddle-input-2d");
|
|
12
|
-
var RUNTIME_WORLD_BOUNDARY_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-world-boundary-2d");
|
|
13
|
-
var RUNTIME_RICOCHET_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-ricochet-2d");
|
|
14
|
-
var RUNTIME_TRIGGER_REGION_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-trigger-region-2d");
|
|
15
|
-
var RUNTIME_GOAL_ZONE_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:runtime-goal-zone-2d");
|
|
16
|
-
var RICOCHET_LISTENERS = /* @__PURE__ */ Symbol("runtime-ricochet-listeners");
|
|
17
|
-
var TRIGGER_REGION_LISTENERS = /* @__PURE__ */ Symbol("runtime-trigger-region-listeners");
|
|
18
|
-
var GOAL_LISTENERS = /* @__PURE__ */ Symbol("runtime-goal-listeners");
|
|
19
|
-
var RUNTIME_BALL_SET_POSITION = /* @__PURE__ */ Symbol("runtime-ball-set-position");
|
|
20
|
-
var RUNTIME_BALL_SET_VELOCITY = /* @__PURE__ */ Symbol("runtime-ball-set-velocity");
|
|
21
|
-
var RUNTIME_BALL_GET_POSITION = /* @__PURE__ */ Symbol("runtime-ball-get-position");
|
|
22
|
-
var RuntimePongBallBehavior = defineBehavior({
|
|
23
|
-
name: "runtime-pong-ball",
|
|
24
|
-
defaultOptions: {
|
|
25
|
-
initialVelocity: [0, 0]
|
|
26
|
-
},
|
|
27
|
-
systemFactory: runtimeNoopSystemFactory,
|
|
28
|
-
createHandle: (ref) => ({
|
|
29
|
-
setRuntimePosition(x, y, z = 0) {
|
|
30
|
-
const callback = ref[RUNTIME_BALL_SET_POSITION];
|
|
31
|
-
callback?.(x, y, z);
|
|
32
|
-
},
|
|
33
|
-
setRuntimeVelocity(x, y) {
|
|
34
|
-
const callback = ref[RUNTIME_BALL_SET_VELOCITY];
|
|
35
|
-
callback?.(x, y);
|
|
36
|
-
},
|
|
37
|
-
getRuntimePosition() {
|
|
38
|
-
const callback = ref[RUNTIME_BALL_GET_POSITION];
|
|
39
|
-
return callback?.() ?? null;
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
});
|
|
43
|
-
var RuntimePaddleInput2DBehavior = defineBehavior({
|
|
44
|
-
name: "runtime-paddle-input-2d",
|
|
45
|
-
defaultOptions: {
|
|
46
|
-
player: "p1",
|
|
47
|
-
speed: 5
|
|
48
|
-
},
|
|
49
|
-
systemFactory: runtimeNoopSystemFactory
|
|
50
|
-
});
|
|
51
|
-
var RuntimeWorldBoundary2DBehavior = defineBehavior({
|
|
52
|
-
name: "runtime-world-boundary-2d",
|
|
53
|
-
defaultOptions: {
|
|
54
|
-
boundaries: {
|
|
55
|
-
left: 0,
|
|
56
|
-
right: 0,
|
|
57
|
-
bottom: 0,
|
|
58
|
-
top: 0
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
systemFactory: runtimeNoopSystemFactory
|
|
62
|
-
});
|
|
63
|
-
var RuntimeRicochet2DBehavior = defineBehavior({
|
|
64
|
-
name: "runtime-ricochet-2d",
|
|
65
|
-
defaultOptions: {
|
|
66
|
-
minSpeed: 2,
|
|
67
|
-
maxSpeed: 20,
|
|
68
|
-
speedMultiplier: 1.05,
|
|
69
|
-
reflectionMode: "angled",
|
|
70
|
-
maxAngleDeg: 60
|
|
71
|
-
},
|
|
72
|
-
systemFactory: runtimeNoopSystemFactory,
|
|
73
|
-
createHandle: (ref) => ({
|
|
74
|
-
onRicochet(callback) {
|
|
75
|
-
const listeners = ref[RICOCHET_LISTENERS] ?? [];
|
|
76
|
-
if (!ref[RICOCHET_LISTENERS]) {
|
|
77
|
-
ref[RICOCHET_LISTENERS] = listeners;
|
|
78
|
-
}
|
|
79
|
-
listeners.push(callback);
|
|
80
|
-
return () => {
|
|
81
|
-
const index = listeners.indexOf(callback);
|
|
82
|
-
if (index >= 0) {
|
|
83
|
-
listeners.splice(index, 1);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
});
|
|
89
|
-
var RuntimeTriggerRegion2DBehavior = defineBehavior({
|
|
90
|
-
name: "runtime-trigger-region-2d",
|
|
91
|
-
defaultOptions: {
|
|
92
|
-
triggerId: 0,
|
|
93
|
-
halfExtents: [0.5, 2.5]
|
|
94
|
-
},
|
|
95
|
-
systemFactory: runtimeNoopSystemFactory,
|
|
96
|
-
createHandle: (ref) => ({
|
|
97
|
-
onRegionEnter(callback) {
|
|
98
|
-
const listeners = ref[TRIGGER_REGION_LISTENERS] ?? [];
|
|
99
|
-
if (!ref[TRIGGER_REGION_LISTENERS]) {
|
|
100
|
-
ref[TRIGGER_REGION_LISTENERS] = listeners;
|
|
101
|
-
}
|
|
102
|
-
listeners.push(callback);
|
|
103
|
-
return () => {
|
|
104
|
-
const index = listeners.indexOf(callback);
|
|
105
|
-
if (index >= 0) {
|
|
106
|
-
listeners.splice(index, 1);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
});
|
|
112
|
-
var RuntimeGoalZone2DBehavior = defineBehavior({
|
|
113
|
-
name: "runtime-goal-zone-2d",
|
|
114
|
-
defaultOptions: {
|
|
115
|
-
scorer: "p1"
|
|
116
|
-
},
|
|
117
|
-
systemFactory: runtimeNoopSystemFactory,
|
|
118
|
-
createHandle: (ref) => ({
|
|
119
|
-
onGoal(callback) {
|
|
120
|
-
const listeners = ref[GOAL_LISTENERS] ?? [];
|
|
121
|
-
if (!ref[GOAL_LISTENERS]) {
|
|
122
|
-
ref[GOAL_LISTENERS] = listeners;
|
|
123
|
-
}
|
|
124
|
-
listeners.push(callback);
|
|
125
|
-
return () => {
|
|
126
|
-
const index = listeners.indexOf(callback);
|
|
127
|
-
if (index >= 0) {
|
|
128
|
-
listeners.splice(index, 1);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
});
|
|
134
|
-
function emitRuntimeRicochet(ref, event) {
|
|
135
|
-
const listeners = ref[RICOCHET_LISTENERS];
|
|
136
|
-
for (const listener of listeners ?? []) {
|
|
137
|
-
listener(event);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function emitRuntimeTriggerRegionEnter(ref, event) {
|
|
141
|
-
const listeners = ref[TRIGGER_REGION_LISTENERS];
|
|
142
|
-
for (const listener of listeners ?? []) {
|
|
143
|
-
listener(event);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function emitRuntimeGoal(ref, event) {
|
|
147
|
-
const listeners = ref[GOAL_LISTENERS];
|
|
148
|
-
for (const listener of listeners ?? []) {
|
|
149
|
-
listener(event);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function bindRuntimePongBallHandle(ref, controls) {
|
|
153
|
-
ref[RUNTIME_BALL_SET_POSITION] = controls.setPosition;
|
|
154
|
-
ref[RUNTIME_BALL_SET_VELOCITY] = controls.setVelocity;
|
|
155
|
-
ref[RUNTIME_BALL_GET_POSITION] = controls.getPosition;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export {
|
|
159
|
-
RUNTIME_PONG_BALL_BEHAVIOR_KEY,
|
|
160
|
-
RUNTIME_PADDLE_INPUT_2D_BEHAVIOR_KEY,
|
|
161
|
-
RUNTIME_WORLD_BOUNDARY_2D_BEHAVIOR_KEY,
|
|
162
|
-
RUNTIME_RICOCHET_2D_BEHAVIOR_KEY,
|
|
163
|
-
RUNTIME_TRIGGER_REGION_2D_BEHAVIOR_KEY,
|
|
164
|
-
RUNTIME_GOAL_ZONE_2D_BEHAVIOR_KEY,
|
|
165
|
-
RuntimePongBallBehavior,
|
|
166
|
-
RuntimePaddleInput2DBehavior,
|
|
167
|
-
RuntimeWorldBoundary2DBehavior,
|
|
168
|
-
RuntimeRicochet2DBehavior,
|
|
169
|
-
RuntimeTriggerRegion2DBehavior,
|
|
170
|
-
RuntimeGoalZone2DBehavior,
|
|
171
|
-
emitRuntimeRicochet,
|
|
172
|
-
emitRuntimeTriggerRegionEnter,
|
|
173
|
-
emitRuntimeGoal,
|
|
174
|
-
bindRuntimePongBallHandle
|
|
175
|
-
};
|
|
176
|
-
//# sourceMappingURL=chunk-2C3Z5GE7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/runtime-pong/runtime-pong.descriptors.ts"],"sourcesContent":["import {\n\tdefineBehavior,\n\ttype BehaviorRef,\n} from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\n\nexport interface RuntimePongBallOptions {\n\tinitialVelocity: readonly [number, number];\n}\n\nexport interface RuntimePongBallHandle {\n\tsetRuntimePosition(x: number, y: number, z?: number): void;\n\tsetRuntimeVelocity(x: number, y: number): void;\n\tgetRuntimePosition(): { x: number; y: number; z: number } | null;\n}\n\nexport interface RuntimePaddleInput2DOptions {\n\tplayer: 'p1' | 'p2';\n\tspeed: number;\n}\n\nexport interface RuntimeWorldBoundary2DOptions {\n\tboundaries: {\n\t\tleft: number;\n\t\tright: number;\n\t\tbottom: number;\n\t\ttop: number;\n\t};\n}\n\nexport interface RuntimeRicochet2DOptions {\n\tminSpeed: number;\n\tmaxSpeed: number;\n\tspeedMultiplier: number;\n\treflectionMode: 'simple' | 'angled';\n\tmaxAngleDeg: number;\n}\n\nexport interface RuntimeRicochet2DEvent {\n\tkind: 'wall' | 'paddle';\n}\n\nexport interface RuntimeRicochet2DHandle {\n\tonRicochet(callback: (event: RuntimeRicochet2DEvent) => void): () => void;\n}\n\n/** Registers a gameplay2d trigger AABB (sensor); wasm reports overlap via {@link emitRuntimeTriggerRegionEnter}. */\nexport interface RuntimeTriggerRegion2DOptions {\n\ttriggerId: number;\n\thalfExtents: readonly [number, number];\n}\n\nexport interface RuntimeTriggerRegion2DEvent {\n\ttriggerId: number;\n}\n\nexport interface RuntimeTriggerRegion2DHandle {\n\tonRegionEnter(callback: (event: RuntimeTriggerRegion2DEvent) => void): () => void;\n}\n\nexport interface RuntimeGoalZone2DOptions {\n\tscorer: 'p1' | 'p2';\n}\n\nexport interface RuntimeGoalZone2DEvent {\n\tscorer: 'p1' | 'p2';\n}\n\nexport interface RuntimeGoalZone2DHandle {\n\tonGoal(callback: (event: RuntimeGoalZone2DEvent) => void): () => void;\n}\n\nconst runtimeNoopSystemFactory = (): BehaviorSystem => ({\n\tupdate(_ecs: unknown, _delta: number) {},\n});\n\nexport const RUNTIME_PONG_BALL_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-pong-ball');\nexport const RUNTIME_PADDLE_INPUT_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-paddle-input-2d');\nexport const RUNTIME_WORLD_BOUNDARY_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-world-boundary-2d');\nexport const RUNTIME_RICOCHET_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-ricochet-2d');\nexport const RUNTIME_TRIGGER_REGION_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-trigger-region-2d');\nexport const RUNTIME_GOAL_ZONE_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:runtime-goal-zone-2d');\n\nconst RICOCHET_LISTENERS = Symbol('runtime-ricochet-listeners');\nconst TRIGGER_REGION_LISTENERS = Symbol('runtime-trigger-region-listeners');\nconst GOAL_LISTENERS = Symbol('runtime-goal-listeners');\nconst RUNTIME_BALL_SET_POSITION = Symbol('runtime-ball-set-position');\nconst RUNTIME_BALL_SET_VELOCITY = Symbol('runtime-ball-set-velocity');\nconst RUNTIME_BALL_GET_POSITION = Symbol('runtime-ball-get-position');\n\nexport const RuntimePongBallBehavior = defineBehavior<\n\tRuntimePongBallOptions,\n\tRuntimePongBallHandle\n>({\n\tname: 'runtime-pong-ball',\n\tdefaultOptions: {\n\t\tinitialVelocity: [0, 0] as [number, number],\n\t},\n\tsystemFactory: runtimeNoopSystemFactory,\n\tcreateHandle: (ref: BehaviorRef<RuntimePongBallOptions>) => ({\n\t\tsetRuntimePosition(x: number, y: number, z: number = 0): void {\n\t\t\tconst callback = (ref as any)[RUNTIME_BALL_SET_POSITION] as\n\t\t\t\t| ((x: number, y: number, z: number) => void)\n\t\t\t\t| undefined;\n\t\t\tcallback?.(x, y, z);\n\t\t},\n\t\tsetRuntimeVelocity(x: number, y: number): void {\n\t\t\tconst callback = (ref as any)[RUNTIME_BALL_SET_VELOCITY] as\n\t\t\t\t| ((x: number, y: number) => void)\n\t\t\t\t| undefined;\n\t\t\tcallback?.(x, y);\n\t\t},\n\t\tgetRuntimePosition(): { x: number; y: number; z: number } | null {\n\t\t\tconst callback = (ref as any)[RUNTIME_BALL_GET_POSITION] as\n\t\t\t\t| (() => { x: number; y: number; z: number } | null)\n\t\t\t\t| undefined;\n\t\t\treturn callback?.() ?? null;\n\t\t},\n\t}),\n});\n\nexport const RuntimePaddleInput2DBehavior = defineBehavior<RuntimePaddleInput2DOptions>({\n\tname: 'runtime-paddle-input-2d',\n\tdefaultOptions: {\n\t\tplayer: 'p1',\n\t\tspeed: 5,\n\t} as RuntimePaddleInput2DOptions,\n\tsystemFactory: runtimeNoopSystemFactory,\n});\n\nexport const RuntimeWorldBoundary2DBehavior = defineBehavior({\n\tname: 'runtime-world-boundary-2d',\n\tdefaultOptions: {\n\t\tboundaries: {\n\t\t\tleft: 0,\n\t\t\tright: 0,\n\t\t\tbottom: 0,\n\t\t\ttop: 0,\n\t\t},\n\t},\n\tsystemFactory: runtimeNoopSystemFactory,\n});\n\nexport const RuntimeRicochet2DBehavior = defineBehavior<\n\tRuntimeRicochet2DOptions,\n\tRuntimeRicochet2DHandle\n>({\n\tname: 'runtime-ricochet-2d',\n\tdefaultOptions: {\n\t\tminSpeed: 2,\n\t\tmaxSpeed: 20,\n\t\tspeedMultiplier: 1.05,\n\t\treflectionMode: 'angled' as const,\n\t\tmaxAngleDeg: 60,\n\t},\n\tsystemFactory: runtimeNoopSystemFactory,\n\tcreateHandle: (ref: BehaviorRef<RuntimeRicochet2DOptions>) => ({\n\t\tonRicochet(callback: (event: RuntimeRicochet2DEvent) => void): () => void {\n\t\t\tconst listeners =\n\t\t\t\t((ref as any)[RICOCHET_LISTENERS] as Array<(event: RuntimeRicochet2DEvent) => void> | undefined)\n\t\t\t\t?? [];\n\t\t\tif (!(ref as any)[RICOCHET_LISTENERS]) {\n\t\t\t\t(ref as any)[RICOCHET_LISTENERS] = listeners;\n\t\t\t}\n\t\t\tlisteners.push(callback);\n\t\t\treturn () => {\n\t\t\t\tconst index = listeners.indexOf(callback);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\tlisteners.splice(index, 1);\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t}),\n});\n\nexport const RuntimeTriggerRegion2DBehavior = defineBehavior<\n\tRuntimeTriggerRegion2DOptions,\n\tRuntimeTriggerRegion2DHandle\n>({\n\tname: 'runtime-trigger-region-2d',\n\tdefaultOptions: {\n\t\ttriggerId: 0,\n\t\thalfExtents: [0.5, 2.5] as [number, number],\n\t},\n\tsystemFactory: runtimeNoopSystemFactory,\n\tcreateHandle: (ref: BehaviorRef<RuntimeTriggerRegion2DOptions>) => ({\n\t\tonRegionEnter(callback: (event: RuntimeTriggerRegion2DEvent) => void): () => void {\n\t\t\tconst listeners =\n\t\t\t\t((ref as any)[TRIGGER_REGION_LISTENERS] as\n\t\t\t\t\t| Array<(event: RuntimeTriggerRegion2DEvent) => void>\n\t\t\t\t\t| undefined) ?? [];\n\t\t\tif (!(ref as any)[TRIGGER_REGION_LISTENERS]) {\n\t\t\t\t(ref as any)[TRIGGER_REGION_LISTENERS] = listeners;\n\t\t\t}\n\t\t\tlisteners.push(callback);\n\t\t\treturn () => {\n\t\t\t\tconst index = listeners.indexOf(callback);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\tlisteners.splice(index, 1);\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t}),\n});\n\nexport const RuntimeGoalZone2DBehavior = defineBehavior<\n\tRuntimeGoalZone2DOptions,\n\tRuntimeGoalZone2DHandle\n>({\n\tname: 'runtime-goal-zone-2d',\n\tdefaultOptions: {\n\t\tscorer: 'p1',\n\t} as RuntimeGoalZone2DOptions,\n\tsystemFactory: runtimeNoopSystemFactory,\n\tcreateHandle: (ref: BehaviorRef<RuntimeGoalZone2DOptions>) => ({\n\t\tonGoal(callback: (event: RuntimeGoalZone2DEvent) => void): () => void {\n\t\t\tconst listeners =\n\t\t\t\t((ref as any)[GOAL_LISTENERS] as Array<(event: RuntimeGoalZone2DEvent) => void> | undefined)\n\t\t\t\t?? [];\n\t\t\tif (!(ref as any)[GOAL_LISTENERS]) {\n\t\t\t\t(ref as any)[GOAL_LISTENERS] = listeners;\n\t\t\t}\n\t\t\tlisteners.push(callback);\n\t\t\treturn () => {\n\t\t\t\tconst index = listeners.indexOf(callback);\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\tlisteners.splice(index, 1);\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t}),\n});\n\nexport function emitRuntimeRicochet(\n\tref: BehaviorRef<RuntimeRicochet2DOptions>,\n\tevent: RuntimeRicochet2DEvent,\n): void {\n\tconst listeners = (ref as any)[RICOCHET_LISTENERS] as\n\t\t| Array<(event: RuntimeRicochet2DEvent) => void>\n\t\t| undefined;\n\tfor (const listener of listeners ?? []) {\n\t\tlistener(event);\n\t}\n}\n\nexport function emitRuntimeTriggerRegionEnter(\n\tref: BehaviorRef<RuntimeTriggerRegion2DOptions>,\n\tevent: RuntimeTriggerRegion2DEvent,\n): void {\n\tconst listeners = (ref as any)[TRIGGER_REGION_LISTENERS] as\n\t\t| Array<(event: RuntimeTriggerRegion2DEvent) => void>\n\t\t| undefined;\n\tfor (const listener of listeners ?? []) {\n\t\tlistener(event);\n\t}\n}\n\nexport function emitRuntimeGoal(\n\tref: BehaviorRef<RuntimeGoalZone2DOptions>,\n\tevent: RuntimeGoalZone2DEvent,\n): void {\n\tconst listeners = (ref as any)[GOAL_LISTENERS] as\n\t\t| Array<(event: RuntimeGoalZone2DEvent) => void>\n\t\t| undefined;\n\tfor (const listener of listeners ?? []) {\n\t\tlistener(event);\n\t}\n}\n\nexport function bindRuntimePongBallHandle(\n\tref: BehaviorRef<RuntimePongBallOptions>,\n\tcontrols: {\n\t\tsetPosition: (x: number, y: number, z: number) => void;\n\t\tsetVelocity: (x: number, y: number) => void;\n\t\tgetPosition: () => { x: number; y: number; z: number } | null;\n\t},\n): void {\n\t(ref as any)[RUNTIME_BALL_SET_POSITION] = controls.setPosition;\n\t(ref as any)[RUNTIME_BALL_SET_VELOCITY] = controls.setVelocity;\n\t(ref as any)[RUNTIME_BALL_GET_POSITION] = controls.getPosition;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/thruster/thruster-fsm.ts","../src/lib/behaviors/thruster/thruster-movement.behavior.ts","../src/lib/behaviors/thruster/thruster.descriptor.ts"],"sourcesContent":["/**\n * ThrusterFSM\n * \n * State machine controller for thruster behavior.\n * FSM does NOT touch physics or ThrusterMovementBehavior - it only writes ThrusterInputComponent.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { ThrusterInputComponent } from './components';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterState {\n\tIdle = 'idle',\n\tActive = 'active',\n\tBoosting = 'boosting',\n\tDisabled = 'disabled',\n\tDocked = 'docked',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterEvent {\n\tActivate = 'activate',\n\tDeactivate = 'deactivate',\n\tBoost = 'boost',\n\tEndBoost = 'endBoost',\n\tDisable = 'disable',\n\tEnable = 'enable',\n\tDock = 'dock',\n\tUndock = 'undock',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Context Object\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterFSMContext {\n\tinput: ThrusterInputComponent;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Player Input (raw input from controller/keyboard)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PlayerInput {\n\tthrust: number;\n\trotate: number;\n\tthrustX?: number;\n\tthrustY?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterFSM Controller\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ThrusterFSM {\n\tmachine: SyncStateMachine<ThrusterState, ThrusterEvent, never>;\n\n\tconstructor(private ctx: ThrusterFSMContext) {\n\t\tthis.machine = new SyncStateMachine<ThrusterState, ThrusterEvent, never>(\n\t\t\tThrusterState.Idle,\n\t\t\t[\n\t\t\t\t// Core transitions\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Boost, ThrusterState.Boosting),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Dock, ThrusterState.Docked),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.EndBoost, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Disabled, ThrusterEvent.Enable, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Docked, ThrusterEvent.Undock, ThrusterState.Idle),\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): ThrusterState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to transition state\n\t */\n\tdispatch(event: ThrusterEvent): 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 * Update FSM state based on player input.\n\t * Auto-transitions between Idle/Active to report current state.\n\t * Does NOT modify input - just observes and reports.\n\t */\n\tupdate(playerInput: PlayerInput): void {\n\t\tconst state = this.machine.getState();\n\t\tconst hasInput =\n\t\t\tMath.abs(playerInput.thrust) > 0.01\n\t\t\t|| Math.abs(playerInput.rotate) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustX ?? 0) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustY ?? 0) > 0.01;\n\n\t\t// Auto-transition to report state based on input\n\t\tif (hasInput && state === ThrusterState.Idle) {\n\t\t\tthis.dispatch(ThrusterEvent.Activate);\n\t\t} else if (!hasInput && state === ThrusterState.Active) {\n\t\t\tthis.dispatch(ThrusterEvent.Deactivate);\n\t\t}\n\t}\n}\n","/**\n * ThrusterMovementBehavior\n * \n * This is the heart of the thruster movement system - a pure, stateless force generator.\n * Works identically for player, AI, and replay.\n */\n\nimport type { ZylemWorld } from '../../collision/world';\nimport type { PhysicsBodyComponent } from '../components';\nimport type { ThrusterMovementComponent, ThrusterInputComponent } from './components';\nimport {\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n} from '../shared/direction-2d';\n\n/**\n * Zylem-style Behavior interface\n */\nexport interface Behavior {\n\tupdate(dt: number): void;\n}\n\n/**\n * Entity with thruster components\n */\nexport interface ThrusterEntity {\n\tphysics: PhysicsBodyComponent;\n\tthruster: ThrusterMovementComponent;\n\t$thruster: ThrusterInputComponent;\n}\n\n/**\n * ThrusterMovementBehavior - Force generator for thruster-equipped entities\n * \n * Responsibilities:\n * - Query entities with PhysicsBody, ThrusterMovement, and ThrusterInput components\n * - Apply velocities based on thrust input (2D mode)\n * - Apply angular velocity based on rotation input\n */\nexport class ThrusterMovementBehavior implements Behavior {\n\tconstructor(private world: ZylemWorld) {}\n\n\t/**\n\t * Update a single thruster-enabled entity.\n\t */\n\tupdateEntity(gameEntity: any, _dt: number): void {\n\t\tif (!gameEntity.physics?.body || !gameEntity.thruster || !gameEntity.$thruster) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst e: ThrusterEntity = {\n\t\t\tphysics: gameEntity.physics,\n\t\t\tthruster: gameEntity.thruster,\n\t\t\t$thruster: gameEntity.$thruster,\n\t\t};\n\n\t\tconst body = e.physics.body;\n\t\tconst thruster = e.thruster;\n\t\tconst input = e.$thruster;\n\n\t\tif (thruster.linearDamping != null && typeof body.setLinearDamping === 'function') {\n\t\t\tbody.setLinearDamping(thruster.linearDamping);\n\t\t}\n\n\t\t// Get Z rotation from quaternion (for 2D sprites)\n\t\tconst rotationZ = getRotationAngle2D(body.rotation());\n\t\tconst currentVel = body.linvel();\n\t\tconst vectorThrust = normalizeDirection2D(input.thrustX, input.thrustY);\n\n\t\t// --- Linear thrust ---\n\t\tif (vectorThrust) {\n\t\t\tconst thrustScale = Math.min(1, Math.hypot(input.thrustX, input.thrustY));\n\t\t\tconst thrustAmount = thruster.linearThrust * thrustScale * 0.1;\n\t\t\tbody.setLinvel({\n\t\t\t\tx: currentVel.x + vectorThrust.x * thrustAmount,\n\t\t\t\ty: currentVel.y + vectorThrust.y * thrustAmount,\n\t\t\t\tz: currentVel.z,\n\t\t\t}, true);\n\t\t} else if (input.thrust !== 0) {\n\t\t\tconst forwardX = Math.sin(-rotationZ);\n\t\t\tconst forwardY = Math.cos(-rotationZ);\n\t\t\tconst thrustAmount = thruster.linearThrust * input.thrust * 0.1;\n\t\t\tbody.setLinvel({\n\t\t\t\tx: currentVel.x + forwardX * thrustAmount,\n\t\t\t\ty: currentVel.y + forwardY * thrustAmount,\n\t\t\t\tz: currentVel.z\n\t\t\t}, true);\n\t\t}\n\n\t\t// --- Angular thrust (Z-axis for 2D) ---\n\t\tif (input.rotate !== 0) {\n\t\t\tbody.setAngvel({ x: 0, y: 0, z: -thruster.angularThrust * input.rotate }, true);\n\t\t} else {\n\t\t\t// Stop rotation when no input\n\t\t\tconst angVel = body.angvel();\n\t\t\tbody.setAngvel({ x: angVel.x, y: angVel.y, z: 0 }, true);\n\t\t}\n\t}\n\n\tupdate(dt: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tthis.updateEntity(entity, dt);\n\t\t}\n\t}\n}\n","/**\n * Thruster Behavior Descriptor\n * \n * Type-safe descriptor for the thruster behavior system using the new entity.use() API.\n * This wraps the existing ThrusterMovementBehavior and components.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ThrusterMovementBehavior, ThrusterEntity } from './thruster-movement.behavior';\nimport { ThrusterFSM } from './thruster-fsm';\nimport {\n\tcreateThrusterInputComponent,\n\tcreateThrusterMovementComponent,\n\ttype ThrusterMovementComponent,\n\ttype ThrusterInputComponent,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Thruster behavior options (typed for entity.use() autocomplete)\n */\nexport interface ThrusterBehaviorOptions {\n\t/** Forward thrust force (default: 10) */\n\tlinearThrust: number;\n\t/** Rotation torque (default: 5) */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n}\n\nconst defaultOptions: ThrusterBehaviorOptions = {\n\tlinearThrust: 10,\n\tangularThrust: 5,\n\tlinearDamping: undefined,\n};\n\nconst THRUSTER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:thruster');\n\n/**\n * Adapter that wraps ThrusterMovementBehavior as a BehaviorSystem.\n * \n * This bridges the entity.use() pattern with the ECS component-based approach:\n * - Reads options from entity behaviorRefs\n * - Initializes ThrusterMovementComponent and ThrusterInputComponent on entities\n * - Creates FSM lazily and attaches to BehaviorRef for access via handle.getFSM()\n * - Delegates physics to ThrusterMovementBehavior\n * \n * NOTE: Input is controlled by the user via entity.onUpdate() - set entity.input.thrust/rotate\n */\nclass ThrusterBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: ThrusterMovementBehavior;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new ThrusterMovementBehavior(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(THRUSTER_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 thrusterRef = link.ref as any;\n\n\t\t\tconst options = thrusterRef.options as ThrusterBehaviorOptions;\n\n\t\t\tif (!gameEntity.thruster) {\n\t\t\t\tgameEntity.thruster = createThrusterMovementComponent(\n\t\t\t\t\toptions.linearThrust,\n\t\t\t\t\toptions.angularThrust,\n\t\t\t\t\t{ linearDamping: options.linearDamping },\n\t\t\t\t) as ThrusterMovementComponent;\n\t\t\t}\n\n\t\t\tif (!gameEntity.$thruster) {\n\t\t\t\tgameEntity.$thruster = createThrusterInputComponent() as ThrusterInputComponent;\n\t\t\t}\n\n\t\t\tif (!thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm = new ThrusterFSM({ input: gameEntity.$thruster });\n\t\t\t}\n\n\t\t\tif (thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm.update({\n\t\t\t\t\tthrust: gameEntity.$thruster.thrust,\n\t\t\t\t\trotate: gameEntity.$thruster.rotate,\n\t\t\t\t\tthrustX: gameEntity.$thruster.thrustX,\n\t\t\t\t\tthrustY: gameEntity.$thruster.thrustY,\n\t\t\t\t});\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.$thruster as ThrusterInputComponent | undefined;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.setThrusterInput(handle, input.thrust, input.rotate);\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\tif (!gameEntity.physics) {\n\t\t\t\tgameEntity.physics = { body: gameEntity.body };\n\t\t\t}\n\t\t\tthis.movementBehavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ThrusterBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachThruster(handle, {\n\t\t\tmaxSpeed: 50,\n\t\t\tacceleration: options.linearThrust,\n\t\t\tturnRateRadPerSec: options.angularThrust,\n\t\t\tlinearDamping: options.linearDamping ?? 0,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ThrusterBehavior - typed descriptor for thruster movement.\n * \n * Uses the existing ThrusterMovementBehavior under the hood.\n * \n * @example\n * ```typescript\n * import { ThrusterBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const ship = createSprite({ ... });\n * ship.use(ThrusterBehavior, { linearThrust: 15, angularThrust: 8 });\n * ```\n */\nexport const ThrusterBehavior = defineBehavior<ThrusterBehaviorOptions, Record<string, never>, ThrusterEntity>({\n\tname: 'thruster',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ThrusterBehaviorSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ThrusterState","ThrusterEvent"]}
|
package/dist/chunk-3PYGRHAL.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getBounds3DNormalFromHits
|
|
3
|
-
} from "./chunk-OHCPV2NT.js";
|
|
4
|
-
|
|
5
|
-
// src/lib/coordinators/boundary-ricochet-3d.coordinator.ts
|
|
6
|
-
var BoundaryRicochet3DCoordinator = class {
|
|
7
|
-
constructor(entity, boundary, ricochet) {
|
|
8
|
-
this.entity = entity;
|
|
9
|
-
this.boundary = boundary;
|
|
10
|
-
this.ricochet = ricochet;
|
|
11
|
-
}
|
|
12
|
-
entity;
|
|
13
|
-
boundary;
|
|
14
|
-
ricochet;
|
|
15
|
-
update() {
|
|
16
|
-
const hits = this.boundary.getLastHits();
|
|
17
|
-
if (!hits) return false;
|
|
18
|
-
const anyHit = hits.left || hits.right || hits.top || hits.bottom || hits.back || hits.front;
|
|
19
|
-
if (!anyHit) return false;
|
|
20
|
-
const clamped = this.boundary.getLastClampedPosition();
|
|
21
|
-
if (clamped && this.entity.body) {
|
|
22
|
-
this.entity.body.setTranslation(clamped, true);
|
|
23
|
-
}
|
|
24
|
-
const normal = getBounds3DNormalFromHits(hits);
|
|
25
|
-
return this.ricochet.applyRicochet({
|
|
26
|
-
entity: this.entity,
|
|
27
|
-
contact: { normal }
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
BoundaryRicochet3DCoordinator
|
|
34
|
-
};
|
|
35
|
-
//# sourceMappingURL=chunk-3PYGRHAL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/coordinators/boundary-ricochet-3d.coordinator.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport type { MoveableEntity } from '../actions/capabilities/moveable';\nimport type { Ricochet3DHandle } from '../behaviors/ricochet-3d/ricochet-3d.descriptor';\nimport type { WorldBoundary3DHandle } from '../behaviors/world-boundary-3d/world-boundary-3d.descriptor';\nimport { getBounds3DNormalFromHits } from '../behaviors/shared/bounds-3d';\n\nexport class BoundaryRicochet3DCoordinator {\n\tconstructor(\n\t\tprivate entity: GameEntity<any> & MoveableEntity,\n\t\tprivate boundary: WorldBoundary3DHandle,\n\t\tprivate ricochet: Ricochet3DHandle,\n\t) {}\n\n\tpublic update(): boolean {\n\t\tconst hits = this.boundary.getLastHits();\n\t\tif (!hits) return false;\n\n\t\tconst anyHit =\n\t\t\thits.left || hits.right || hits.top || hits.bottom || hits.back || hits.front;\n\t\tif (!anyHit) return false;\n\n\t\tconst clamped = this.boundary.getLastClampedPosition();\n\t\tif (clamped && this.entity.body) {\n\t\t\tthis.entity.body.setTranslation(clamped, true);\n\t\t}\n\n\t\tconst normal = getBounds3DNormalFromHits(hits);\n\t\treturn this.ricochet.applyRicochet({\n\t\t\tentity: this.entity,\n\t\t\tcontact: { normal },\n\t\t});\n\t}\n}\n"],"mappings":";;;;;AAMO,IAAM,gCAAN,MAAoC;AAAA,EAC1C,YACS,QACA,UACA,UACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EAGF,SAAkB;AACxB,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,SACL,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK;AACzE,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,UAAU,KAAK,SAAS,uBAAuB;AACrD,QAAI,WAAW,KAAK,OAAO,MAAM;AAChC,WAAK,OAAO,KAAK,eAAe,SAAS,IAAI;AAAA,IAC9C;AAEA,UAAM,SAAS,0BAA0B,IAAI;AAC7C,WAAO,KAAK,SAAS,cAAc;AAAA,MAClC,QAAQ,KAAK;AAAA,MACb,SAAS,EAAE,OAAO;AAAA,IACnB,CAAC;AAAA,EACF;AACD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/coordinators/shared/top-down-aim.ts"],"sourcesContent":["import {\n\tangleFromDirection2D,\n\tdirectionFromAngle2D,\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n} from '../../behaviors/shared/direction-2d';\n\nexport interface ResolvedAim2D {\n\tangle: number;\n\tdirection: { x: number; y: number };\n\thasAimInput: boolean;\n}\n\nexport function getEntityFacingAngle2D(entity: any): number {\n\tif (entity.topDownMovementState) {\n\t\treturn entity.topDownMovementState.facingAngle;\n\t}\n\n\tif (entity.body?.rotation) {\n\t\treturn getRotationAngle2D(entity.body.rotation());\n\t}\n\n\treturn entity._rotation2DAngle ?? 0;\n}\n\nexport function resolveAim2D(\n\taimX: number,\n\taimY: number,\n\tfallbackAngle: number,\n): ResolvedAim2D {\n\tconst direction = normalizeDirection2D(aimX, aimY);\n\tif (direction) {\n\t\treturn {\n\t\t\tangle: angleFromDirection2D(direction),\n\t\t\tdirection,\n\t\t\thasAimInput: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tangle: fallbackAngle,\n\t\tdirection: directionFromAngle2D(fallbackAngle),\n\t\thasAimInput: false,\n\t};\n}\n"],"mappings":";;;;;;;;AAaO,SAAS,uBAAuB,QAAqB;AAC3D,MAAI,OAAO,sBAAsB;AAChC,WAAO,OAAO,qBAAqB;AAAA,EACpC;AAEA,MAAI,OAAO,MAAM,UAAU;AAC1B,WAAO,mBAAmB,OAAO,KAAK,SAAS,CAAC;AAAA,EACjD;AAEA,SAAO,OAAO,oBAAoB;AACnC;AAEO,SAAS,aACf,MACA,MACA,eACgB;AAChB,QAAM,YAAY,qBAAqB,MAAM,IAAI;AACjD,MAAI,WAAW;AACd,WAAO;AAAA,MACN,OAAO,qBAAqB,SAAS;AAAA,MACrC;AAAA,MACA,aAAa;AAAA,IACd;AAAA,EACD;AAEA,SAAO;AAAA,IACN,OAAO;AAAA,IACP,WAAW,qBAAqB,aAAa;AAAA,IAC7C,aAAa;AAAA,EACd;AACD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/jumper-3d/jumper-3d.behavior.ts","../src/lib/behaviors/jumper-3d/jumper-3d-fsm.ts","../src/lib/behaviors/jumper-3d/jumper-3d.descriptor.ts"],"sourcesContent":["/**\n * Jumper 3D Behavior — Pure Jump Physics\n *\n * A stateless tick function that receives configuration, input, context, and\n * state, then applies jump physics through the context's velocity helpers.\n *\n * Vertical velocity (Y) is owned exclusively by this behavior.\n * Horizontal velocity (X/Z) is only touched when airborne planar control\n * is configured; otherwise the movement controller owns X/Z.\n */\n\nimport type {\n\tJumpConfig3D,\n\tJumpInput3D,\n\tJumpContext3D,\n\tJumpState3D,\n} from './components';\n\n/** v = sqrt(2 * g * h) */\nfunction jumpVelocityFromHeight(gravity: number, height: number): number {\n\treturn Math.sqrt(2 * gravity * height);\n}\n\nfunction planarSpeed(x: number, z: number): number {\n\treturn Math.sqrt(x * x + z * z);\n}\n\nfunction normalizeMut(v: { x: number; y: number; z: number }): number {\n\tconst len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);\n\tif (len > 1e-6) {\n\t\tv.x /= len;\n\t\tv.y /= len;\n\t\tv.z /= len;\n\t}\n\treturn len;\n}\n\nexport class Jumper3DBehavior {\n\t/**\n\t * Advance the jump system by one frame.\n\t *\n\t * Call order within a frame:\n\t * 1. Movement controller writes X/Z via transformStore.dirty.velocityX/Z.\n\t * 2. `tick()` writes Y via ctx.setVerticalVelocity().\n\t * (Optionally writes X/Z when airborne planar control is active.)\n\t */\n\ttick(\n\t\tconfig: JumpConfig3D,\n\t\tinput: JumpInput3D,\n\t\tctx: JumpContext3D,\n\t\tstate: JumpState3D,\n\t): JumperTickResult {\n\t\tconst dtMs = ctx.dt * 1000;\n\t\tconst now = performance.now();\n\t\tlet event: JumperTickEvent = JumperTickEvent.None;\n\t\tlet jumpedThisFrame = false;\n\n\t\t// ── 1. Ground-contact bookkeeping ──────────────────────────────────\n\t\tif (ctx.isGrounded) {\n\t\t\tstate.coyoteMs = config.coyoteTimeMs;\n\t\t\tif (config.resetJumpsOnGround) {\n\t\t\t\tstate.jumpsUsed = 0;\n\t\t\t}\n\t\t\tif (state.isJumping) {\n\t\t\t\tstate.isJumping = false;\n\t\t\t\tstate.jumpCutApplied = false;\n\t\t\t\tevent = JumperTickEvent.Land;\n\t\t\t}\n\t\t} else {\n\t\t\tstate.coyoteMs = Math.max(0, state.coyoteMs - dtMs);\n\t\t}\n\n\t\t// ── 2. Jump buffer ─────────────────────────────────────────────────\n\t\tif (input.jumpPressed) {\n\t\t\tstate.bufferedJumpMs = config.jumpBufferMs;\n\t\t} else {\n\t\t\tstate.bufferedJumpMs = Math.max(0, state.bufferedJumpMs - dtMs);\n\t\t}\n\n\t\t// ── 3. Variable-jump hold tracking ─────────────────────────────────\n\t\tif (state.isJumping && input.jumpHeld) {\n\t\t\tstate.jumpHoldMs += dtMs;\n\t\t}\n\n\t\t// ── 4. Jump eligibility & execution ────────────────────────────────\n\t\tconst wantsJump = state.bufferedJumpMs > 0;\n\n\t\tconst isFirstJump =\n\t\t\tctx.isGrounded || (state.coyoteMs > 0 && state.jumpsUsed === 0);\n\t\tconst hasAirJumps = state.jumpsUsed > 0 && state.jumpsUsed < config.maxJumps;\n\t\tconst canJump = isFirstJump || hasAirJumps;\n\n\t\t\tconst cooldownOk =\n\t\t\t\tconfig.minTimeBetweenJumpsMs == null ||\n\t\t\t\tnow - state.lastJumpTimeMs >= config.minTimeBetweenJumpsMs;\n\n\t\t\tif (wantsJump && canJump && cooldownOk) {\n\t\t\t\tconst vy = jumpVelocityFromHeight(config.gravity, config.jumpHeight);\n\n\t\t\t\tctx.setVerticalVelocity(vy);\n\t\t\t\tjumpedThisFrame = true;\n\n\t\t\tif (config.planar?.launchPlanarSpeed != null && !config.planar.preservePlanarSpeed) {\n\t\t\t\tconst dir = input.moveDirWorld;\n\t\t\t\tif (dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6)) {\n\t\t\t\t\tconst d = { x: dir.x, y: 0, z: dir.z };\n\t\t\t\t\tnormalizeMut(d);\n\t\t\t\t\tctx.setHorizontalVelocity(\n\t\t\t\t\t\td.x * config.planar.launchPlanarSpeed,\n\t\t\t\t\t\td.z * config.planar.launchPlanarSpeed,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstate.jumpsUsed++;\n\t\t\tstate.lastJumpTimeMs = now;\n\t\t\tstate.bufferedJumpMs = 0;\n\t\t\tstate.coyoteMs = 0;\n\t\t\tstate.isJumping = true;\n\t\t\tstate.jumpHoldMs = 0;\n\t\t\tstate.jumpCutApplied = false;\n\n\t\t\tevent = JumperTickEvent.Jump;\n\t\t}\n\n\t\t// ── 5. Gravity (with variable-jump and fall multipliers) ───────────\n\t\t// Treat a jump-launch frame as airborne so we don't immediately clamp\n\t\t// the freshly applied jump velocity back to zero.\n\t\tif (!ctx.isGrounded || jumpedThisFrame) {\n\t\t\tlet gravityMul = 1;\n\t\t\tconst vy = ctx.velocityY;\n\t\t\tconst isFalling = vy < 0;\n\n\t\t\tif (isFalling) {\n\t\t\t\tgravityMul = config.fall?.fallGravityMultiplier ?? 1;\n\t\t\t\tif (input.fastFall && config.fall?.fastFallMultiplier != null) {\n\t\t\t\t\tgravityMul *= config.fall.fastFallMultiplier;\n\t\t\t\t}\n\t\t\t\tif (event !== JumperTickEvent.Land) {\n\t\t\t\t\tevent = JumperTickEvent.Fall;\n\t\t\t\t}\n\t\t\t} else if (state.isJumping) {\n\t\t\t\tconst vj = config.variableJump;\n\t\t\t\tif (vj?.enabled && !state.jumpCutApplied) {\n\t\t\t\t\tconst holdExpired =\n\t\t\t\t\t\tvj.maxHoldMs != null && state.jumpHoldMs >= vj.maxHoldMs;\n\t\t\t\t\tif (!input.jumpHeld || holdExpired) {\n\t\t\t\t\t\tgravityMul = vj.cutGravityMultiplier;\n\t\t\t\t\t\tstate.jumpCutApplied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet newVy = ctx.velocityY - config.gravity * gravityMul * ctx.dt;\n\n\t\t\tif (config.maxFallSpeed != null) {\n\t\t\t\tnewVy = Math.max(-config.maxFallSpeed, newVy);\n\t\t\t}\n\n\t\t\tctx.setVerticalVelocity(newVy);\n\t\t} else {\n\t\t\t// Grounded: clamp Y to 0 so the entity doesn't drift\n\t\t\tctx.setVerticalVelocity(0);\n\t\t}\n\n\t\t// ── 6. Airborne planar control ─────────────────────────────────────\n\t\tconst airCtrl = config.planar?.airControl;\n\t\tif (airCtrl && !ctx.isGrounded) {\n\t\t\tconst dir = input.moveDirWorld;\n\t\t\tconst hasDir = dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6);\n\n\t\t\tlet vx = ctx.horizontalVelocity.x;\n\t\t\tlet vz = ctx.horizontalVelocity.z;\n\n\t\t\tif (hasDir) {\n\t\t\t\tconst d = { x: dir!.x, y: 0, z: dir!.z };\n\t\t\t\tnormalizeMut(d);\n\n\t\t\t\tvx += d.x * airCtrl.acceleration * ctx.dt;\n\t\t\t\tvz += d.z * airCtrl.acceleration * ctx.dt;\n\n\t\t\t\tconst speed = Math.sqrt(vx * vx + vz * vz);\n\t\t\t\tif (speed > airCtrl.maxPlanarSpeed) {\n\t\t\t\t\tconst scale = airCtrl.maxPlanarSpeed / speed;\n\t\t\t\t\tvx *= scale;\n\t\t\t\t\tvz *= scale;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst speed = planarSpeed(vx, vz);\n\t\t\t\tif (speed > 1e-6) {\n\t\t\t\t\tconst decel = Math.min(airCtrl.deceleration * ctx.dt, speed);\n\t\t\t\t\tconst scale = (speed - decel) / speed;\n\t\t\t\t\tvx *= scale;\n\t\t\t\t\tvz *= scale;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.setHorizontalVelocity(vx, vz);\n\t\t}\n\n\t\treturn { event };\n\t}\n}\n\n/** Events emitted by a single tick (used by FSM). */\nexport enum JumperTickEvent {\n\tNone = 'none',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface JumperTickResult {\n\tevent: JumperTickEvent;\n}\n","/**\n * Jumper 3D Finite State Machine\n *\n * Three high-level states driven by events emitted from the behavior tick:\n * Grounded ──Jump──▶ Jumping\n * Grounded ──Fall──▶ Falling (walked off a ledge)\n * Jumping ──Fall──▶ Falling (apex / descending)\n * Jumping ──Jump──▶ Jumping (multi-jump)\n * Jumping ──Land──▶ Grounded (rare — very short hop)\n * Falling ──Land──▶ Grounded\n * Falling ──Jump──▶ Jumping (air-jump while falling)\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { JumpState3D } from './components';\n\nexport enum Jumper3DState {\n\tGrounded = 'grounded',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n}\n\nexport enum Jumper3DEvent {\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface Jumper3DFSMContext {\n\tstate: JumpState3D;\n}\n\nexport class Jumper3DFSM {\n\tmachine: SyncStateMachine<Jumper3DState, Jumper3DEvent, never>;\n\n\tconstructor(private ctx: Jumper3DFSMContext) {\n\t\tthis.machine = new SyncStateMachine<Jumper3DState, Jumper3DEvent, never>(\n\t\t\tJumper3DState.Grounded,\n\t\t\t[\n\t\t\t\t// Grounded\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\n\t\t\t\t// Jumping\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\n\t\t\t\t// Falling\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Jumper3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Jumper3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisJumping(): boolean {\n\t\treturn this.getState() === Jumper3DState.Jumping;\n\t}\n\n\tisFalling(): boolean {\n\t\treturn this.getState() === Jumper3DState.Falling;\n\t}\n\n\tisGrounded(): boolean {\n\t\treturn this.getState() === Jumper3DState.Grounded;\n\t}\n\n\tgetJumpsUsed(): number {\n\t\treturn this.ctx.state.jumpsUsed;\n\t}\n\n\t/**\n\t * Translate a tick event into an FSM dispatch.\n\t * Also handles the implicit Grounded→Falling transition when the entity\n\t * is no longer grounded but no explicit event was emitted (e.g. walked\n\t * off a ledge without jumping).\n\t */\n\tapplyTickEvent(tickEvent: string, isGrounded: boolean): void {\n\t\tswitch (tickEvent) {\n\t\t\tcase 'jump':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Jump);\n\t\t\t\tbreak;\n\t\t\tcase 'fall':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\tbreak;\n\t\t\tcase 'land':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// No explicit event — detect implicit fall\n\t\t\t\tif (!isGrounded && this.getState() === Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\t}\n\t\t\t\tif (isGrounded && this.getState() !== Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n","/**\n * Jumper 3D Behavior Descriptor\n *\n * Type-safe descriptor for the jumper-3d behavior system.\n * Provides `entity.use()` API for composable 3D jumping.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { setVelocityIntent } from '../../actions/capabilities/velocity-intents';\nimport { Jumper3DBehavior } from './jumper-3d.behavior';\nimport { Jumper3DFSM, Jumper3DState } from './jumper-3d-fsm';\nimport {\n\tGroundProbe3D,\n\tgetGroundAnchorOffsetY,\n\tgetGroundSnapTargetY,\n\ttype GroundProbeSupportHit,\n} from '../shared/ground-probe-3d';\nimport {\n\tcreateJumpConfig3D,\n\tcreateJumpInput3D,\n\tcreateJumpState3D,\n\ttype JumpConfig3D,\n\ttype JumpInput3D,\n\ttype JumpState3D,\n\ttype JumpContext3D,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options exposed through entity.use()\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DBehaviorOptions {\n\t/** Desired peak height in world units (default: 2.5) */\n\tjumpHeight?: number;\n\t/** Gravity magnitude applied by the behavior (default: 20) */\n\tgravity?: number;\n\t/** Terminal fall speed (optional) */\n\tmaxFallSpeed?: number;\n\t/** Max concurrent jumps, 1 = single, 2 = double, etc. (default: 1) */\n\tmaxJumps?: number;\n\t/** Reset jump counter on ground (default: true) */\n\tresetJumpsOnGround?: boolean;\n\t/** Reset jump counter on wall contact (default: undefined) */\n\tresetJumpsOnWall?: boolean;\n\t/** Coyote time in ms (default: 100) */\n\tcoyoteTimeMs?: number;\n\t/** Jump buffer in ms (default: 80) */\n\tjumpBufferMs?: number;\n\t/** Min cooldown between jumps in ms (optional) */\n\tminTimeBetweenJumpsMs?: number;\n\t/** Variable jump height config (optional) */\n\tvariableJump?: JumpConfig3D['variableJump'];\n\t/** Planar control config (optional) */\n\tplanar?: JumpConfig3D['planar'];\n\t/** Fall tuning (optional) */\n\tfall?: JumpConfig3D['fall'];\n\t/** Ground-detection ray length (default: 1.0) */\n\tgroundRayLength?: number;\n\t/** Max support distance that counts as grounded and can snap to ground */\n\tsnapToGroundDistance?: number;\n\t/** Enable debug visualization for ground probe rays (default: false) */\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Jumper3DBehaviorOptions = {\n\tjumpHeight: 2.5,\n\tgravity: 20,\n\tmaxJumps: 1,\n\tresetJumpsOnGround: true,\n\tcoyoteTimeMs: 100,\n\tjumpBufferMs: 80,\n\tgroundRayLength: 1.0,\n\tdebugGroundProbe: false,\n};\n\nconst JUMPER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:jumper-3d');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Entity shape expected by the system\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DEntity {\n\tuuid: string;\n\tbody: any;\n\ttransformStore: any;\n\tjumper: JumpConfig3D;\n\t$jumper: JumpInput3D;\n\tjumperState: JumpState3D;\n}\n\nexport function isJumper3DGrounded(params: {\n\tsupportToi?: number | null;\n\tvelocityY: number;\n\tsnapToGroundDistance?: number;\n}): boolean {\n\tif (params.snapToGroundDistance != null) {\n\t\treturn (\n\t\t\tparams.supportToi != null &&\n\t\t\tparams.supportToi <= params.snapToGroundDistance &&\n\t\t\tparams.velocityY <= 0.25\n\t\t);\n\t}\n\n\treturn (\n\t\tparams.supportToi != null &&\n\t\tparams.velocityY > -2.0 &&\n\t\tparams.velocityY < 2.0\n\t);\n}\n\nfunction shouldSnapJumper3DToGround(\n\tsupport: GroundProbeSupportHit | null,\n\toptions: Jumper3DBehaviorOptions,\n\tvelocityY: number,\n): support is GroundProbeSupportHit {\n\treturn (\n\t\tsupport != null &&\n\t\toptions.snapToGroundDistance != null &&\n\t\tvelocityY <= 0.25 &&\n\t\tsupport.toi <= options.snapToGroundDistance\n\t);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior System adapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass Jumper3DBehaviorSystem implements BehaviorSystem {\n\tprivate behavior = new Jumper3DBehavior();\n\tprivate groundProbe: GroundProbe3D;\n\tprivate initializedEntities = new Set<string>();\n\tprivate timeSinceGroundedMs = new Map<string, number>();\n\tprivate wasJumpHeld = new Map<string, boolean>();\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate scene: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.groundProbe = new GroundProbe3D(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(JUMPER_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 jumperRef = link.ref as any;\n\n\t\t\tconst options = jumperRef.options as Jumper3DBehaviorOptions;\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.$jumper as JumpInput3D | undefined;\n\t\t\t\tconst jumpPressed = !!(input?.jumpPressed || input?.jumpHeld);\n\t\t\t\tthis.wasmStage.setJumper3DInput(handle, jumpPressed);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\n\t\t\t// ── Lazy component init ────────────────────────────────────────\n\t\t\tif (!gameEntity.jumper) {\n\t\t\t\tgameEntity.jumper = createJumpConfig3D(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$jumper) {\n\t\t\t\tgameEntity.$jumper = createJumpInput3D();\n\t\t\t}\n\t\t\tif (!gameEntity.jumperState) {\n\t\t\t\tgameEntity.jumperState = createJumpState3D();\n\t\t\t}\n\n\t\t\tif (!this.initializedEntities.has(gameEntity.uuid)) {\n\t\t\t\tthis.initializedEntities.add(gameEntity.uuid);\n\t\t\t\tgameEntity.body.setGravityScale(0, true);\n\t\t\t}\n\n\t\t\t// ── Lazy FSM init ──────────────────────────────────────────────\n\t\t\tif (!jumperRef.fsm && gameEntity.jumperState) {\n\t\t\t\tjumperRef.fsm = new Jumper3DFSM({\n\t\t\t\t\tstate: gameEntity.jumperState,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// ── Ground detection ───────────────────────────────────────────\n\t\t\tconst rayLength = options.groundRayLength ?? 1.0;\n\t\t\tconst bodyVel = gameEntity.body.linvel();\n\t\t\tconst probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);\n\t\t\tconst support = this.groundProbe.probeSupport(gameEntity, {\n\t\t\t\trayLength,\n\t\t\t\tmode: 'center',\n\t\t\t\tdebug: options.debugGroundProbe ?? false,\n\t\t\t\tscene: this.scene,\n\t\t\t\toriginYOffset: probeOriginYOffset,\n\t\t\t});\n\t\t\tconst isGrounded = isJumper3DGrounded({\n\t\t\t\tsupportToi: support?.toi ?? null,\n\t\t\t\tvelocityY: bodyVel.y,\n\t\t\t\tsnapToGroundDistance: options.snapToGroundDistance,\n\t\t\t});\n\t\t\tif (shouldSnapJumper3DToGround(support, options, bodyVel.y)) {\n\t\t\t\tconst currentPosition = gameEntity.body.translation();\n\t\t\t\tgameEntity.body.setTranslation(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: currentPosition.x,\n\t\t\t\t\t\ty: getGroundSnapTargetY(gameEntity, support),\n\t\t\t\t\t\tz: currentPosition.z,\n\t\t\t\t\t},\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet tsg = this.timeSinceGroundedMs.get(gameEntity.uuid) ?? 0;\n\t\t\tif (isGrounded) {\n\t\t\t\ttsg = 0;\n\t\t\t} else {\n\t\t\t\ttsg += delta * 1000;\n\t\t\t}\n\t\t\tthis.timeSinceGroundedMs.set(gameEntity.uuid, tsg);\n\n\t\t\t// ── Build context ──────────────────────────────────────────────\n\t\t\t// Each behavior owns its own velocity axes. The jumper only\n\t\t\t// reads/writes Y; horizontal axes are left to the movement\n\t\t\t// controller. We read the current Y from the physics body\n\t\t\t// (post-step) so the jumper always sees the real vertical state.\n\t\t\tconst config: JumpConfig3D = gameEntity.jumper;\n\t\t\tconst input: JumpInput3D = gameEntity.$jumper;\n\t\t\tconst state: JumpState3D = gameEntity.jumperState;\n\t\t\tconst prevHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;\n\t\t\tconst held = input.jumpHeld === true;\n\n\t\t\t// Input callbacks run after behavior systems in the frame loop.\n\t\t\t// Reconstruct button edges from held-state history so one-frame\n\t\t\t// `pressed`/`released` pulses are not missed by the jumper tick.\n\t\t\tconst effectiveInput: JumpInput3D = {\n\t\t\t\t...input,\n\t\t\t\tjumpPressed: input.jumpPressed || (held && !prevHeld),\n\t\t\t\tjumpReleased: input.jumpReleased || (!held && prevHeld),\n\t\t\t};\n\n\t\t\tconst store = gameEntity.transformStore;\n\n\t\t\tconst ctx: JumpContext3D = {\n\t\t\t\tdt: delta,\n\t\t\t\tup: { x: 0, y: 1, z: 0 },\n\t\t\t\tvelocityY: bodyVel.y,\n\t\t\t\thorizontalVelocity: { x: bodyVel.x, z: bodyVel.z },\n\t\t\t\tisGrounded,\n\t\t\t\ttimeSinceGroundedMs: tsg,\n\t\t\t\tsetVerticalVelocity: (y: number) => {\n\t\t\t\t\tsetVelocityIntent(\n\t\t\t\t\t\tstore,\n\t\t\t\t\t\t'jumper-3d',\n\t\t\t\t\t\t{ y },\n\t\t\t\t\t\t{ mode: 'replace', priority: 20 },\n\t\t\t\t\t);\n\t\t\t\t\tctx.velocityY = y;\n\t\t\t\t},\n\t\t\t\tsetHorizontalVelocity: (x: number, z: number) => {\n\t\t\t\t\tsetVelocityIntent(\n\t\t\t\t\t\tstore,\n\t\t\t\t\t\t'jumper-3d',\n\t\t\t\t\t\t{ x, z },\n\t\t\t\t\t\t{ mode: 'replace', priority: 20 },\n\t\t\t\t\t);\n\t\t\t\t\tctx.horizontalVelocity.x = x;\n\t\t\t\t\tctx.horizontalVelocity.z = z;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// ── Tick ───────────────────────────────────────────────────────\n\t\t\tconst result = this.behavior.tick(config, effectiveInput, ctx, state);\n\t\t\tthis.wasJumpHeld.set(gameEntity.uuid, held);\n\n\t\t\t// ── FSM sync ───────────────────────────────────────────────────\n\t\t\tif (jumperRef.fsm) {\n\t\t\t\t(jumperRef.fsm as Jumper3DFSM).applyTickEvent(\n\t\t\t\t\tresult.event,\n\t\t\t\t\tisGrounded,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Jumper3DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst jumpForce = Math.sqrt(2 * (options.gravity ?? 20) * (options.jumpHeight ?? 2.5));\n\t\tthis.wasmStage.attachJumper3D(handle, {\n\t\t\tjumpForce,\n\t\t\tmaxJumps: options.maxJumps ?? 1,\n\t\t\tgravity: options.gravity ?? 20,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.groundProbe.destroy();\n\t\tthis.initializedEntities.clear();\n\t\tthis.timeSinceGroundedMs.clear();\n\t\tthis.wasJumpHeld.clear();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public descriptor\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Jumper3D — composable 3D jump behavior.\n *\n * Provides height-based jumping, multi-jump, coyote time, jump buffering,\n * variable jump height, configurable fall gravity, and optional air control.\n *\n * Designed to compose with any movement controller (e.g. FirstPersonController)\n * — the controller writes X/Z velocity and Jumper3D writes Y velocity.\n *\n * @example\n * ```typescript\n * import { Jumper3D } from \"@zylem/game-lib/behavior\";\n *\n * const jumper = player.use(Jumper3D, {\n * jumpHeight: 2.5,\n * gravity: 20,\n * maxJumps: 2,\n * coyoteTimeMs: 100,\n * jumpBufferMs: 80,\n * variableJump: { enabled: true, cutGravityMultiplier: 3 },\n * fall: { fallGravityMultiplier: 1.5 },\n * });\n *\n * player.onUpdate(({ inputs }) => {\n * player.$jumper.jumpPressed = inputs.p1.buttons.A.pressed;\n * player.$jumper.jumpHeld = inputs.p1.buttons.A.held > 0;\n * player.$jumper.jumpReleased = inputs.p1.buttons.A.released;\n *\n * const state = jumper.getState(); // Grounded | Jumping | Falling\n * });\n * ```\n */\nexport const Jumper3D = defineBehavior<\n\tJumper3DBehaviorOptions,\n\t{\n\t\tgetState: () => Jumper3DState;\n\t\tisJumping: () => boolean;\n\t\tgetJumpsUsed: () => number;\n\t\tgetJumpsRemaining: () => number;\n\t},\n\tJumper3DEntity\n>({\n\tname: 'jumper-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Jumper3DBehaviorSystem(\n\t\t\tctx.world,\n\t\t\tctx.scene,\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getState() ??\n\t\t\tJumper3DState.Grounded,\n\t\tisJumping: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.isJumping() ?? false,\n\t\tgetJumpsUsed: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getJumpsUsed() ?? 0,\n\t\tgetJumpsRemaining: () => {\n\t\t\tconst maxJumps = ref.options.maxJumps ?? 1;\n\t\t\tconst used =\n\t\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getJumpsUsed() ?? 0;\n\t\t\treturn maxJumps - used;\n\t\t},\n\t}),\n});\n"],"mappings":"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","names":["JumperTickEvent","Jumper3DState","Jumper3DEvent","input"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/coordinators/top-down-shooter.coordinator.ts"],"sourcesContent":["import {\n\tcreateTopDownMovementInputComponent,\n\ttype TopDownMovementInputComponent,\n} from '../behaviors/top-down-movement/components';\nimport type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../behaviors/shooter-2d/shooter-2d.descriptor';\nimport { getEntityFacingAngle2D, resolveAim2D } from './shared/top-down-aim';\n\nexport interface TopDownShooterInput {\n\tmoveX: number;\n\tmoveY: number;\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\ntype TopDownShooterEntity = Shooter2DSourceEntity & {\n\t$topDownMovement?: TopDownMovementInputComponent;\n};\n\nexport class TopDownShooterCoordinator {\n\tconstructor(\n\t\tprivate entity: TopDownShooterEntity,\n\t\tprivate shooter: Shooter2DHandle,\n\t\tprivate stage: Shooter2DStageLike,\n\t) {}\n\n\tpublic update(input: TopDownShooterInput): void {\n\t\tif (!this.entity.$topDownMovement) {\n\t\t\tthis.entity.$topDownMovement = createTopDownMovementInputComponent();\n\t\t}\n\n\t\tthis.entity.$topDownMovement.moveX = input.moveX;\n\t\tthis.entity.$topDownMovement.moveY = input.moveY;\n\t\tthis.entity.$topDownMovement.faceX = input.aimX;\n\t\tthis.entity.$topDownMovement.faceY = input.aimY;\n\n\t\tif (!input.shootPressed && !input.shootHeld) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sourcePosition = this.entity.body?.translation();\n\t\tif (!sourcePosition) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst aim = resolveAim2D(\n\t\t\tinput.aimX,\n\t\t\tinput.aimY,\n\t\t\tgetEntityFacingAngle2D(this.entity),\n\t\t);\n\t\tvoid this.shooter.fire({\n\t\t\tsource: this.entity,\n\t\t\tstage: this.stage,\n\t\t\ttarget: {\n\t\t\t\tx: sourcePosition.x + aim.direction.x,\n\t\t\t\ty: sourcePosition.y + aim.direction.y,\n\t\t\t},\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AAwBO,IAAM,4BAAN,MAAgC;AAAA,EACtC,YACS,QACA,SACA,OACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EAGF,OAAO,OAAkC;AAC/C,QAAI,CAAC,KAAK,OAAO,kBAAkB;AAClC,WAAK,OAAO,mBAAmB,oCAAoC;AAAA,IACpE;AAEA,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAE3C,QAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C;AAAA,IACD;AAEA,UAAM,iBAAiB,KAAK,OAAO,MAAM,YAAY;AACrD,QAAI,CAAC,gBAAgB;AACpB;AAAA,IACD;AAEA,UAAM,MAAM;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN,uBAAuB,KAAK,MAAM;AAAA,IACnC;AACA,SAAK,KAAK,QAAQ,KAAK;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,QACP,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,QACpC,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,MACrC;AAAA,IACD,CAAC;AAAA,EACF;AACD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/coordinators/boundary-ricochet.coordinator.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport { Ricochet2DHandle } from '../behaviors/ricochet-2d/ricochet-2d.descriptor';\nimport { WorldBoundary2DHandle } from '../behaviors/world-boundary-2d/world-boundary-2d.descriptor';\nimport { MoveableEntity } from '../actions/capabilities/moveable';\nimport { getBounds2DNormalFromHits } from '../behaviors/shared/bounds-2d';\n\n/**\n * Coordinator that bridges WorldBoundary2DBehavior and Ricochet2DBehavior.\n * \n * Automatically handles:\n * 1. Checking boundary hits\n * 2. Computing collision normals\n * 3. Requesting ricochet result\n * 4. Applying movement\n */\nexport class BoundaryRicochetCoordinator {\n constructor(\n private entity: GameEntity<any> & MoveableEntity,\n private boundary: WorldBoundary2DHandle,\n private ricochet: Ricochet2DHandle\n ) {}\n\n /**\n * Update loop - call this every frame.\n * Applies ricochet via transformStore and notifies listeners.\n * \n * @returns true if ricochet was applied, false otherwise\n */\n public update(): boolean {\n const hits = this.boundary.getLastHits();\n if (!hits) return false;\n\n const anyHit = hits.left || hits.right || hits.top || hits.bottom;\n if (!anyHit) return false;\n\n const normal = getBounds2DNormalFromHits(hits);\n\n // Apply ricochet (also emits to listeners)\n return this.ricochet.applyRicochet({\n entity: this.entity,\n contact: { normal: { x: normal.x, y: normal.y } },\n });\n }\n}\n"],"mappings":";;;;;AAeO,IAAM,8BAAN,MAAkC;AAAA,EACrC,YACY,QACA,UACA,UACV;AAHU;AACA;AACA;AAAA,EACT;AAAA,EAHS;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASL,SAAkB;AACrB,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,KAAK;AAC3D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,SAAS,0BAA0B,IAAI;AAG7C,WAAO,KAAK,SAAS,cAAc;AAAA,MAC/B,QAAQ,KAAK;AAAA,MACb,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE,EAAE;AAAA,IACpD,CAAC;AAAA,EACL;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/shared/direction-2d.ts"],"sourcesContent":["export interface Direction2D {\n\tx: number;\n\ty: number;\n}\n\nconst DIRECTION_EPSILON = 1e-6;\n\nexport function normalizeDirection2D(x: number, y: number): Direction2D | null {\n\tconst length = Math.hypot(x, y);\n\tif (length <= DIRECTION_EPSILON) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: x / length,\n\t\ty: y / length,\n\t};\n}\n\nexport function angleFromDirection2D(direction: Direction2D): number {\n\treturn Math.atan2(-direction.x, direction.y);\n}\n\nexport function directionFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.sin(-angle),\n\t\ty: Math.cos(-angle),\n\t};\n}\n\nexport function rightFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.cos(angle),\n\t\ty: Math.sin(angle),\n\t};\n}\n\nexport function rotateLocalOffset2D(\n\toffset: Direction2D,\n\tangle: number,\n): Direction2D {\n\tconst right = rightFromAngle2D(angle);\n\tconst forward = directionFromAngle2D(angle);\n\treturn {\n\t\tx: right.x * offset.x + forward.x * offset.y,\n\t\ty: right.y * offset.x + forward.y * offset.y,\n\t};\n}\n\nexport function getRotationAngle2D(\n\trotation: { x: number; y: number; z: number; w: number },\n): number {\n\treturn Math.atan2(\n\t\t2 * (rotation.w * rotation.z + rotation.x * rotation.y),\n\t\t1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z),\n\t);\n}\n"],"mappings":";AAKA,IAAM,oBAAoB;AAEnB,SAAS,qBAAqB,GAAW,GAA+B;AAC9E,QAAM,SAAS,KAAK,MAAM,GAAG,CAAC;AAC9B,MAAI,UAAU,mBAAmB;AAChC,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN,GAAG,IAAI;AAAA,IACP,GAAG,IAAI;AAAA,EACR;AACD;AAEO,SAAS,qBAAqB,WAAgC;AACpE,SAAO,KAAK,MAAM,CAAC,UAAU,GAAG,UAAU,CAAC;AAC5C;AAEO,SAAS,qBAAqB,OAA4B;AAChE,SAAO;AAAA,IACN,GAAG,KAAK,IAAI,CAAC,KAAK;AAAA,IAClB,GAAG,KAAK,IAAI,CAAC,KAAK;AAAA,EACnB;AACD;AAEO,SAAS,iBAAiB,OAA4B;AAC5D,SAAO;AAAA,IACN,GAAG,KAAK,IAAI,KAAK;AAAA,IACjB,GAAG,KAAK,IAAI,KAAK;AAAA,EAClB;AACD;AAEO,SAAS,oBACf,QACA,OACc;AACd,QAAM,QAAQ,iBAAiB,KAAK;AACpC,QAAM,UAAU,qBAAqB,KAAK;AAC1C,SAAO;AAAA,IACN,GAAG,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,OAAO;AAAA,IAC3C,GAAG,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,OAAO;AAAA,EAC5C;AACD;AAEO,SAAS,mBACf,UACS;AACT,SAAO,KAAK;AAAA,IACX,KAAK,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,SAAS;AAAA,IACrD,IAAI,KAAK,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,SAAS;AAAA,EAC1D;AACD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/platformer-3d/components.ts","../src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts","../src/lib/behaviors/platformer-3d/platformer-3d.behavior.ts","../src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 3D ECS Components\n * \n * Components for 3D platformer movement system including walking, running,\n * jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DMovementComponent (capability / configuration)\n// Defines the movement capabilities and physics parameters\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force (optional override) */\n\tgravity: number;\n\t/** Ray length for ground detection */\n\tgroundRayLength: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Enable ground-ray debug visualization */\n\tdebugGroundProbe: boolean;\n}\n\nexport function createPlatformer3DMovementComponent(\n\toptions: Partial<Platformer3DMovementComponent> = {}\n): Platformer3DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 12,\n\t\trunSpeed: options.runSpeed ?? 24,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 9.82,\n\t\tgroundRayLength: options.groundRayLength ?? 1.0,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15, // 150ms default\n\t\tdebugGroundProbe: options.debugGroundProbe ?? false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer3DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Forward/backward movement input (-1 to 1) */\n\tmoveZ: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer3DInputComponent(): Platformer3DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tmoveZ: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DStateComponent (runtime state)\n// Tracks the current state of the platformer entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t\n\t// -- New fields --\n\t/** Is currently colliding with ground (from physics collision callback) */\n\tcollisionGrounded: boolean;\n\t/** Timestamp of last ground collision */\n\tgroundedCollisionTime: number;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, velocity.y lerps toward velocity.y * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/** Remaining seconds of grounded-hysteresis grace (keeps `grounded` true briefly after ground probe misses, to absorb edge/ray flicker). */\n\tgroundedGraceTimer: number;\n}\n\nexport function createPlatformer3DStateComponent(): Platformer3DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\tcollisionGrounded: false,\n\t\tgroundedCollisionTime: 0,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tgroundedGraceTimer: 0,\n\t};\n}\n","/**\n * Platformer 3D Finite State Machine\n * \n * Manages state transitions for 3D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer3DInputComponent, Platformer3DStateComponent } from './components';\n\n/**\n * Simplified Collision Context interface\n */\nexport interface PlatformerCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number; z?: number };\n\t};\n}\n\n/**\n * Platformer states\n */\nexport enum Platformer3DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum Platformer3DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface Platformer3DContext {\n\tinput: Platformer3DInputComponent;\n\tstate: Platformer3DStateComponent;\n}\n\n/**\n * Platformer 3D FSM\n */\nexport class Platformer3DFSM {\n\tmachine: SyncStateMachine<Platformer3DState, Platformer3DEvent, never>;\n\n\tconstructor(private ctx: Platformer3DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer3DState, Platformer3DEvent, never>(\n\t\t\tPlatformer3DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Jump, Platformer3DState.Jumping), // Multi-jump\n\t\t\t\t\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer3DState.Falling, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\t\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\t\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get the current state\n\t */\n\tgetState(): Platformer3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to the FSM\n\t */\n\tdispatch(event: Platformer3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Check if grounded\n\t */\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn state === Platformer3DState.Idle || \n\t\t state === Platformer3DState.Walking || \n\t\t state === Platformer3DState.Running || \n\t\t state === Platformer3DState.Landing;\n\t}\n\n\t/**\n\t * Get current jump count from context\n\t */\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Handle collision event to update ground state\n\t */\n\thandleCollision(ctx: PlatformerCollisionContext): void {\n\t\t// Check if collision is ground (normal pointing up)\n\t\t// Y normal > 0.5 means roughly within 60 degrees of up\n\t\tif (ctx.contact.normal.y > 0.5) {\n\t\t\tthis.ctx.state.collisionGrounded = true;\n\t\t\tthis.ctx.state.groundedCollisionTime = performance.now();\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on current state\n\t */\n\tupdate(input: Platformer3DInputComponent, state: Platformer3DStateComponent): void {\n\t\t// Update context\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\t// Get current FSM state\n\t\tconst currentState = this.getState();\n\n\t\t// Determine movement state\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer3DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer3DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\t// Send appropriate events based on state\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Stop);\n\t\t\t}\n\t\t} else {\n\t\t\t// Not grounded - check if we should be falling\n\t\t\t// Transition to falling if: We're in jumping state and now falling, \n\t\t\t// OR we walked off a ledge (idle/walking/running and now falling)\n\t\t\tif (state.falling) {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Fall);\n\t\t\t}\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in behavior)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer3DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 3D Behavior System\n *\n * Handles 3D platformer physics including:\n * - Contact-based ground detection (uses the actual capsule collider — no\n * raycast/shapecast probe). The bottom of the body's collider is the\n * single source of truth for \"is grounded\".\n * - Horizontal movement (walk/run)\n * - Jumping with multi-jump support\n * - Gravity application (Rapier's internal gravity is disabled per-body so\n * `movement.gravity` is the only fall acceleration — previously the two\n * stacked, doubling the airborne fall rate).\n * - State synchronization with FSM\n */\n\nimport type {\n\tPlatformer3DMovementComponent,\n\tPlatformer3DInputComponent,\n\tPlatformer3DStateComponent,\n} from './components';\n\n/**\n * Duration of the jump-cut velocity ramp in seconds.\n * Instead of halving velocity.y in a single frame (which produces a visible\n * camera pop), we lerp toward the cut velocity across roughly 3 physics steps\n * at 60 Hz. Short enough to still feel responsive, long enough to be smooth.\n */\nconst JUMP_CUT_RAMP_DURATION = 0.05;\n\n/**\n * Hysteresis window for the grounded state (seconds).\n *\n * When the ground probe misses for a frame but we were grounded the previous\n * frame, remain grounded for this brief window before flipping to airborne.\n * Prevents rapid grounded/airborne toggling at platform edges where a single\n * corner ray may miss intermittently, and absorbs sub-step probe noise.\n */\nconst GROUNDED_GRACE_WINDOW = 0.06;\n\n/**\n * Entity with platformer components\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tbody: any; // RigidBody\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n\ttransformStore?: any;\n}\n\n/**\n * Platformer 3D Movement Behavior\n *\n * Core physics system for 3D platformer movement.\n *\n * Ground detection uses Rapier's narrow-phase contacts on the entity's\n * collider (`world.contactsWith` + `contactPair`). This eliminates the old\n * shape/ray probe and aligns \"grounded\" with the same surface the body\n * physically rests on, which removes the jitter that happened on heightmap\n * terrain (the probe and the contact resolver could disagree by a frame).\n */\nexport class Platformer3DBehavior {\n\tprivate world: any;\n\tprivate scene: any;\n\t/**\n\t * Tracks entities whose Rapier `gravityScale` has been zeroed so that\n\t * `applyGravity` is the sole writer of vertical velocity. We do this\n\t * lazily on the first update because the body isn't always available at\n\t * construction time.\n\t */\n\tprivate gravityConfiguredUUIDs = new Set<string>();\n\n\tconstructor(world: any, scene: any) {\n\t\tthis.world = world;\n\t\tthis.scene = scene;\n\t}\n\n\t/**\n\t * Detect \"grounded\" using the actual capsule collider's contact pairs.\n\t *\n\t * A contact is treated as ground when its solver point lies below the\n\t * body center (i.e., it sits on the bottom hemisphere/cylinder). We\n\t * deliberately don't filter by normal direction: the contact point's\n\t * position alone is robust against the various normal-flip conventions\n\t * Rapier uses for `flipped` manifolds and against rolling-bottom edges\n\t * on heightmap terrain.\n\t */\n\tprivate detectGroundedFromContacts(entity: Platformer3DEntity): boolean {\n\t\tconst collider = (entity as any).collider;\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (!collider || !physicsWorld?.contactsWith) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet grounded = false;\n\n\t\tphysicsWorld.contactsWith(collider, (other: any) => {\n\t\t\tif (grounded) return;\n\t\t\tphysicsWorld.contactPair(collider, other, (manifold: any, flipped: boolean) => {\n\t\t\t\tif (grounded) return;\n\t\t\t\tconst num = manifold.numContacts();\n\t\t\t\tfor (let i = 0; i < num; i++) {\n\t\t\t\t\tconst local = flipped\n\t\t\t\t\t\t? manifold.localContactPoint2(i)\n\t\t\t\t\t\t: manifold.localContactPoint1(i);\n\t\t\t\t\tif (!local) continue;\n\t\t\t\t\tif (local.y < 0) {\n\t\t\t\t\t\tgrounded = true;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\treturn grounded;\n\t}\n\n\t/**\n\t * One-time per-entity physics setup: take over gravity from Rapier's\n\t * world so that `applyGravity` controls the entire airborne fall curve.\n\t * Without this, gravity was effectively doubled (Rapier's world gravity\n\t * + this behavior's explicit gravity were both pulling the body down).\n\t */\n\tprivate ensurePhysicsConfigured(entity: Platformer3DEntity): void {\n\t\tif (this.gravityConfiguredUUIDs.has(entity.uuid)) return;\n\t\tif (typeof entity.body?.setGravityScale === 'function') {\n\t\t\tentity.body.setGravityScale(0, true);\n\t\t}\n\t\tthis.gravityConfiguredUUIDs.add(entity.uuid);\n\t}\n\n\t/**\n\t * Apply horizontal movement based on input.\n\t *\n\t * Only writes X/Z axes — Y is left untouched so Rapier's contact resolution\n\t * can damp vertical motion correctly while grounded. Jump and gravity are\n\t * the sole writers of Y velocity (see handleJump / applyGravity).\n\t */\n\tprivate applyMovement(entity: Platformer3DEntity): void {\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\tconst speed = input.run ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\n\t\tconst moveX = input.moveX * speed;\n\t\tconst moveZ = input.moveZ * speed;\n\n\t\tentity.transformStore.velocity.x = moveX;\n\t\tentity.transformStore.velocity.z = moveZ;\n\t\tentity.transformStore.dirty.velocityX = true;\n\t\tentity.transformStore.dirty.velocityZ = true;\n\t}\n\n\t/**\n\t * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut ramp.\n\t */\n\tprivate handleJump(entity: Platformer3DEntity, delta: number): void {\n\t\tconst input = entity.$platformer;\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\t// Track time since last jump (for multi-jump window)\n\t\tif (state.jumping || state.falling) {\n\t\t\tstate.timeSinceJump += delta;\n\t\t}\n\n\t\t// Track jump button release (required for multi-jump)\n\t\tif (!input.jump && state.jumpHeld) {\n\t\t\tstate.jumpReleasedSinceLastJump = true;\n\t\t}\n\n\t\t// 1. Jump Buffering - queue on new press\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tstate.jumpBuffered = true;\n\t\t\tstate.jumpBufferTimer = movement.jumpBufferTime;\n\t\t}\n\t\t\n\t\tstate.jumpPressedLastFrame = input.jump;\n\t\tstate.jumpHeld = input.jump;\n\n\t\t// Decrement buffer timer\n\t\tif (state.jumpBuffered) {\n\t\t\tstate.jumpBufferTimer -= delta;\n\t\t\tif (state.jumpBufferTimer <= 0) {\n\t\t\t\tstate.jumpBuffered = false;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Variable Jump Height (Jump Cut)\n\t\t// Only trigger a cut if we've been jumping for a bit (prevents cutting multi-jump immediately).\n\t\tconst minTimeBeforeCut = 0.1;\n\t\tconst canApplyCut = state.timeSinceJump >= minTimeBeforeCut;\n\t\tif (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {\n\t\t\tstate.jumpCutApplied = true;\n\t\t\tstate.jumpCutRampTimer = JUMP_CUT_RAMP_DURATION;\n\t\t}\n\n\t\t// 2a. Ramp velocity.y toward velocity.y * jumpCutMultiplier across the\n\t\t// ramp window so the change isn't a single-frame discontinuity.\n\t\tif (state.jumpCutRampTimer > 0) {\n\t\t\tconst velocity = entity.body.linvel();\n\t\t\tif (velocity.y > 0) {\n\t\t\t\tconst stepT = Math.min(delta / state.jumpCutRampTimer, 1);\n\t\t\t\tconst targetY = velocity.y * movement.jumpCutMultiplier;\n\t\t\t\tentity.transformStore.velocity.y = velocity.y + (targetY - velocity.y) * stepT;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t}\n\t\t\tstate.jumpCutRampTimer = Math.max(0, state.jumpCutRampTimer - delta);\n\t\t}\n\n\t\t// Execute Jump (if buffered input exists)\n\t\tif (!state.jumpBuffered) return;\n\n\t\t// 3. Jump eligibility check\n\t\tconst inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;\n\t\tconst isFirstJump = state.grounded || (inCoyoteWindow && state.jumpCount === 0);\n\t\t\n\t\t// Multi-jump requirements:\n\t\t// 1. Not grounded\n\t\t// 2. Have jumps remaining\n\t\t// 3. Button was released since last jump (no holding for double jump)\n\t\t// 4. Within the jump window (after multiJumpWindowTime has passed)\n\t\tconst hasJumpsRemaining = state.jumpCount < movement.maxJumps;\n\t\tconst buttonReleased = state.jumpReleasedSinceLastJump;\n\t\tconst inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;\n\t\tconst canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;\n\n\t\tif (isFirstJump || canMultiJump) {\n\t\t\t// Consume buffered input\n\t\t\tstate.jumpBuffered = false;\n\n\t\t\t// Increment jump count and reset tracking\n\t\t\tstate.jumpCount++;\n\t\t\tstate.jumpReleasedSinceLastJump = false; // Must release again for next jump\n\t\t\tstate.timeSinceJump = 0; // Reset window timer\n\n\t\t\t// Record jump start height\n\t\t\tstate.jumpStartHeight = entity.body.translation().y;\n\n\t\t\t// Apply jump force\n\t\t\tstate.jumping = true;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\t\t\tstate.groundedGraceTimer = 0;\n\n\t\t\t// Apply jump force via transform store (Y-axis only)\n\t\t\tentity.transformStore.velocity.y = movement.jumpForce;\n\t\t\tentity.transformStore.dirty.velocityY = true;\n\n\t\t\t// Flag this frame so applyGravity skips exactly one gravity step\n\t\t\t// (the jump impulse we just wrote is the authoritative Y velocity).\n\t\t\tstate.jumpedThisFrame = true;\n\t\t}\n\t}\n\n\t/**\n\t * Apply gravity when not grounded.\n\t *\n\t * Skipped on the single frame immediately after a jump impulse is applied\n\t * (tracked via `state.jumpedThisFrame`) so gravity never overwrites a\n\t * just-applied jump velocity. The flag is cleared here regardless, so it\n\t * only suppresses exactly one gravity step — independent of frame rate.\n\t */\n\tprivate applyGravity(entity: Platformer3DEntity, delta: number): void {\n\t\tconst movement = entity.platformer;\n\t\tconst state = entity.platformerState;\n\n\t\tif (state.jumpedThisFrame) {\n\t\t\tstate.jumpedThisFrame = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (state.grounded) return;\n\n\t\tconst currentVel = entity.body.linvel();\n\t\tconst newYVelocity = currentVel.y - movement.gravity * delta;\n\n\t\tentity.transformStore.velocity.y = newYVelocity;\n\t\tentity.transformStore.dirty.velocityY = true;\n\t}\n\n\t/**\n\t * Update entity state based on physics.\n\t *\n\t * Grounded is determined purely by Rapier's contact pairs on the entity's\n\t * collider. This removes a whole class of slope/heightmap jitter caused\n\t * by the previous shape probe disagreeing with the contact resolver — the\n\t * probe could report grounded while a contact impulse had just nudged the\n\t * body off the ground, producing visible micro-bouncing.\n\t *\n\t * While grounded, vertical velocity is held at 0 each frame. This stops\n\t * residual upward kicks from slope-contact resolution from accumulating\n\t * across frames (which previously caused the character to \"ride up\"\n\t * slopes and lose contact briefly, then fall back, then re-contact, in a\n\t * tight oscillation).\n\t */\n\tprivate updateState(entity: Platformer3DEntity, delta: number): void {\n\t\tconst state = entity.platformerState;\n\t\tconst wasGrounded = state.grounded;\n\t\tconst velocity = entity.body.linvel();\n\t\tconst hasContact = this.detectGroundedFromContacts(entity);\n\n\t\tconst isAirborne = state.jumping || state.falling;\n\t\tlet isGrounded: boolean;\n\t\tif (isAirborne) {\n\t\t\t// While in a jump arc the contact still exists for one frame after\n\t\t\t// liftoff; require the falling state before allowing a landing.\n\t\t\tconst canLand = state.falling && !state.jumping;\n\t\t\tisGrounded = hasContact && canLand;\n\t\t} else {\n\t\t\tisGrounded = hasContact;\n\t\t}\n\n\t\t// Hysteresis: if the contact briefly drops over a heightmap seam,\n\t\t// stay grounded for a short grace window.\n\t\tif (!isGrounded && wasGrounded) {\n\t\t\tif (state.groundedGraceTimer <= 0) {\n\t\t\t\tstate.groundedGraceTimer = GROUNDED_GRACE_WINDOW;\n\t\t\t}\n\t\t}\n\t\tif (state.groundedGraceTimer > 0) {\n\t\t\tif (!isGrounded && wasGrounded && !isAirborne) {\n\t\t\t\tisGrounded = true;\n\t\t\t\tstate.groundedGraceTimer = Math.max(0, state.groundedGraceTimer - delta);\n\t\t\t} else {\n\t\t\t\tstate.groundedGraceTimer = 0;\n\t\t\t}\n\t\t}\n\n\t\tstate.grounded = isGrounded;\n\n\t\t// Hold vy = 0 while grounded so slope-impulse residual doesn't drift\n\t\t// the body upward across frames. handleJump runs after this and will\n\t\t// overwrite velocity.y with jumpForce on a jump frame.\n\t\tif (state.grounded) {\n\t\t\tentity.transformStore.velocity.y = 0;\n\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tstate.timeSinceGrounded = 0;\n\t\t\tstate.lastGroundedY = entity.body.translation().y;\n\t\t} else {\n\t\t\tstate.timeSinceGrounded += delta;\n\t\t}\n\n\t\tif (!wasGrounded && state.grounded) {\n\t\t\tstate.jumpCount = 0;\n\t\t\tstate.jumping = false;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\t\t}\n\n\t\tif (velocity.y < -0.1 && !state.grounded) {\n\t\t\tif (state.jumping && velocity.y < 0) {\n\t\t\t\tstate.jumping = false;\n\t\t\t\tstate.falling = true;\n\t\t\t} else if (!state.jumping) {\n\t\t\t\tstate.falling = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update one platformer entity. Invoked per linked entity by the\n\t * Platformer3DBehaviorSystem adapter.\n\t */\n\tupdateEntity(entity: any, delta: number): void {\n\t\tif (!entity.platformer || !entity.$platformer || !entity.platformerState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst platformerEntity = entity as Platformer3DEntity;\n\t\tthis.ensurePhysicsConfigured(platformerEntity);\n\t\tthis.updateState(platformerEntity, delta);\n\t\tthis.applyMovement(platformerEntity);\n\t\tthis.handleJump(platformerEntity, delta);\n\t\tthis.applyGravity(platformerEntity, delta);\n\t}\n\n\tdestroy(): void {\n\t\tthis.gravityConfiguredUUIDs.clear();\n\t}\n}\n","/**\n * Platformer 3D Behavior Descriptor\n * \n * Type-safe descriptor for the platformer 3D behavior system.\n * Provides entity.use() API for 3D platformer movement.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer3DBehavior as Platformer3DMovementBehavior, Platformer3DEntity } from './platformer-3d.behavior';\nimport { Platformer3DFSM, Platformer3DState, PlatformerCollisionContext } from './platformer-3d-fsm';\nimport {\n\tcreatePlatformer3DMovementComponent,\n\tcreatePlatformer3DInputComponent,\n\tcreatePlatformer3DStateComponent,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Platformer behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer3DBehaviorOptions {\n\t/** Base walking speed (default: 12) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 24) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 9.82) */\n\tgravity?: number;\n\t/** Ray length for ground detection (default: 1.0) */\n\tgroundRayLength?: number;\n\t/** Enable debug visualization for ground probe rays (default: false) */\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tgroundRayLength: 1.0,\n\tdebugGroundProbe: false,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\n/**\n * Adapter that wraps Platformer3DBehavior as a BehaviorSystem\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: Platformer3DMovementBehavior;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate scene: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new Platformer3DMovementBehavior(world, scene);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer3DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer) {\n\t\t\t\tgameEntity.platformer = createPlatformer3DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer) {\n\t\t\t\tgameEntity.$platformer = createPlatformer3DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformerState) {\n\t\t\t\tgameEntity.platformerState = createPlatformer3DStateComponent();\n\t\t\t}\n\n\t\t\tif (!platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {\n\t\t\t\tplatformerRef.fsm = new Platformer3DFSM({\n\t\t\t\t\tinput: gameEntity.$platformer,\n\t\t\t\t\tstate: gameEntity.platformerState,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {\n\t\t\t\tplatformerRef.fsm.update(gameEntity.$platformer, gameEntity.platformerState);\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst input = gameEntity.$platformer;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.setPlatformer3DInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\t\tthis.wasmStage.setPlatformer3DInputButtons(handle, !!input.jump, !!input.run);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tthis.movementBehavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Platformer3DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachPlatformer3D(handle, {\n\t\t\thalfHeight: 0.9,\n\t\t\tradius: 0.4,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.movementBehavior.destroy();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer3DBehavior - typed descriptor for 3D platformer movement\n * \n * Provides complete 3D platformer physics including:\n * - Walking and running\n * - Jumping with multi-jump support\n * - Gravity and falling\n * - Ground detection\n * \n * @example\n * ```typescript\n * import { Platformer3DBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const player = await playgroundActor('player');\n * const platformer = player.use(Platformer3DBehavior, {\n * walkSpeed: 12,\n * runSpeed: 24,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n * \n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer.moveZ = inputs.p1.axes.Vertical.value;\n * player.$platformer.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer.run = inputs.p1.shoulders.LTrigger.held > 0;\n * \n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer3DBehavior = defineBehavior<\n\tPlatformer3DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer3DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t\tonPlatformCollision: (ctx: PlatformerCollisionContext) => void;\n\t},\n\tPlatformer3DEntity\n>({\n\tname: 'platformer-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer3DBehaviorSystem(\n\t\t\tctx.world,\n\t\t\tctx.scene,\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => ref.fsm?.getState() ?? Platformer3DState.Idle,\n\t\tisGrounded: () => ref.fsm?.isGrounded() ?? false,\n\t\tgetJumpCount: () => ref.fsm?.getJumpCount() ?? 0,\n\t\tonPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx),\n\t}),\n});\n"],"mappings":";;;;;;;;;AAqCO,SAAS,oCACf,UAAkD,CAAC,GACnB;AAChC,SAAO;AAAA,IACN,WAAW,QAAQ,aAAa;AAAA,IAChC,UAAU,QAAQ,YAAY;AAAA,IAC9B,WAAW,QAAQ,aAAa;AAAA,IAChC,UAAU,QAAQ,YAAY;AAAA,IAC9B,SAAS,QAAQ,WAAW;AAAA,IAC5B,iBAAiB,QAAQ,mBAAmB;AAAA,IAC5C,YAAY,QAAQ,cAAc;AAAA,IAClC,gBAAgB,QAAQ,kBAAkB;AAAA,IAC1C,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,qBAAqB,QAAQ,uBAAuB;AAAA;AAAA,IACpD,kBAAkB,QAAQ,oBAAoB;AAAA,EAC/C;AACD;AAmBO,SAAS,mCAA+D;AAC9E,SAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,EACN;AACD;AAoDO,SAAS,mCAA+D;AAC9E,SAAO;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,2BAA2B;AAAA,IAC3B,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,EACrB;AACD;;;AC/HO,IAAK,oBAAL,kBAAKA,uBAAL;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AANC,SAAAA;AAAA,GAAA;AAYL,IAAK,oBAAL,kBAAKC,uBAAL;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,SAAM;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AANI,SAAAA;AAAA,GAAA;AAoBL,IAAM,kBAAN,MAAsB;AAAA,EAG5B,YAAoB,KAA0B;AAA1B;AACnB,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA;AAAA,QAEC,EAAE,mBAAwB,mBAAwB,uBAAyB;AAAA,QAC3E,EAAE,mBAAwB,iBAAuB,uBAAyB;AAAA,QAC1E,EAAE,mBAAwB,mBAAwB,uBAAyB;AAAA,QAC3E,EAAE,mBAAwB,mBAAwB,uBAAyB;AAAA;AAAA,QAG3E,EAAE,yBAA2B,iBAAuB,uBAAyB;AAAA,QAC7E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,mBAAwB,iBAAsB;AAAA,QAC3E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,QAG9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,mBAAwB,iBAAsB;AAAA,QAC3E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,QAG9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA;AAAA,QAG9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,QAG9E,EAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,QAC9E,EAAE,yBAA2B,iBAAuB,uBAAyB;AAAA,QAC7E,EAAE,yBAA2B,mBAAwB,iBAAsB;AAAA;AAAA,QAG3E,EAAE,mBAAwB,mBAAwB,iBAAsB;AAAA,MACzE;AAAA,IACD;AAAA,EACD;AAAA,EAvCoB;AAAA,EAFpB;AAAA;AAAA;AAAA;AAAA,EA8CA,WAA8B;AAC7B,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAgC;AACxC,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACrB,UAAM,QAAQ,KAAK,SAAS;AAC5B,WAAO,UAAU,qBACV,UAAU,2BACV,UAAU,2BACV,UAAU;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACtB,WAAO,KAAK,IAAI,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAuC;AAGtD,QAAI,IAAI,QAAQ,OAAO,IAAI,KAAK;AAC/B,WAAK,IAAI,MAAM,oBAAoB;AACnC,WAAK,IAAI,MAAM,wBAAwB,YAAY,IAAI;AAAA,IACxD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAmC,OAAyC;AAElF,SAAK,IAAI,QAAQ;AACjB,SAAK,IAAI,QAAQ;AAGjB,UAAM,eAAe,KAAK,SAAS;AAGnC,UAAM,WAAW,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AACxE,UAAM,YAAY,MAAM;AAGxB,QAAI,iBAAiB,2BAA6B,MAAM,UAAU;AACjE,WAAK,SAAS,iBAAsB;AAAA,IAErC;AAGA,QAAI,MAAM,UAAU;AACnB,UAAI,UAAU;AACb,YAAI,WAAW;AACd,eAAK,SAAS,eAAqB;AAAA,QACpC,OAAO;AACN,eAAK,SAAS,iBAAsB;AAAA,QACrC;AAAA,MACD,OAAO;AACN,aAAK,SAAS,iBAAsB;AAAA,MACrC;AAAA,IACD,OAAO;AAIN,UAAI,MAAM,SAAS;AAClB,aAAK,SAAS,iBAAsB;AAAA,MACrC;AAAA,IACD;AAGA,QAAI,MAAM,QAAQ,CAAC,MAAM,sBAAsB;AAC9C,WAAK,SAAS,iBAAsB;AAAA,IACrC;AAAA,EACD;AACD;;;ACxKA,IAAM,yBAAyB;AAU/B,IAAM,wBAAwB;AAyBvB,IAAM,uBAAN,MAA2B;AAAA,EACzB;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,yBAAyB,oBAAI,IAAY;AAAA,EAEjD,YAAY,OAAY,OAAY;AACnC,SAAK,QAAQ;AACb,SAAK,QAAQ;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,2BAA2B,QAAqC;AACvE,UAAM,WAAY,OAAe;AACjC,UAAM,eAAe,KAAK,OAAO;AACjC,QAAI,CAAC,YAAY,CAAC,cAAc,cAAc;AAC7C,aAAO;AAAA,IACR;AAEA,QAAI,WAAW;AAEf,iBAAa,aAAa,UAAU,CAAC,UAAe;AACnD,UAAI,SAAU;AACd,mBAAa,YAAY,UAAU,OAAO,CAAC,UAAe,YAAqB;AAC9E,YAAI,SAAU;AACd,cAAM,MAAM,SAAS,YAAY;AACjC,iBAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,gBAAM,QAAQ,UACX,SAAS,mBAAmB,CAAC,IAC7B,SAAS,mBAAmB,CAAC;AAChC,cAAI,CAAC,MAAO;AACZ,cAAI,MAAM,IAAI,GAAG;AAChB,uBAAW;AACX;AAAA,UACD;AAAA,QACD;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAAwB,QAAkC;AACjE,QAAI,KAAK,uBAAuB,IAAI,OAAO,IAAI,EAAG;AAClD,QAAI,OAAO,OAAO,MAAM,oBAAoB,YAAY;AACvD,aAAO,KAAK,gBAAgB,GAAG,IAAI;AAAA,IACpC;AACA,SAAK,uBAAuB,IAAI,OAAO,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,QAAkC;AACvD,UAAM,QAAQ,OAAO;AACrB,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,OAAO;AAErB,UAAM,QAAQ,MAAM,MAAM,SAAS,WAAW,SAAS;AACvD,UAAM,eAAe;AAErB,UAAM,QAAQ,MAAM,QAAQ;AAC5B,UAAM,QAAQ,MAAM,QAAQ;AAE5B,WAAO,eAAe,SAAS,IAAI;AACnC,WAAO,eAAe,SAAS,IAAI;AACnC,WAAO,eAAe,MAAM,YAAY;AACxC,WAAO,eAAe,MAAM,YAAY;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,QAA4B,OAAqB;AACnE,UAAM,QAAQ,OAAO;AACrB,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,OAAO;AAGrB,QAAI,MAAM,WAAW,MAAM,SAAS;AACnC,YAAM,iBAAiB;AAAA,IACxB;AAGA,QAAI,CAAC,MAAM,QAAQ,MAAM,UAAU;AAClC,YAAM,4BAA4B;AAAA,IACnC;AAGA,QAAI,MAAM,QAAQ,CAAC,MAAM,sBAAsB;AAC9C,YAAM,eAAe;AACrB,YAAM,kBAAkB,SAAS;AAAA,IAClC;AAEA,UAAM,uBAAuB,MAAM;AACnC,UAAM,WAAW,MAAM;AAGvB,QAAI,MAAM,cAAc;AACvB,YAAM,mBAAmB;AACzB,UAAI,MAAM,mBAAmB,GAAG;AAC/B,cAAM,eAAe;AAAA,MACtB;AAAA,IACD;AAIA,UAAM,mBAAmB;AACzB,UAAM,cAAc,MAAM,iBAAiB;AAC3C,QAAI,CAAC,MAAM,QAAQ,MAAM,WAAW,CAAC,MAAM,kBAAkB,aAAa;AACzE,YAAM,iBAAiB;AACvB,YAAM,mBAAmB;AAAA,IAC1B;AAIA,QAAI,MAAM,mBAAmB,GAAG;AAC/B,YAAM,WAAW,OAAO,KAAK,OAAO;AACpC,UAAI,SAAS,IAAI,GAAG;AACnB,cAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,kBAAkB,CAAC;AACxD,cAAM,UAAU,SAAS,IAAI,SAAS;AACtC,eAAO,eAAe,SAAS,IAAI,SAAS,KAAK,UAAU,SAAS,KAAK;AACzE,eAAO,eAAe,MAAM,YAAY;AAAA,MACzC;AACA,YAAM,mBAAmB,KAAK,IAAI,GAAG,MAAM,mBAAmB,KAAK;AAAA,IACpE;AAGA,QAAI,CAAC,MAAM,aAAc;AAGzB,UAAM,iBAAiB,CAAC,MAAM,YAAY,MAAM,qBAAqB,SAAS;AAC9E,UAAM,cAAc,MAAM,YAAa,kBAAkB,MAAM,cAAc;AAO7E,UAAM,oBAAoB,MAAM,YAAY,SAAS;AACrD,UAAM,iBAAiB,MAAM;AAC7B,UAAM,oBAAoB,MAAM,iBAAiB,SAAS;AAC1D,UAAM,eAAe,CAAC,MAAM,YAAY,qBAAqB,kBAAkB;AAE/E,QAAI,eAAe,cAAc;AAEhC,YAAM,eAAe;AAGrB,YAAM;AACN,YAAM,4BAA4B;AAClC,YAAM,gBAAgB;AAGtB,YAAM,kBAAkB,OAAO,KAAK,YAAY,EAAE;AAGlD,YAAM,UAAU;AAChB,YAAM,UAAU;AAChB,YAAM,iBAAiB;AACvB,YAAM,mBAAmB;AACzB,YAAM,qBAAqB;AAG3B,aAAO,eAAe,SAAS,IAAI,SAAS;AAC5C,aAAO,eAAe,MAAM,YAAY;AAIxC,YAAM,kBAAkB;AAAA,IACzB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,aAAa,QAA4B,OAAqB;AACrE,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,OAAO;AAErB,QAAI,MAAM,iBAAiB;AAC1B,YAAM,kBAAkB;AACxB;AAAA,IACD;AAEA,QAAI,MAAM,SAAU;AAEpB,UAAM,aAAa,OAAO,KAAK,OAAO;AACtC,UAAM,eAAe,WAAW,IAAI,SAAS,UAAU;AAEvD,WAAO,eAAe,SAAS,IAAI;AACnC,WAAO,eAAe,MAAM,YAAY;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBQ,YAAY,QAA4B,OAAqB;AACpE,UAAM,QAAQ,OAAO;AACrB,UAAM,cAAc,MAAM;AAC1B,UAAM,WAAW,OAAO,KAAK,OAAO;AACpC,UAAM,aAAa,KAAK,2BAA2B,MAAM;AAEzD,UAAM,aAAa,MAAM,WAAW,MAAM;AAC1C,QAAI;AACJ,QAAI,YAAY;AAGf,YAAM,UAAU,MAAM,WAAW,CAAC,MAAM;AACxC,mBAAa,cAAc;AAAA,IAC5B,OAAO;AACN,mBAAa;AAAA,IACd;AAIA,QAAI,CAAC,cAAc,aAAa;AAC/B,UAAI,MAAM,sBAAsB,GAAG;AAClC,cAAM,qBAAqB;AAAA,MAC5B;AAAA,IACD;AACA,QAAI,MAAM,qBAAqB,GAAG;AACjC,UAAI,CAAC,cAAc,eAAe,CAAC,YAAY;AAC9C,qBAAa;AACb,cAAM,qBAAqB,KAAK,IAAI,GAAG,MAAM,qBAAqB,KAAK;AAAA,MACxE,OAAO;AACN,cAAM,qBAAqB;AAAA,MAC5B;AAAA,IACD;AAEA,UAAM,WAAW;AAKjB,QAAI,MAAM,UAAU;AACnB,aAAO,eAAe,SAAS,IAAI;AACnC,aAAO,eAAe,MAAM,YAAY;AAAA,IACzC;AAEA,QAAI,MAAM,UAAU;AACnB,YAAM,oBAAoB;AAC1B,YAAM,gBAAgB,OAAO,KAAK,YAAY,EAAE;AAAA,IACjD,OAAO;AACN,YAAM,qBAAqB;AAAA,IAC5B;AAEA,QAAI,CAAC,eAAe,MAAM,UAAU;AACnC,YAAM,YAAY;AAClB,YAAM,UAAU;AAChB,YAAM,UAAU;AAChB,YAAM,iBAAiB;AACvB,YAAM,mBAAmB;AAAA,IAC1B;AAEA,QAAI,SAAS,IAAI,QAAQ,CAAC,MAAM,UAAU;AACzC,UAAI,MAAM,WAAW,SAAS,IAAI,GAAG;AACpC,cAAM,UAAU;AAChB,cAAM,UAAU;AAAA,MACjB,WAAW,CAAC,MAAM,SAAS;AAC1B,cAAM,UAAU;AAAA,MACjB;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAa,OAAqB;AAC9C,QAAI,CAAC,OAAO,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,iBAAiB;AACzE;AAAA,IACD;AAEA,UAAM,mBAAmB;AACzB,SAAK,wBAAwB,gBAAgB;AAC7C,SAAK,YAAY,kBAAkB,KAAK;AACxC,SAAK,cAAc,gBAAgB;AACnC,SAAK,WAAW,kBAAkB,KAAK;AACvC,SAAK,aAAa,kBAAkB,KAAK;AAAA,EAC1C;AAAA,EAEA,UAAgB;AACf,SAAK,uBAAuB,MAAM;AAAA,EACnC;AACD;;;AC9VA,IAAM,iBAA8C;AAAA,EACnD,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,kBAAkB;AACnB;AAEA,IAAM,0BAA0B,uBAAO,IAAI,8BAA8B;AAKzE,IAAM,6BAAN,MAA2D;AAAA,EAI1D,YACS,OACA,OACA,WACA,kBACP;AAJO;AACA;AACA;AACA;AAER,SAAK,mBAAmB,IAAI,qBAA6B,OAAO,KAAK;AAAA,EACtE;AAAA,EANS;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAPD;AAAA,EACA,uBAAuB,oBAAI,IAAY;AAAA,EAW/C,OAAO,MAAe,OAAqB;AAC1C,UAAM,QAAQ,KAAK,mBAAmB,uBAAuB;AAC7D,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,aAAa,KAAK;AACxB,YAAM,gBAAgB,KAAK;AAE3B,YAAM,UAAU,cAAc;AAE9B,UAAI,CAAC,WAAW,YAAY;AAC3B,mBAAW,aAAa,oCAAoC,OAAO;AAAA,MACpE;AACA,UAAI,CAAC,WAAW,aAAa;AAC5B,mBAAW,cAAc,iCAAiC;AAAA,MAC3D;AACA,UAAI,CAAC,WAAW,iBAAiB;AAChC,mBAAW,kBAAkB,iCAAiC;AAAA,MAC/D;AAEA,UAAI,CAAC,cAAc,OAAO,WAAW,eAAe,WAAW,iBAAiB;AAC/E,sBAAc,MAAM,IAAI,gBAAgB;AAAA,UACvC,OAAO,WAAW;AAAA,UAClB,OAAO,WAAW;AAAA,QACnB,CAAC;AAAA,MACF;AACA,UAAI,cAAc,OAAO,WAAW,eAAe,WAAW,iBAAiB;AAC9E,sBAAc,IAAI,OAAO,WAAW,aAAa,WAAW,eAAe;AAAA,MAC5E;AAEA,YAAM,SAAU,WAAW,iBAAiB;AAC5C,UAAI,KAAK,aAAa,UAAU,GAAG;AAClC,aAAK,mBAAmB,QAAQ,OAAO;AACvC,cAAM,QAAQ,WAAW;AACzB,YAAI,OAAO;AACV,eAAK,UAAU,yBAAyB,QAAQ,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC;AAClF,eAAK,UAAU,4BAA4B,QAAQ,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,GAAG;AAAA,QAC7E;AACA;AAAA,MACD;AAEA,UAAI,CAAC,WAAW,KAAM;AACtB,WAAK,iBAAiB,aAAa,YAAY,KAAK;AAAA,IACrD;AAAA,EACD;AAAA,EAEQ,mBAAmB,QAAgB,SAA4C;AACtF,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,SAAK,UAAU,mBAAmB,QAAQ;AAAA,MACzC,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,WAAW,QAAQ,aAAa,eAAe;AAAA,MAC/C,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,WAAW,QAAQ,aAAa,eAAe;AAAA,MAC/C,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,SAAS,QAAQ,WAAW,eAAe;AAAA,IAC5C,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AAmCO,IAAMC,wBAAuB,eASlC;AAAA,EACD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI;AAAA,IACH,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI,aAAa;AAAA,IACjB,IAAI;AAAA,EACL;AAAA,EACD,cAAc,CAAC,SAAS;AAAA,IACvB,UAAU,MAAM,IAAI,KAAK,SAAS;AAAA,IAClC,YAAY,MAAM,IAAI,KAAK,WAAW,KAAK;AAAA,IAC3C,cAAc,MAAM,IAAI,KAAK,aAAa,KAAK;AAAA,IAC/C,qBAAqB,CAAC,QAAQ,IAAI,KAAK,gBAAgB,GAAG;AAAA,EAC3D;AACD,CAAC;","names":["Platformer3DState","Platformer3DEvent","Platformer3DBehavior"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/cooldown/cooldown-store.ts","../src/lib/behaviors/cooldown/cooldown-behavior.ts"],"sourcesContent":["import { proxy } from 'valtio';\n\n/**\n * A single cooldown entry in the global store.\n */\nexport interface CooldownEntry {\n\t/** Identifier for this cooldown */\n\tname: string;\n\t/** Total cooldown time in seconds */\n\tduration: number;\n\t/** Time accumulated since last fire */\n\telapsed: number;\n\t/** Whether the cooldown is ready (elapsed >= duration) */\n\tready: boolean;\n\t/** Normalized progress 0..1 (0 = just fired, 1 = ready) */\n\tprogress: number;\n\t/** Whether this cooldown is currently counting down */\n\tactive: boolean;\n}\n\n/**\n * Global reactive cooldown registry (valtio proxy).\n * Accessible from entities, behaviors, and UI components.\n */\nconst cooldownStore = proxy<Record<string, CooldownEntry>>({});\n\n/**\n * Register a named cooldown in the global store.\n * If a cooldown with the same name already exists, it is overwritten.\n *\n * @param name Unique cooldown identifier\n * @param duration Cooldown duration in seconds\n * @param immediate If true, the cooldown starts ready (default: true)\n */\nexport function registerCooldown(\n\tname: string,\n\tduration: number,\n\timmediate: boolean = true,\n): void {\n\tcooldownStore[name] = {\n\t\tname,\n\t\tduration,\n\t\telapsed: immediate ? duration : 0,\n\t\tready: immediate,\n\t\tprogress: immediate ? 1 : 0,\n\t\tactive: !immediate,\n\t};\n}\n\n/**\n * Get a cooldown entry by name.\n * Returns undefined if the cooldown has not been registered.\n */\nexport function getCooldown(name: string): CooldownEntry | undefined {\n\treturn cooldownStore[name];\n}\n\n/**\n * Fire (trigger) a cooldown, resetting its timer.\n * Sets the cooldown to not-ready and starts the countdown.\n */\nexport function fireCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = 0;\n\tentry.ready = false;\n\tentry.progress = 0;\n\tentry.active = true;\n}\n\n/**\n * Force a cooldown to ready immediately.\n */\nexport function resetCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = entry.duration;\n\tentry.ready = true;\n\tentry.progress = 1;\n\tentry.active = false;\n}\n\n/**\n * Advance all active cooldowns by delta seconds.\n * Called once per frame by the CooldownBehavior system.\n */\nexport function tickCooldowns(delta: number): void {\n\tfor (const name in cooldownStore) {\n\t\tconst entry = cooldownStore[name];\n\t\tif (!entry.active) continue;\n\n\t\tentry.elapsed += delta;\n\t\tif (entry.elapsed >= entry.duration) {\n\t\t\tentry.elapsed = entry.duration;\n\t\t\tentry.ready = true;\n\t\t\tentry.progress = 1;\n\t\t\tentry.active = false;\n\t\t} else {\n\t\t\tentry.progress = entry.duration > 0 ? entry.elapsed / entry.duration : 1;\n\t\t}\n\t}\n}\n\n/**\n * Get the raw cooldown store (for advanced/debugging use).\n */\nexport function getCooldownStore(): Record<string, CooldownEntry> {\n\treturn cooldownStore;\n}\n","/**\n * CooldownBehavior\n *\n * A stage-scoped behavior that manages named cooldowns via the global CooldownStore.\n * Entities use this to register, check, and fire cooldowns.\n * UI components (CooldownIcon) can read from the same store.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tregisterCooldown,\n\tgetCooldown,\n\tfireCooldown,\n\tresetCooldown,\n\ttickCooldowns,\n} from './cooldown-store';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CooldownConfig {\n\t/** Duration in seconds */\n\tduration: number;\n\t/** Whether the cooldown starts ready (default: true) */\n\timmediate?: boolean;\n}\n\nexport interface CooldownOptions {\n\t/**\n\t * Named cooldowns to register.\n\t * Keys are the cooldown names, values are their config.\n\t */\n\tcooldowns: Record<string, CooldownConfig>;\n}\n\ninterface CompiledCooldownEntry {\n\tname: string;\n\tduration: number;\n\timmediate: boolean;\n}\n\nconst COOLDOWN_BEHAVIOR_KEY = Symbol.for('zylem:behavior:cooldown');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Handle methods returned by entity.use(CooldownBehavior, ...).\n */\nexport interface CooldownHandle {\n\t/** Check if a named cooldown is ready */\n\tisReady(name: string): boolean;\n\t/** Fire (trigger) a named cooldown, resetting its timer */\n\tfire(name: string): void;\n\t/** Force a cooldown to ready immediately */\n\treset(name: string): void;\n\t/** Get cooldown progress 0..1 (0 = just fired, 1 = ready) */\n\tgetProgress(name: string): number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// System\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass CooldownSystem implements BehaviorSystem {\n\tconstructor(\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) { }\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(COOLDOWN_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tensureCooldownEntriesRegistered(link.ref as BehaviorRef<CooldownOptions>);\n\t\t\t}\n\t\t}\n\n\t\ttickCooldowns(delta);\n\t}\n\n\tdestroy(_ecs: unknown): void { }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle factory\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction createCooldownHandle(ref: BehaviorRef<CooldownOptions>): CooldownHandle {\n\tensureCooldownEntriesRegistered(ref);\n\n\treturn {\n\t\tisReady: (name: string) => getCooldown(name)?.ready ?? false,\n\t\tfire: (name: string) => fireCooldown(name),\n\t\treset: (name: string) => resetCooldown(name),\n\t\tgetProgress: (name: string) => getCooldown(name)?.progress ?? 1,\n\t};\n}\n\nfunction getCompiledCooldownEntries(\n\tref: BehaviorRef<CooldownOptions>,\n): CompiledCooldownEntry[] {\n\tconst compiled = (ref as any).__compiledCooldownEntries as CompiledCooldownEntry[] | undefined;\n\tif (compiled) return compiled;\n\n\tconst entries = Object.entries(ref.options.cooldowns).map(([name, config]) => ({\n\t\tname,\n\t\tduration: config.duration,\n\t\timmediate: config.immediate ?? true,\n\t}));\n\t(ref as any).__compiledCooldownEntries = entries;\n\treturn entries;\n}\n\nfunction ensureCooldownEntriesRegistered(ref: BehaviorRef<CooldownOptions>): void {\n\tif ((ref as any).__cooldownsRegistered) return;\n\n\tfor (const entry of getCompiledCooldownEntries(ref)) {\n\t\tregisterCooldown(entry.name, entry.duration, entry.immediate);\n\t}\n\n\t(ref as any).__cooldownsRegistered = true;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior Definition\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * CooldownBehavior\n *\n * @example\n * ```ts\n * import { CooldownBehavior } from '@zylem/game-lib/behavior';\n * import { moveBy } from '@zylem/game-lib/actions';\n *\n * const hero = createBox({ ... });\n * const cd = hero.use(CooldownBehavior, {\n * cooldowns: {\n * attack: { duration: 5 },\n * dash: { duration: 2, immediate: false },\n * },\n * });\n *\n * hero.onUpdate(({ me, inputs }) => {\n * if (cd.isReady('attack') && inputs.p1.buttons.A.pressed) {\n * cd.fire('attack');\n * // perform attack\n * }\n * if (cd.isReady('dash') && inputs.p1.buttons.B.pressed) {\n * cd.fire('dash');\n * me.runAction(moveBy({ x: 10, duration: 0.3 }));\n * }\n * });\n * ```\n */\nexport const CooldownBehavior = defineBehavior<CooldownOptions, CooldownHandle>({\n\tname: 'cooldown',\n\tdefaultOptions: { cooldowns: {} },\n\tsystemFactory: (ctx) =>\n\t\tnew CooldownSystem(ctx.getBehaviorLinks),\n\tcreateHandle: createCooldownHandle,\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/behaviors/top-down-movement/top-down-movement.behavior.ts","../src/lib/behaviors/top-down-movement/top-down-movement.descriptor.ts"],"sourcesContent":["import {\n\tangleFromDirection2D,\n\tnormalizeDirection2D,\n} from '../shared/direction-2d';\nimport type {\n\tTopDownMovementComponent,\n\tTopDownMovementInputComponent,\n\tTopDownMovementStateComponent,\n} from './components';\n\nexport interface TopDownMovementEntity {\n\tbody: {\n\t\tlinvel(): { x: number; y: number; z: number };\n\t};\n\ttransformStore?: {\n\t\tvelocity: { x: number; y: number; z: number };\n\t\tdirty: { velocity: boolean };\n\t};\n\tsetRotationZ?: (angle: number) => void;\n\ttopDownMovement: TopDownMovementComponent;\n\t$topDownMovement: TopDownMovementInputComponent;\n\ttopDownMovementState: TopDownMovementStateComponent;\n}\n\nexport class TopDownMovementRuntimeBehavior {\n\tupdateEntity(entity: TopDownMovementEntity, _delta: number): void {\n\t\tif (\n\t\t\t!entity.body\n\t\t\t|| !entity.transformStore\n\t\t\t|| !entity.topDownMovement\n\t\t\t|| !entity.$topDownMovement\n\t\t\t|| !entity.topDownMovementState\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst movement = entity.topDownMovement;\n\t\tconst input = entity.$topDownMovement;\n\t\tconst state = entity.topDownMovementState;\n\t\tconst moveDirection = normalizeDirection2D(input.moveX, input.moveY);\n\t\tconst velocity = entity.body.linvel();\n\n\t\tstate.moving = moveDirection !== null;\n\t\tentity.transformStore.velocity.x = (moveDirection?.x ?? 0) * movement.moveSpeed;\n\t\tentity.transformStore.velocity.y = (moveDirection?.y ?? 0) * movement.moveSpeed;\n\t\tentity.transformStore.velocity.z = velocity.z;\n\t\tentity.transformStore.dirty.velocity = true;\n\n\t\tconst facingDirection = normalizeDirection2D(input.faceX, input.faceY);\n\t\tif (facingDirection) {\n\t\t\tstate.facingAngle = angleFromDirection2D(facingDirection);\n\t\t}\n\n\t\tentity.setRotationZ?.(state.facingAngle);\n\t}\n}\n","import { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tcreateTopDownMovementComponent,\n\tcreateTopDownMovementInputComponent,\n\tcreateTopDownMovementStateComponent,\n} from './components';\nimport {\n\tTopDownMovementRuntimeBehavior,\n\ttype TopDownMovementEntity,\n} from './top-down-movement.behavior';\nimport {\n\tStageTopDownPlane,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport interface TopDownMovementBehaviorOptions {\n\tmoveSpeed?: number;\n}\n\nexport interface TopDownMovementHandle {\n\tgetFacingAngle(): number;\n}\n\nconst defaultOptions: TopDownMovementBehaviorOptions = {\n\tmoveSpeed: 8,\n};\n\nconst TOP_DOWN_MOVEMENT_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:top-down-movement',\n);\n\nfunction createTopDownMovementHandle(\n\tref: BehaviorRef<TopDownMovementBehaviorOptions>,\n): TopDownMovementHandle {\n\treturn {\n\t\tgetFacingAngle: () =>\n\t\t\t(ref as any).__entity?.topDownMovementState?.facingAngle ?? 0,\n\t};\n}\n\nclass TopDownMovementBehaviorSystem implements BehaviorSystem {\n\tprivate behavior = new TopDownMovementRuntimeBehavior();\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(TOP_DOWN_MOVEMENT_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst entity = link.entity as any;\n\t\t\tconst ref = link.ref as any;\n\n\t\t\tif (!entity.topDownMovement) {\n\t\t\t\tentity.topDownMovement = createTopDownMovementComponent(ref.options);\n\t\t\t}\n\t\t\tif (!entity.$topDownMovement) {\n\t\t\t\tentity.$topDownMovement = createTopDownMovementInputComponent();\n\t\t\t}\n\t\t\tif (!entity.topDownMovementState) {\n\t\t\t\tentity.topDownMovementState = createTopDownMovementStateComponent();\n\t\t\t}\n\n\t\t\tref.__entity = entity;\n\n\t\t\tconst handle = (entity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, ref.options as TopDownMovementBehaviorOptions);\n\t\t\t\tconst input = entity.$topDownMovement;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.setTopDownInput(handle, input.moveX ?? 0, input.moveY ?? input.moveZ ?? 0);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!entity.body || !entity.transformStore) continue;\n\t\t\tthis.behavior.updateEntity(entity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: TopDownMovementBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachTopDown(handle, {\n\t\t\tplane: StageTopDownPlane.Xy,\n\t\t\tspeed: options.moveSpeed ?? defaultOptions.moveSpeed!,\n\t\t\tfaceMovement: true,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\nexport const TopDownMovementBehavior = defineBehavior<\n\tTopDownMovementBehaviorOptions,\n\tTopDownMovementHandle,\n\tTopDownMovementEntity\n>({\n\tname: 'top-down-movement',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew TopDownMovementBehaviorSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createTopDownMovementHandle,\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/coordinators/multidirectional-space-shooter.coordinator.ts"],"sourcesContent":["import type { GameEntity } from '../entities/entity';\nimport {\n\tcreateThrusterInputComponent,\n\ttype ThrusterInputComponent,\n} from '../behaviors/thruster/components';\nimport type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../behaviors/shooter-2d/shooter-2d.descriptor';\nimport { getEntityFacingAngle2D, resolveAim2D } from './shared/top-down-aim';\n\nexport interface MultidirectionalSpaceShooterInput {\n\tmoveX: number;\n\tmoveY: number;\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\ntype MultidirectionalSpaceShooterEntity = GameEntity<any> & Shooter2DSourceEntity & {\n\t$thruster?: ThrusterInputComponent;\n};\n\nexport class MultidirectionalSpaceShooterCoordinator {\n\tconstructor(\n\t\tprivate entity: MultidirectionalSpaceShooterEntity,\n\t\tprivate shooter: Shooter2DHandle,\n\t\tprivate stage: Shooter2DStageLike,\n\t) {}\n\n\tpublic update(input: MultidirectionalSpaceShooterInput): void {\n\t\tif (!this.entity.$thruster) {\n\t\t\tthis.entity.$thruster = createThrusterInputComponent();\n\t\t}\n\n\t\tthis.entity.$thruster.thrustX = input.moveX;\n\t\tthis.entity.$thruster.thrustY = input.moveY;\n\t\tthis.entity.$thruster.thrust = 0;\n\t\tthis.entity.$thruster.rotate = 0;\n\n\t\tconst aim = resolveAim2D(\n\t\t\tinput.aimX,\n\t\t\tinput.aimY,\n\t\t\tgetEntityFacingAngle2D(this.entity),\n\t\t);\n\t\tif (aim.hasAimInput) {\n\t\t\tthis.entity.setRotationZ(aim.angle);\n\t\t}\n\n\t\tif (!input.shootPressed && !input.shootHeld) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sourcePosition = this.entity.body?.translation();\n\t\tif (!sourcePosition) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid this.shooter.fire({\n\t\t\tsource: this.entity,\n\t\t\tstage: this.stage,\n\t\t\ttarget: {\n\t\t\t\tx: sourcePosition.x + aim.direction.x,\n\t\t\t\ty: sourcePosition.y + aim.direction.y,\n\t\t\t},\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AAyBO,IAAM,0CAAN,MAA8C;AAAA,EACpD,YACS,QACA,SACA,OACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EAGF,OAAO,OAAgD;AAC7D,QAAI,CAAC,KAAK,OAAO,WAAW;AAC3B,WAAK,OAAO,YAAY,6BAA6B;AAAA,IACtD;AAEA,SAAK,OAAO,UAAU,UAAU,MAAM;AACtC,SAAK,OAAO,UAAU,UAAU,MAAM;AACtC,SAAK,OAAO,UAAU,SAAS;AAC/B,SAAK,OAAO,UAAU,SAAS;AAE/B,UAAM,MAAM;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN,uBAAuB,KAAK,MAAM;AAAA,IACnC;AACA,QAAI,IAAI,aAAa;AACpB,WAAK,OAAO,aAAa,IAAI,KAAK;AAAA,IACnC;AAEA,QAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C;AAAA,IACD;AAEA,UAAM,iBAAiB,KAAK,OAAO,MAAM,YAAY;AACrD,QAAI,CAAC,gBAAgB;AACpB;AAAA,IACD;AAEA,SAAK,KAAK,QAAQ,KAAK;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,QACP,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,QACpC,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,MACrC;AAAA,IACD,CAAC;AAAA,EACF;AACD;","names":[]}
|