@lagless/create 0.0.36 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +26 -0
  2. package/dist/index.js +96 -16
  3. package/dist/index.js.map +1 -1
  4. package/package.json +5 -5
  5. package/templates/pixi-react/AGENTS.md +57 -27
  6. package/templates/pixi-react/CLAUDE.md +225 -49
  7. package/templates/pixi-react/README.md +16 -6
  8. package/templates/pixi-react/__packageName__-backend/package.json +1 -0
  9. package/templates/pixi-react/__packageName__-backend/src/main.ts +2 -0
  10. package/templates/pixi-react/__packageName__-frontend/package.json +8 -1
  11. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/grid-background.tsx +4 -0
  12. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/player-view.tsx +68 -0
  13. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/runner-provider.tsx +57 -0
  14. package/templates/pixi-react/__packageName__-frontend/src/app/hooks/use-start-multiplayer-match.ts +5 -5
  15. package/templates/pixi-react/__packageName__-frontend/src/app/screens/title.screen.tsx +18 -1
  16. package/templates/pixi-react/__packageName__-frontend/{vite.config.ts → vite.config.ts.ejs} +0 -2
  17. package/templates/pixi-react/__packageName__-simulation/package.json +7 -0
  18. package/templates/pixi-react/__packageName__-simulation/src/lib/arena.ts +12 -0
  19. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/ecs.yaml +90 -6
  20. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/apply-move-input.system.ts +73 -0
  21. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/boundary.system.ts +2 -0
  22. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/damping.system.ts +2 -0
  23. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/index.ts +8 -0
  24. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/integrate.system.ts +2 -0
  25. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/physics-step.system.ts +65 -0
  26. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-connection.system.ts +158 -0
  27. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-leave.system.ts +70 -0
  28. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/save-prev-transform.system.ts +46 -0
  29. package/templates/pixi-react/docs/01-schema-and-codegen.md +244 -0
  30. package/templates/pixi-react/docs/02-ecs-systems.md +293 -0
  31. package/templates/pixi-react/docs/03-determinism.md +204 -0
  32. package/templates/pixi-react/docs/04-input-system.md +255 -0
  33. package/templates/pixi-react/docs/05-signals.md +175 -0
  34. package/templates/pixi-react/docs/06-rendering.md +256 -0
  35. package/templates/pixi-react/docs/07-multiplayer.md +277 -0
  36. package/templates/pixi-react/docs/08-physics2d.md +266 -0
  37. package/templates/pixi-react/docs/08-physics3d.md +312 -0
  38. package/templates/pixi-react/docs/09-recipes.md +362 -0
  39. package/templates/pixi-react/docs/10-common-mistakes.md +224 -0
  40. package/templates/pixi-react/docs/api-quick-reference.md +254 -0
  41. package/templates/pixi-react/package.json +6 -0
  42. /package/templates/pixi-react/__packageName__-backend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  43. /package/templates/pixi-react/__packageName__-frontend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  44. /package/templates/pixi-react/__packageName__-simulation/{.swcrc → .swcrc.ejs} +0 -0
  45. /package/templates/pixi-react/__packageName__-simulation/{tsconfig.json → tsconfig.json.ejs} +0 -0
  46. /package/templates/pixi-react/{tsconfig.base.json → tsconfig.base.json.ejs} +0 -0
@@ -0,0 +1,244 @@
1
+ # Schema & Codegen
2
+
3
+ ## Overview
4
+
5
+ All ECS structure is defined in a single YAML file: `<game>-simulation/src/lib/schema/ecs.yaml`. After editing, run `pnpm codegen` to regenerate TypeScript code into `src/lib/code-gen/`.
6
+
7
+ **Never edit files in `code-gen/` manually** — they are overwritten on every codegen run.
8
+
9
+ ## Codegen Command
10
+
11
+ ```bash
12
+ pnpm codegen
13
+ # Or directly:
14
+ npx @lagless/codegen -c <game>-simulation/src/lib/schema/ecs.yaml
15
+ ```
16
+
17
+ ## Field Types
18
+
19
+ | YAML Type | TypedArray | Bytes | Range |
20
+ |-----------|-----------|-------|-------|
21
+ | `uint8` | Uint8Array | 1 | 0 to 255 |
22
+ | `uint16` | Uint16Array | 2 | 0 to 65,535 |
23
+ | `uint32` | Uint32Array | 4 | 0 to 4,294,967,295 |
24
+ | `int8` | Int8Array | 1 | -128 to 127 |
25
+ | `int16` | Int16Array | 2 | -32,768 to 32,767 |
26
+ | `int32` | Int32Array | 4 | -2,147,483,648 to 2,147,483,647 |
27
+ | `float32` | Float32Array | 4 | ~7 significant digits |
28
+ | `float64` | Float64Array | 8 | ~15 significant digits |
29
+ | `uint8[N]` | Uint8Array | N | Fixed-size byte array (e.g., UUIDs) |
30
+
31
+ ## Components
32
+
33
+ Components define per-entity data stored in Structure-of-Arrays layout.
34
+
35
+ ```yaml
36
+ components:
37
+ # Regular component with fields
38
+ Transform2d:
39
+ positionX: float32
40
+ positionY: float32
41
+ prevPositionX: float32
42
+ prevPositionY: float32
43
+
44
+ # Component with mixed types
45
+ PlayerBody:
46
+ playerSlot: uint8
47
+ radius: float32
48
+ health: uint16
49
+
50
+ # Tag component — no fields, zero memory, bitmask-only
51
+ Frozen: {}
52
+
53
+ # Also a tag (equivalent to {})
54
+ Dead:
55
+ ```
56
+
57
+ ### Tag Components
58
+
59
+ Components with no fields (`{}` or empty body) are automatically detected as **tags**. They:
60
+ - Occupy zero memory per entity (only a bitmask bit)
61
+ - Work in filters and prefabs like normal components
62
+ - Used for state flags: `Frozen`, `Dead`, `Invincible`, etc.
63
+
64
+ ### Component IDs
65
+
66
+ Component IDs are sequential bit indices (0, 1, 2, ...) assigned in YAML declaration order. The framework supports up to **64 component types** (auto-detected: 1 Uint32 mask word for ≤32, 2 words for 33-64).
67
+
68
+ ## Singletons
69
+
70
+ Global typed fields — one copy per simulation (not per entity).
71
+
72
+ ```yaml
73
+ singletons:
74
+ GameState:
75
+ gamePhase: uint8 # 0=lobby, 1=playing, 2=gameover
76
+ roundTimer: uint32
77
+ arenaRadius: float32
78
+ ```
79
+
80
+ Access in systems: `this._gameState.gamePhase` (read/write directly).
81
+
82
+ ## Player Resources
83
+
84
+ Per-player data indexed by player slot (0 to maxPlayers-1).
85
+
86
+ ```yaml
87
+ playerResources:
88
+ PlayerResource:
89
+ id: uint8[16] # Player UUID
90
+ entity: uint32 # Player's entity ID
91
+ connected: uint8 # Boolean: is connected
92
+ score: uint32
93
+ lastReportedHash: uint32 # Required for hash verification
94
+ lastReportedHashTick: uint32 # Required for hash verification
95
+ hashMismatchCount: uint16 # Required for hash verification
96
+ ```
97
+
98
+ Access in systems: `this._playerResource.score[slot]`.
99
+
100
+ **Hash verification fields** (`lastReportedHash`, `lastReportedHashTick`, `hashMismatchCount`) are required if you use hash-based divergence detection.
101
+
102
+ ## Inputs (RPCs)
103
+
104
+ Inputs define the data structure for client→server→client messages.
105
+
106
+ ```yaml
107
+ inputs:
108
+ # Server event — emitted by server hooks (onPlayerJoin, etc.)
109
+ PlayerJoined:
110
+ slot: uint8
111
+ playerId: uint8[16]
112
+
113
+ PlayerLeft:
114
+ slot: uint8
115
+ reason: uint8
116
+
117
+ # Player input — sent by client via drainInputs/addRPC
118
+ MoveInput:
119
+ directionX: float32
120
+ directionY: float32
121
+
122
+ # Hash reporting — required for divergence detection
123
+ ReportHash:
124
+ hash: uint32
125
+ atTick: uint32
126
+ ```
127
+
128
+ ### Input Conventions
129
+ - **Server events** (PlayerJoined, PlayerLeft): emitted by `ctx.emitServerEvent()` in RoomHooks
130
+ - **Player inputs** (MoveInput, etc.): sent by client via `addRPC()` in `drainInputs`
131
+ - **ReportHash**: automatically sent by `createHashReporter()` — don't send manually
132
+
133
+ ## Filters
134
+
135
+ Filters maintain live entity lists matching include/exclude component masks.
136
+
137
+ ```yaml
138
+ filters:
139
+ PlayerFilter:
140
+ include: [Transform2d, PlayerBody]
141
+
142
+ MovingFilter:
143
+ include: [Transform2d, Velocity2d]
144
+
145
+ FrozenPlayerFilter:
146
+ include: [Transform2d, PlayerBody, Frozen]
147
+
148
+ ActivePlayerFilter:
149
+ include: [Transform2d, PlayerBody]
150
+ exclude: [Frozen, Dead]
151
+ ```
152
+
153
+ Filters are iterable in systems: `for (const entity of this._filter) { ... }`.
154
+ Filter data lives in the shared ArrayBuffer and is restored on rollback.
155
+
156
+ ## Simulation Type
157
+
158
+ The `simulationType` field enables physics engine integration:
159
+
160
+ ```yaml
161
+ # No physics — manual velocity/position management
162
+ # (default, no simulationType field needed)
163
+
164
+ # Rapier 2D physics
165
+ simulationType: physics2d
166
+
167
+ # Rapier 3D physics
168
+ simulationType: physics3d
169
+ ```
170
+
171
+ ### Auto-Prepended Components
172
+
173
+ When `simulationType: physics2d`:
174
+ - **Transform2d** (6 fields): `positionX`, `positionY`, `rotation`, `prevPositionX`, `prevPositionY`, `prevRotation`
175
+ - **PhysicsRefs**: `bodyHandle: float64`, `colliderHandle: float64`, `bodyType: uint8`, `collisionLayer: uint16`
176
+ - **PhysicsRefsFilter** automatically created
177
+
178
+ When `simulationType: physics3d`:
179
+ - **Transform3d** (14 fields): `positionX/Y/Z`, `rotationX/Y/Z/W`, `prevPositionX/Y/Z`, `prevRotationX/Y/Z/W`
180
+ - **PhysicsRefs**: same as 2D
181
+ - **PhysicsRefsFilter** automatically created
182
+
183
+ **Do NOT declare Transform2d/Transform3d or PhysicsRefs manually** when using a simulationType — they are auto-prepended.
184
+
185
+ ## Generated Files
186
+
187
+ After running `pnpm codegen`, the following files are generated in `src/lib/code-gen/`:
188
+
189
+ | File | Contents |
190
+ |------|----------|
191
+ | `core.ts` | All component classes, singleton classes, filter classes, player resource classes |
192
+ | `runner.ts` | `<ProjectName>Runner` class extending `ECSRunner` with typed `Core` accessor |
193
+ | `input-registry.ts` | `<ProjectName>InputRegistry` with all input types registered |
194
+ | `prefabs.ts` | Helper prefab builders for common entity archetypes |
195
+
196
+ The runner class provides typed access to all ECS objects:
197
+ ```typescript
198
+ runner.Core.Transform2d // component
199
+ runner.Core.PlayerFilter // filter
200
+ runner.Core.GameState // singleton
201
+ runner.Core.PlayerResource // player resource
202
+ ```
203
+
204
+ ## Common Schema Patterns
205
+
206
+ ### Player Entity
207
+ ```yaml
208
+ components:
209
+ PlayerBody:
210
+ playerSlot: uint8
211
+ radius: float32
212
+ health: uint16
213
+ maxHealth: uint16
214
+
215
+ filters:
216
+ PlayerFilter:
217
+ include: [Transform2d, PlayerBody]
218
+ ```
219
+
220
+ ### Projectile with Lifetime
221
+ ```yaml
222
+ components:
223
+ Projectile:
224
+ ownerSlot: uint8
225
+ damage: uint16
226
+ spawnTick: uint32
227
+ lifetimeTicks: uint16
228
+ Velocity2d:
229
+ velocityX: float32
230
+ velocityY: float32
231
+
232
+ filters:
233
+ ProjectileFilter:
234
+ include: [Transform2d, Projectile, Velocity2d]
235
+ ```
236
+
237
+ ### Game Phases via Singleton
238
+ ```yaml
239
+ singletons:
240
+ GameState:
241
+ gamePhase: uint8 # enum: 0=waiting, 1=countdown, 2=playing, 3=gameover
242
+ phaseStartTick: uint32
243
+ roundNumber: uint8
244
+ ```
@@ -0,0 +1,293 @@
1
+ # ECS Systems
2
+
3
+ ## Anatomy of a System
4
+
5
+ Systems are classes decorated with `@ECSSystem()` that implement `IECSSystem`. Dependencies are injected via constructor.
6
+
7
+ ```typescript
8
+ import { ECSSystem, IECSSystem } from '@lagless/core';
9
+ import { Transform2d, Velocity2d, MovingFilter } from '../code-gen/core.js';
10
+
11
+ @ECSSystem()
12
+ export class IntegrateSystem implements IECSSystem {
13
+ constructor(
14
+ private readonly _transform: Transform2d,
15
+ private readonly _velocity: Velocity2d,
16
+ private readonly _filter: MovingFilter,
17
+ ) {}
18
+
19
+ update(tick: number): void {
20
+ for (const entity of this._filter) {
21
+ this._transform.unsafe.positionX[entity] += this._velocity.unsafe.velocityX[entity];
22
+ this._transform.unsafe.positionY[entity] += this._velocity.unsafe.velocityY[entity];
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## DI Injectable Tokens
29
+
30
+ Any of these types can be requested in a system's constructor:
31
+
32
+ | Type | What You Get |
33
+ |------|-------------|
34
+ | `Transform2d`, `PlayerBody`, etc. | Component class — access entity data |
35
+ | `PlayerFilter`, `MovingFilter`, etc. | Filter class — iterate matching entities |
36
+ | `GameState`, etc. | Singleton — global data fields |
37
+ | `PlayerResource` | Per-player data indexed by slot |
38
+ | `EntitiesManager` | Create/remove entities, add/remove components |
39
+ | `PRNG` | Deterministic random number generator |
40
+ | `ECSConfig` | Simulation configuration (maxEntities, tickRate, etc.) |
41
+ | `AbstractInputProvider` | Read RPCs via `collectTickRPCs()` |
42
+ | `ScoreSignal`, etc. | Signal classes — emit rollback-aware events |
43
+
44
+ ## Data Access Patterns
45
+
46
+ ### Hot Path — Unsafe Typed Arrays (Fastest)
47
+
48
+ Direct typed array access. No bounds checking. Use in system `update()` loops.
49
+
50
+ ```typescript
51
+ // Read
52
+ const x = this._transform.unsafe.positionX[entity];
53
+
54
+ // Write
55
+ this._transform.unsafe.positionX[entity] = 100;
56
+ this._transform.unsafe.positionY[entity] = 200;
57
+ ```
58
+
59
+ ### Convenient — Cursor (Single Entity)
60
+
61
+ Object-like access for setup/initialization code. Slower than unsafe.
62
+
63
+ ```typescript
64
+ const cursor = this._transform.getCursor(entity);
65
+ cursor.positionX = 100;
66
+ cursor.positionY = 200;
67
+ // cursor.positionX also readable
68
+ ```
69
+
70
+ ### Component Set (Bulk Init)
71
+
72
+ Set multiple fields at once:
73
+
74
+ ```typescript
75
+ this._transform.set(entity, {
76
+ positionX: 100,
77
+ positionY: 200,
78
+ prevPositionX: 100, // ALWAYS set prev = current on spawn!
79
+ prevPositionY: 200,
80
+ });
81
+ ```
82
+
83
+ ## Entity Lifecycle
84
+
85
+ ### Creating Entities
86
+
87
+ ```typescript
88
+ const entity = this._entities.createEntity();
89
+ this._entities.addComponent(entity, Transform2d);
90
+ this._entities.addComponent(entity, PlayerBody);
91
+
92
+ // Set initial data
93
+ this._transform.set(entity, {
94
+ positionX: spawnX,
95
+ positionY: spawnY,
96
+ prevPositionX: spawnX, // MUST match position to avoid interpolation jump
97
+ prevPositionY: spawnY,
98
+ });
99
+ this._playerBody.set(entity, { playerSlot: slot, radius: 20 });
100
+ ```
101
+
102
+ ### Removing Entities
103
+
104
+ ```typescript
105
+ this._entities.removeEntity(entity);
106
+ // Entity ID goes to recycling stack, will be reused
107
+ ```
108
+
109
+ ### Adding/Removing Components
110
+
111
+ ```typescript
112
+ // Add component to existing entity
113
+ this._entities.addComponent(entity, Frozen); // tag component
114
+
115
+ // Remove component
116
+ this._entities.removeComponent(entity, Frozen);
117
+
118
+ // Check if entity has component
119
+ if (this._entities.hasComponent(entity, Frozen)) { ... }
120
+ ```
121
+
122
+ ### Component Masks
123
+
124
+ Entity presence in filters is determined by component bitmasks. When you add/remove a component, the entity automatically enters/leaves matching filters.
125
+
126
+ ## Prefabs
127
+
128
+ Prefabs provide a fluent API for entity creation with multiple components:
129
+
130
+ ```typescript
131
+ import { Prefab } from '@lagless/core';
132
+
133
+ // In a system:
134
+ const entity = Prefab.create(this._entities)
135
+ .with(Transform2d, { positionX: 0, positionY: 0, prevPositionX: 0, prevPositionY: 0 })
136
+ .with(PlayerBody, { playerSlot: slot, radius: 20 })
137
+ .with(Velocity2d, { velocityX: 0, velocityY: 0 })
138
+ .build();
139
+ ```
140
+
141
+ ## Filter Iteration
142
+
143
+ Filters are iterable — they yield entity IDs matching their include/exclude masks.
144
+
145
+ ```typescript
146
+ // Basic iteration
147
+ for (const entity of this._filter) {
148
+ const x = this._transform.unsafe.positionX[entity];
149
+ // ...
150
+ }
151
+
152
+ // Check filter length
153
+ if (this._filter.length === 0) return;
154
+
155
+ // Access underlying array (advanced)
156
+ const entities = this._filter.entities; // number[]
157
+ ```
158
+
159
+ **Filter data lives in the ArrayBuffer** — it's automatically restored on rollback.
160
+
161
+ ## PRNG (Deterministic Random)
162
+
163
+ The PRNG state is stored in the ArrayBuffer, so it's restored on rollback. **Never use `Math.random()`.**
164
+
165
+ ```typescript
166
+ @ECSSystem()
167
+ export class SpawnSystem implements IECSSystem {
168
+ constructor(private readonly _prng: PRNG) {}
169
+
170
+ update(tick: number): void {
171
+ // Random float in [0, 1)
172
+ const f = this._prng.getFloat();
173
+
174
+ // Random integer in [from, to) — exclusive upper bound
175
+ const x = this._prng.getRandomInt(-500, 500);
176
+
177
+ // Random integer in [from, to] — inclusive upper bound
178
+ const y = this._prng.getRandomIntInclusive(1, 6);
179
+ }
180
+ }
181
+ ```
182
+
183
+ ## Player Resources
184
+
185
+ Per-player data indexed by slot (0 to maxPlayers-1). Stored in the ArrayBuffer.
186
+
187
+ ```typescript
188
+ @ECSSystem()
189
+ export class ScoreSystem implements IECSSystem {
190
+ constructor(private readonly _playerResource: PlayerResource) {}
191
+
192
+ update(tick: number): void {
193
+ // Read
194
+ const score = this._playerResource.score[slot];
195
+
196
+ // Write
197
+ this._playerResource.score[slot] += 100;
198
+
199
+ // Check connection
200
+ if (this._playerResource.connected[slot]) { ... }
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## ECSConfig
206
+
207
+ Access simulation configuration:
208
+
209
+ ```typescript
210
+ @ECSSystem()
211
+ export class MySystem implements IECSSystem {
212
+ constructor(private readonly _config: ECSConfig) {}
213
+
214
+ update(tick: number): void {
215
+ const maxE = this._config.maxEntities; // default 1024
216
+ const maxP = this._config.maxPlayers; // default 4
217
+ const dt = this._config.frameLength; // seconds per tick (e.g., 1/20)
218
+ const rate = this._config.tickRate; // ticks per second (e.g., 20)
219
+ }
220
+ }
221
+ ```
222
+
223
+ ## System Registration
224
+
225
+ Systems must be registered in the systems array in `systems/index.ts`. **Order matters** — systems execute sequentially in array order every tick.
226
+
227
+ ```typescript
228
+ import { IECSSystemClass } from '@lagless/core';
229
+ import { SavePrevTransformSystem } from './save-prev-transform.system.js';
230
+ import { PlayerConnectionSystem } from './player-connection.system.js';
231
+ import { ApplyMoveInputSystem } from './apply-move-input.system.js';
232
+ import { IntegrateSystem } from './integrate.system.js';
233
+ import { BoundarySystem } from './boundary.system.js';
234
+ import { PlayerLeaveSystem } from './player-leave.system.js';
235
+ import { HashVerificationSystem } from './hash-verification.system.js';
236
+
237
+ export const systems: IECSSystemClass[] = [
238
+ SavePrevTransformSystem, // 1. Store prev positions for interpolation
239
+ PlayerConnectionSystem, // 2. Handle join/leave events
240
+ ApplyMoveInputSystem, // 3. Read player inputs
241
+ IntegrateSystem, // 4. Apply velocities
242
+ BoundarySystem, // 5. Enforce boundaries
243
+ PlayerLeaveSystem, // 6. Cleanup disconnected entities
244
+ HashVerificationSystem, // 7. ALWAYS LAST — divergence detection
245
+ ];
246
+ ```
247
+
248
+ ## Complete System Example
249
+
250
+ A system that reads move inputs, sanitizes them, and applies velocity:
251
+
252
+ ```typescript
253
+ import { ECSSystem, IECSSystem, AbstractInputProvider, ECSConfig } from '@lagless/core';
254
+ import { MathOps } from '@lagless/math';
255
+ import { Transform2d, Velocity2d, PlayerBody, PlayerFilter, MoveInput } from '../code-gen/core.js';
256
+
257
+ const finite = (v: number): number => Number.isFinite(v) ? v : 0;
258
+
259
+ @ECSSystem()
260
+ export class ApplyMoveInputSystem implements IECSSystem {
261
+ constructor(
262
+ private readonly _input: AbstractInputProvider,
263
+ private readonly _transform: Transform2d,
264
+ private readonly _velocity: Velocity2d,
265
+ private readonly _playerBody: PlayerBody,
266
+ private readonly _filter: PlayerFilter,
267
+ private readonly _config: ECSConfig,
268
+ ) {}
269
+
270
+ update(tick: number): void {
271
+ const rpcs = this._input.collectTickRPCs(tick, MoveInput);
272
+ for (const rpc of rpcs) {
273
+ const slot = rpc.meta.playerSlot;
274
+
275
+ // ALWAYS sanitize RPC data
276
+ let dirX = finite(rpc.data.directionX);
277
+ let dirY = finite(rpc.data.directionY);
278
+ dirX = MathOps.clamp(dirX, -1, 1);
279
+ dirY = MathOps.clamp(dirY, -1, 1);
280
+
281
+ // Find entity for this player slot
282
+ for (const entity of this._filter) {
283
+ if (this._playerBody.unsafe.playerSlot[entity] !== slot) continue;
284
+
285
+ const speed = 200 * this._config.frameLength;
286
+ this._velocity.unsafe.velocityX[entity] = dirX * speed;
287
+ this._velocity.unsafe.velocityY[entity] = dirY * speed;
288
+ break;
289
+ }
290
+ }
291
+ }
292
+ }
293
+ ```