@dcl-regenesislabs/opendcl 0.2.1-26165320302.commit-e6effe4 → 0.2.1-26238928766.commit-28648d7

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 (45) hide show
  1. package/README.md +5 -3
  2. package/context/sdk7-cheat-sheet.md +4 -0
  3. package/dist/index.js +0 -12
  4. package/dist/index.js.map +1 -1
  5. package/extensions/dcl-init.ts +58 -6
  6. package/package.json +3 -3
  7. package/prompts/system.md +71 -41
  8. package/skills/add-3d-models/SKILL.md +120 -70
  9. package/skills/add-interactivity/SKILL.md +74 -2
  10. package/skills/advanced-input/SKILL.md +34 -1
  11. package/skills/advanced-rendering/SKILL.md +82 -9
  12. package/skills/animations-tweens/SKILL.md +203 -98
  13. package/skills/audio-analysis/SKILL.md +164 -0
  14. package/skills/audio-video/SKILL.md +184 -83
  15. package/skills/build-ui/SKILL.md +25 -2
  16. package/skills/camera-control/SKILL.md +78 -7
  17. package/skills/create-scene/SKILL.md +56 -13
  18. package/skills/deploy-scene/SKILL.md +12 -0
  19. package/skills/deploy-worlds/SKILL.md +35 -0
  20. package/skills/editor-gizmo/.gitignore +11 -0
  21. package/skills/editor-gizmo/SKILL.md +222 -0
  22. package/skills/editor-gizmo/src/__editor/camera.ts +277 -0
  23. package/skills/editor-gizmo/src/__editor/discovery.ts +186 -0
  24. package/skills/editor-gizmo/src/__editor/drag.ts +265 -0
  25. package/skills/editor-gizmo/src/__editor/gizmo.ts +496 -0
  26. package/skills/editor-gizmo/src/__editor/history.ts +72 -0
  27. package/skills/editor-gizmo/src/__editor/index.ts +137 -0
  28. package/skills/editor-gizmo/src/__editor/input.ts +55 -0
  29. package/skills/editor-gizmo/src/__editor/math-utils.ts +114 -0
  30. package/skills/editor-gizmo/src/__editor/persistence.ts +113 -0
  31. package/skills/editor-gizmo/src/__editor/selection.ts +157 -0
  32. package/skills/editor-gizmo/src/__editor/state.ts +117 -0
  33. package/skills/editor-gizmo/src/__editor/ui.tsx +697 -0
  34. package/skills/game-design/SKILL.md +1 -2
  35. package/skills/lighting-environment/SKILL.md +103 -56
  36. package/skills/multiplayer-sync/SKILL.md +31 -2
  37. package/skills/nft-blockchain/SKILL.md +45 -40
  38. package/skills/npcs/SKILL.md +180 -0
  39. package/skills/optimize-scene/SKILL.md +7 -2
  40. package/skills/particle-system/SKILL.md +222 -0
  41. package/skills/player-avatar/SKILL.md +133 -7
  42. package/skills/player-physics/SKILL.md +93 -0
  43. package/skills/scene-runtime/SKILL.md +9 -5
  44. package/skills/visual-feedback/SKILL.md +1 -0
  45. package/extensions/dcl-setup-ollama.ts +0 -312
@@ -0,0 +1,222 @@
1
+ ---
2
+ name: particle-system
3
+ description: Emit particles (fire, smoke, sparks, snow, magic, fireworks) from an entity in a Decentraland SDK7 scene with the ParticleSystem component. Covers emitter shapes (Point, Sphere, Cone, Box), continuous rate vs Burst emission, lifetime/size/color/velocity ranges, gravity and additionalForce, blend modes (ALPHA/ADD/MULTIPLY), billboard and faceTravelDirection, sprite-sheet texture animation, simulation space (local vs world), playback state, and per-scene particle budget. Use when the user asks for particles, sparks, fire, smoke, dust, fog, fireworks, magic effects, snowfall, rain, embers, trails, or atmospheric effects. Do NOT use for procedural entity motion (see animations-tweens), GLTF model effects (see add-3d-models), or 2D UI effects (see build-ui).
4
+ ---
5
+
6
+ # ParticleSystem (SDK7)
7
+
8
+ Emit particles from an entity. One `ParticleSystem` component per entity, attached alongside a `Transform`. No mesh required — particles render from the component itself.
9
+
10
+ ## Authoring split
11
+
12
+ The emitter **position** (entity Transform + optional GltfContainer of a torch/fire pit/fog vent) is static and belongs in `main-entities.ts`. `ParticleSystem` itself is **not** in the supported declarative list — attach it at runtime in `src/index.ts` via `getEntityOrNullByName`.
13
+
14
+ ```typescript
15
+ // main-entities.ts — emitter placement
16
+ campfire: {
17
+ components: {
18
+ Transform: { position: { x: 8, y: 0, z: 8 } },
19
+ GltfContainer: { src: 'models/campfire.glb' }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ```typescript
25
+ // src/index.ts — attach ParticleSystem
26
+ import { engine, ParticleSystem, PBParticleSystem_BlendMode } from '@dcl/sdk/ecs'
27
+ import { Color4 } from '@dcl/sdk/math'
28
+
29
+ export function main() {
30
+ const fire = engine.getEntityOrNullByName('campfire')
31
+ if (!fire) return
32
+ ParticleSystem.create(fire, { /* ... config ... */ })
33
+ }
34
+ ```
35
+
36
+ ## RULE: Transform.scale does NOT scale particles
37
+
38
+ Particle size is controlled exclusively by `initialSize` and `sizeOverTime` (`FloatRange`). The `ParticleSystem` also sets the emitter shape's spatial dimensions when the shape has size fields (Sphere radius, Box size, Cone radius). These are not affected by the entity's `Transform.scale`.
39
+
40
+ ## RULE: Particles only render to players inside scene parcels
41
+
42
+ Players viewing the scene from outside its parcels see nothing. Particles are not part of the scene LOD silhouette. Position emitters within parcel bounds.
43
+
44
+ ## RULE: Particles only work in the Unity explorer
45
+
46
+ The mobile Godot explorer and the Bevy explorer don't have this feature implemented. The renderer ignores the component. Design fallbacks (a glowing emissive sphere, a baked-in GLTF animation) for scenes that should look reasonable everywhere.
47
+
48
+ ## RULE: Engine caps total particles at ~1000
49
+
50
+ The engine enforces a per-scene particle budget and will scale down emission rates across all active systems if total live particles would exceed the limit. Cap each system with `maxParticles` and prefer fewer impactful systems over many small ones.
51
+
52
+ ## RULE: prewarm requires loop = true
53
+
54
+ `prewarm: true` only takes effect when `loop: true`. On a one-shot system (`loop: false`) prewarm is silently ignored.
55
+
56
+ ## RULE: faceTravelDirection overrides billboard
57
+
58
+ When `faceTravelDirection: true`, particles orient along their velocity vector and `billboard` is ignored. Use this for trails/streaks (asteroids, bullets, sparks). Set `billboard: false` explicitly to avoid confusion.
59
+
60
+ ## Import
61
+
62
+ ```typescript
63
+ import { engine, ParticleSystem } from '@dcl/sdk/ecs'
64
+ import {
65
+ PBParticleSystem_BlendMode,
66
+ PBParticleSystem_PlaybackState,
67
+ PBParticleSystem_SimulationSpace
68
+ } from '@dcl/sdk/ecs'
69
+ import { Color4, Vector3, Quaternion } from '@dcl/sdk/math'
70
+ ```
71
+
72
+ Aliases `ParticleSystemBlendMode` and `ParticleSystemPlaybackState` are also exported from `@dcl/sdk/ecs` and are interchangeable with the `PB`-prefixed names. There is no `ParticleSystemSimulationSpace` alias — only `PBParticleSystem_SimulationSpace`. Prefer the `PB`-prefixed names everywhere for consistency.
73
+
74
+ ## Field reference
75
+
76
+ | Field | Type | Default | Notes |
77
+ |---|---|---|---|
78
+ | `active` | `boolean` | `true` | Master on/off for new emission. |
79
+ | `rate` | `number` | `10` | Particles emitted per second (continuous). Set to `0` when using `bursts`. |
80
+ | `maxParticles` | `number` | `1000` | Hard cap on simultaneous live particles for this system. |
81
+ | `lifetime` | `number` | `5` | Particle lifespan in seconds. |
82
+ | `gravity` | `number` | `0` | Multiplier on scene gravity (~ -9.81 m/s²). Negative = particles rise. |
83
+ | `additionalForce` | `Vector3` | — | Constant force vector applied each frame (world space). |
84
+ | `initialSize` | `FloatRange` | `{start:1, end:1}` | Random size at spawn. |
85
+ | `sizeOverTime` | `FloatRange` | `{start:1, end:1}` | Size lerped start→end over particle lifetime. |
86
+ | `initialRotation` | `Quaternion` | identity | Spawn orientation. |
87
+ | `rotationOverTime` | `Quaternion` | identity | Per-axis angular velocity. |
88
+ | `faceTravelDirection` | `boolean` | `false` | Orient along velocity. Overrides `billboard`. |
89
+ | `initialColor` | `ColorRange` | `{white,white}` | Random color at spawn. |
90
+ | `colorOverTime` | `ColorRange` | `{white,white}` | Color lerped start→end. Use alpha=0 at end to fade out. |
91
+ | `initialVelocitySpeed` | `FloatRange` | `{start:1, end:1}` | Initial speed in m/s, randomized per particle. |
92
+ | `texture` | `Texture` | white quad | Particle sprite. Same `Texture` shape as Material textures. |
93
+ | `blendMode` | `PBParticleSystem_BlendMode` | `PSB_ALPHA` | `PSB_ALPHA` / `PSB_ADD` / `PSB_MULTIPLY`. |
94
+ | `billboard` | `boolean` | `true` | Particles always face camera. |
95
+ | `spriteSheet` | `{tilesX, tilesY, framesPerSecond?}` | — | Texture-atlas frame animation. |
96
+ | `shape` | oneof Point/Sphere/Cone/Box | Point | Emitter geometry. |
97
+ | `loop` | `boolean` | `true` | Loop emission cycle. `false` = one-shot. |
98
+ | `prewarm` | `boolean` | `false` | Start as if one full loop already simulated. Requires `loop: true`. |
99
+ | `simulationSpace` | `PBParticleSystem_SimulationSpace` | `PSS_LOCAL` | `PSS_LOCAL` (move with entity) / `PSS_WORLD` (stay put after spawn). |
100
+ | `limitVelocity` | `{speed, dampen?}` | — | Clamp top speed. `dampen` 0–1, default `1` = hard clamp. |
101
+ | `playbackState` | `PBParticleSystem_PlaybackState` | `PS_PLAYING` | `PS_PLAYING` / `PS_PAUSED` / `PS_STOPPED`. |
102
+ | `bursts` | `{values: Burst[]}` | — | Discrete emission events. |
103
+
104
+ `FloatRange = { start: number, end: number }`. `ColorRange = { start: Color4, end: Color4 }`.
105
+
106
+ ## Emitter shapes
107
+
108
+ Use `ParticleSystem.Shape.*` helpers — never assemble the `oneof` manually:
109
+
110
+ ```typescript
111
+ shape: ParticleSystem.Shape.Point()
112
+ shape: ParticleSystem.Shape.Sphere({ radius: 1 })
113
+ shape: ParticleSystem.Shape.Cone({ angle: 25, radius: 1 }) // angle = half-angle in degrees
114
+ shape: ParticleSystem.Shape.Box({ size: Vector3.create(1, 1, 1) })
115
+ ```
116
+
117
+ - **Point** — emits from entity origin.
118
+ - **Sphere** — random points inside a sphere of `radius`.
119
+ - **Cone** — base disk projecting outward within `angle` half-angle. Cone direction is the entity's local forward; rotate the parent Transform to aim it.
120
+ - **Box** — random points inside an axis-aligned box of `size`.
121
+
122
+ To rotate the emission direction (snow falling, rain), rotate the parent entity's `Transform.rotation` (in `main-entities.ts`).
123
+
124
+ ## Bursts
125
+
126
+ Discrete emission events at specific times. Set `rate: 0` to use bursts only, or combine with `rate > 0` for continuous + bursty.
127
+
128
+ ```typescript
129
+ bursts: {
130
+ values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
131
+ }
132
+ ```
133
+
134
+ Burst fields: `time` (s from cycle start), `count` (particles per burst), `cycles` (default `1`, `0` = infinite), `interval` (s between cycles, default `0.01`), `probability` (0–1 chance per cycle, default `1`). Multiple bursts in one cycle = staggered ignition pattern (fireworks).
135
+
136
+ ## Common patterns
137
+
138
+ ```typescript
139
+ // 1. Fire ember — Point + ADD blend, slight upward drift
140
+ const fire = engine.getEntityOrNullByName('campfire')
141
+ if (fire) ParticleSystem.create(fire, {
142
+ rate: 40,
143
+ lifetime: 2,
144
+ maxParticles: 200,
145
+ initialSize: { start: 0.1, end: 0.3 },
146
+ sizeOverTime: { start: 1.0, end: 0.0 },
147
+ initialColor: { start: Color4.create(1, 0.6, 0.1, 1), end: Color4.create(1, 0.2, 0, 1) },
148
+ colorOverTime: { start: Color4.create(1, 0.5, 0.1, 1), end: Color4.create(0.2, 0, 0, 0) },
149
+ initialVelocitySpeed: { start: 1.5, end: 2.5 },
150
+ gravity: -0.3,
151
+ blendMode: PBParticleSystem_BlendMode.PSB_ADD,
152
+ shape: ParticleSystem.Shape.Point()
153
+ })
154
+
155
+ // 2. One-shot burst — explosion/pickup VFX
156
+ ParticleSystem.create(entity, {
157
+ loop: false,
158
+ rate: 0,
159
+ lifetime: 3,
160
+ maxParticles: 150,
161
+ initialSize: { start: 0.1, end: 0.25 },
162
+ sizeOverTime: { start: 1.0, end: 0.0 },
163
+ initialVelocitySpeed: { start: 2, end: 4 },
164
+ shape: ParticleSystem.Shape.Sphere({ radius: 0.5 }),
165
+ bursts: {
166
+ values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
167
+ }
168
+ })
169
+ ```
170
+
171
+ ## Playback control
172
+
173
+ ```typescript
174
+ const ps = ParticleSystem.getMutable(entity)
175
+ ps.playbackState = PBParticleSystem_PlaybackState.PS_PAUSED // pause + freeze particles
176
+ ps.playbackState = PBParticleSystem_PlaybackState.PS_PLAYING // resume
177
+ ps.playbackState = PBParticleSystem_PlaybackState.PS_STOPPED // hard cut, clear live particles
178
+ ps.active = false // graceful trail-off
179
+ ```
180
+
181
+ ## Sprite-sheet animation
182
+
183
+ Texture atlas with frames laid out in a grid (left-to-right, top-to-bottom). Total frames = `tilesX * tilesY`.
184
+
185
+ ```typescript
186
+ texture: { src: 'images/flame-sheet.png' },
187
+ spriteSheet: { tilesX: 4, tilesY: 3, framesPerSecond: 12 }
188
+ ```
189
+
190
+ ## Simulation space (local vs world)
191
+
192
+ - `PSS_LOCAL` (default) — particles move with the emitter. A moving emitter drags its particle cloud. Good for auras / halos on moving entities.
193
+ - `PSS_WORLD` — particles stay at their spawn position in world space. A moving emitter leaves a trail. Required for proper trails combined with `Tween` movement on the emitter.
194
+
195
+ ## Texture field
196
+
197
+ ```typescript
198
+ texture: { src: 'images/spark.png' }
199
+ ```
200
+
201
+ The full `Texture` form supports filterMode/wrapMode but particle systems generally only need `src`. Avatar/Video textures on particles are unverified — stick with file textures.
202
+
203
+ ## Gotchas
204
+
205
+ - **`rotationOverTime`** is interpreted as per-axis angular velocity. `Quaternion.fromEulerDegrees(0, 90, 0)` = spin 90°/s on Y. Identity = no spin.
206
+ - **`additionalForce` is world-space** even when `simulationSpace = PSS_LOCAL`. Wind/drift directions stay constant regardless of emitter rotation.
207
+ - **`limitVelocity.dampen = 1`** = hard clamp. Lower values let velocity exceed cap briefly then decay.
208
+ - **Color alpha = 0 at end of `colorOverTime`** is the standard way to fade particles out.
209
+ - **No mesh attached to the emitter entity** — adding `MeshRenderer` is unrelated; particles render from the ParticleSystem component itself.
210
+
211
+ ## Performance
212
+
213
+ - Cap each system with `maxParticles`. Total scene budget across all systems is ~1000.
214
+ - Keep `lifetime * rate` low; that product is the steady-state live count.
215
+ - Disable systems out of view via `playbackState = PS_STOPPED` or `active = false`.
216
+ - Prefer `PSB_ALPHA` for opaque/translucent effects. `PSB_ADD` is best for glow/fire (it stacks visually) but multi-layer additive overdraw is the most expensive case.
217
+ - One texture per system.
218
+
219
+ ## Resources
220
+
221
+ - Test scene: https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0%2C7-particle-system
222
+ - Live tuner: `ParticleLab.dcl.eth` (open with Decentraland client).
@@ -5,6 +5,30 @@ description: Player and avatar system in Decentraland. Read player position/prof
5
5
 
6
6
  # Player and Avatar System in Decentraland
7
7
 
8
+ ## Authoring split
9
+
10
+ `AvatarShape` (the component used for NPCs and pre-placed avatars) is supported in `main-entities.ts` — declare the NPC fully there with id, name, wearables, etc.:
11
+
12
+ ```typescript
13
+ // main-entities.ts
14
+ shopkeeper: {
15
+ components: {
16
+ Transform: { position: { x: 8, y: 0, z: 8 } },
17
+ AvatarShape: {
18
+ id: 'shopkeeper-1',
19
+ name: 'Shopkeeper',
20
+ bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
21
+ wearables: [],
22
+ emotes: []
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ `AvatarAttach`, `AvatarModifierArea`, `AvatarBase`, `AvatarEquippedData` are **not** in the supported list — they're runtime by design (they bind to the live player, or apply to entities you create on-the-fly). Add them in `src/index.ts` and attach to entities looked up via `getEntityOrNullByName` (for static placement) or runtime-created entities (for player-bound effects).
29
+
30
+ The reserved `engine.PlayerEntity` is engine-managed and has no representation in `main-entities.ts`.
31
+
8
32
  ## Player Position and Movement
9
33
 
10
34
  Access the player's position via the reserved `engine.PlayerEntity`:
@@ -61,6 +85,36 @@ function main() {
61
85
  - `userId` — the player's Ethereum wallet address (or guest ID)
62
86
  - `isGuest` — `true` if the player hasn't connected a wallet
63
87
 
88
+ ### Fetch Full Avatar Profile from the Catalyst
89
+
90
+ `getPlayer()` returns the local view of the player. For full avatar data (wearables list, body shape, skin/hair/eye colors), fetch from the Catalyst:
91
+
92
+ ```typescript
93
+ import { executeTask, signedFetch } from '@dcl/sdk/network'
94
+ import { getPlayer } from '@dcl/sdk/players'
95
+
96
+ executeTask(async () => {
97
+ const player = getPlayer()
98
+ if (!player || player.isGuest) return
99
+
100
+ const res = await fetch(`https://peer.decentraland.org/lambdas/profiles/${player.userId}`)
101
+ const body = await res.json()
102
+
103
+ // Response is an array of profiles; the first entry holds the active avatar.
104
+ const profile = body?.avatars?.[0]
105
+ if (!profile) return
106
+
107
+ console.log('name:', profile.name)
108
+ console.log('wearables:', profile.avatar.wearables)
109
+ console.log('skin color:', profile.avatar.skin.color) // { r, g, b } — already unwrapped, NOT { color: { r,g,b } }
110
+ })
111
+ ```
112
+
113
+ **Gotchas:**
114
+ - The response is `{ avatars: [...] }`, not a flat profile object. Always read `body.avatars[0]`.
115
+ - Color fields (`skin.color`, `hair.color`, `eyes.color`) are already `{ r, g, b }` objects — don't unwrap one more level.
116
+ - The fetch is unauthenticated; for endpoints that need the player's signed identity, use `signedFetch` from `@dcl/sdk/network` instead of plain `fetch`.
117
+
64
118
  ## Avatar Attachments
65
119
 
66
120
  Attach 3D objects to a player's avatar:
@@ -79,12 +133,36 @@ AvatarAttach.create(hat, {
79
133
 
80
134
  ### Anchor Points
81
135
 
82
- ```typescript
83
- AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
84
- AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
85
- AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
86
- AvatarAnchorPointType.AAPT_POSITION // Avatar root position
87
- ```
136
+ Full enum of bones / positions an attachment can track. Inside `main-entities.ts` use the integer value (left). In `src/index.ts` use `AvatarAnchorPointType.<NAME>`.
137
+
138
+ | value | enum | location |
139
+ |---|---|---|
140
+ | 0 | AAPT_POSITION | avatar feet (deprecated — prefer `parent: engine.PlayerEntity`) |
141
+ | 1 | AAPT_NAME_TAG | above the name tag |
142
+ | 2 | AAPT_LEFT_HAND | left hand |
143
+ | 3 | AAPT_RIGHT_HAND | right hand |
144
+ | 4 | AAPT_HEAD | head bone |
145
+ | 5 | AAPT_NECK | neck |
146
+ | 6 | AAPT_SPINE | spine root |
147
+ | 7 | AAPT_SPINE1 | spine mid |
148
+ | 8 | AAPT_SPINE2 | spine top |
149
+ | 9 | AAPT_HIP | hip |
150
+ | 10 | AAPT_LEFT_SHOULDER | left shoulder |
151
+ | 11 | AAPT_LEFT_ARM | left upper arm |
152
+ | 12 | AAPT_LEFT_FOREARM | left forearm |
153
+ | 13 | AAPT_LEFT_HAND_INDEX | left index finger |
154
+ | 14 | AAPT_RIGHT_SHOULDER | right shoulder |
155
+ | 15 | AAPT_RIGHT_ARM | right upper arm |
156
+ | 16 | AAPT_RIGHT_FOREARM | right forearm |
157
+ | 17 | AAPT_RIGHT_HAND_INDEX | right index finger |
158
+ | 18 | AAPT_LEFT_UP_LEG | left thigh |
159
+ | 19 | AAPT_LEFT_LEG | left calf |
160
+ | 20 | AAPT_LEFT_FOOT | left foot |
161
+ | 21 | AAPT_LEFT_TOE_BASE | left toes |
162
+ | 22 | AAPT_RIGHT_UP_LEG | right thigh |
163
+ | 23 | AAPT_RIGHT_LEG | right calf |
164
+ | 24 | AAPT_RIGHT_FOOT | right foot |
165
+ | 25 | AAPT_RIGHT_TOE_BASE | right toes |
88
166
 
89
167
  ### Attach to a Specific Player
90
168
 
@@ -230,9 +308,46 @@ AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
230
308
  })
231
309
  ```
232
310
 
311
+ ## InputModifier — Restrict Player Movement
312
+
313
+ Disable specific movement modes for the local player. Useful for cutscenes, dialogue freezes, traversal puzzles. Applies to `engine.PlayerEntity`.
314
+
315
+ ```typescript
316
+ import { engine, InputModifier } from '@dcl/sdk/ecs'
317
+
318
+ InputModifier.createOrReplace(engine.PlayerEntity, {
319
+ mode: InputModifier.Mode.Standard({
320
+ disableWalk: false,
321
+ disableJog: false,
322
+ disableRun: false,
323
+ disableJump: false,
324
+ disableDoubleJump: false,
325
+ disableGliding: false
326
+ })
327
+ })
328
+ ```
329
+
330
+ Set any field to `true` to disable that mode. While disabled:
331
+ - Gravity still applies (the player still falls).
332
+ - The camera can still rotate freely.
333
+ - The player can still trigger pointer / proximity events.
334
+ - All restrictions are auto-lifted when the player leaves the scene.
335
+
336
+ ### Freeze All Movement
337
+
338
+ ```typescript
339
+ InputModifier.createOrReplace(engine.PlayerEntity, {
340
+ mode: InputModifier.Mode.Standard({ disableAll: true })
341
+ })
342
+ ```
343
+
344
+ To release: `InputModifier.deleteFrom(engine.PlayerEntity)` — or `createOrReplace` with all flags `false`.
345
+
233
346
  ## Teleporting the Player
234
347
 
235
- **You MUST use `movePlayerTo` from `~system/RestrictedActions` to move or teleport the player.** Setting `Transform.getMutable(engine.PlayerEntity).position` does NOT work — the runtime ignores direct writes to the player transform.
348
+ **You MUST use `movePlayerTo` from `~system/RestrictedActions` to move or teleport the player.** Setting `Transform.getMutable(engine.PlayerEntity).position` does NOT work — the runtime ignores direct writes to the player transform. `Transform` on `engine.PlayerEntity` is **read-only**; the same applies to `engine.CameraEntity`.
349
+
350
+ `movePlayerTo` only teleports the player **within the same scene**. Cross-scene teleports require explicit player consent and a different API.
236
351
 
237
352
  ```typescript
238
353
  import { movePlayerTo } from '~system/RestrictedActions'
@@ -249,6 +364,17 @@ void movePlayerTo({
249
364
  })
250
365
  ```
251
366
 
367
+ `movePlayerTo` returns a Promise — `await` it if you need to chain actions:
368
+
369
+ ```typescript
370
+ import { executeTask } from '@dcl/sdk/ecs'
371
+
372
+ executeTask(async () => {
373
+ await movePlayerTo({ newRelativePosition: Vector3.create(8, 0, 8) })
374
+ console.log('teleported, continuing flow')
375
+ })
376
+ ```
377
+
252
378
  ### Avatar Change Listeners
253
379
 
254
380
  React to avatar changes in real-time:
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: player-physics
3
+ description: Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
4
+ ---
5
+
6
+ # Player Physics in Decentraland
7
+
8
+ Apply forces to the player's avatar using the `Physics` API from `@dcl/sdk/ecs`. All physics operations affect the **local player** only.
9
+
10
+ ## Authoring split
11
+
12
+ Force calls are runtime — they live in `src/index.ts`. The **trigger entities** that cause forces (the launch pad, the wind tunnel volume, the repulsion field marker) are static placements and belong in `main-entities.ts` with a Transform (and optional `MeshRenderer` / `GltfContainer`). Wire pointer/proximity events or trigger systems in `src/index.ts` to call the `Physics.*` methods.
13
+
14
+ ## Why this skill exists — the Transform mistake
15
+
16
+ The player's `Transform` (on `engine.PlayerEntity`) is **engine-controlled and read-only from scene code**. Writing to it via `Transform.getMutable`, `Transform.createOrReplace`, or direct `.position` / `.rotation` mutation **silently does nothing** — the code compiles, the system ticks, no error is thrown, and the avatar never moves.
17
+
18
+ **If your goal is to lift, float, push, knock back, or apply any sustained force to the player, use this skill's `Physics` API.** For instant teleports / smooth slides to a specific position, use `movePlayerTo` from `~system/RestrictedActions` (skill: `player-avatar`).
19
+
20
+ ```typescript
21
+ // WRONG — has no effect in-world
22
+ const t = Transform.getMutable(engine.PlayerEntity)
23
+ t.position.y += 0.1 // ignored every frame
24
+
25
+ // CORRECT — lift the player upward
26
+ import { Physics } from '@dcl/sdk/ecs'
27
+ import { Vector3 } from '@dcl/sdk/math'
28
+ Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0)) // one-shot upward launch
29
+ // or, sustained lift / hover:
30
+ const lifter = engine.getEntityOrNullByName('hover_zone') // named entity from main-entities.ts
31
+ if (lifter) Physics.applyForceToPlayer(lifter, Vector3.create(0, 1, 0), 12)
32
+ // stop with: Physics.removeForceFromPlayer(lifter)
33
+ ```
34
+
35
+ ## Impulse (One-Shot Push)
36
+
37
+ Apply a single instantaneous force with `Physics.applyImpulseToPlayer(direction, magnitude?)`. Direction is auto-normalized when magnitude is passed separately. Multiple calls within the same frame are accumulated. Use for launch pads, jumps, and explosions.
38
+
39
+ ## Knockback (Push Away from a Point)
40
+
41
+ Push the player away from a source position with `Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?)`. Direction is computed automatically from source to player.
42
+
43
+ ### KnockbackFalloff Options
44
+
45
+ | Falloff | Behavior |
46
+ |---|---|
47
+ | `KnockbackFalloff.CONSTANT` | Same magnitude at any distance within radius (default) |
48
+ | `KnockbackFalloff.LINEAR` | Smooth linear decrease to 0 at the radius edge |
49
+ | `KnockbackFalloff.INVERSE_SQUARE` | Sharp, physically-realistic drop-off |
50
+
51
+ If the player is exactly at the source, they are pushed straight up. A **negative magnitude** pulls the player toward the point (gravity well). The same falloff values apply to `applyRepulsionForceToPlayer()`.
52
+
53
+ ## Continuous Force
54
+
55
+ Apply a persistent directional force identified by an **entity** (the force "owner"). Multiple forces from different entities stack.
56
+
57
+ - Apply: `Physics.applyForceToPlayer(entity, direction, magnitude?)`
58
+ - Remove: `Physics.removeForceFromPlayer(entity)`
59
+
60
+ Use with trigger zones for wind tunnels, conveyor belts, and gravity fields. The owner entity is just an ID handle for later removal — typically the named main-entities.ts entity representing the trigger zone.
61
+
62
+ ## Timed Force
63
+
64
+ Apply a force for a fixed duration: `Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?)`. Expires automatically.
65
+
66
+ ## Repulsion Force
67
+
68
+ Continuous push away from a fixed point with distance-based falloff, recalculated each frame: `Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius)`. The position must be passed explicitly — not read from the entity's Transform automatically. Remove with `Physics.removeForceFromPlayer(entity)`.
69
+
70
+ ## Coordinate Conversion (Local to World Direction)
71
+
72
+ Convert local direction to world space with `Transform.localToWorldDirection(entity, localDir)`. Use when pushing relative to an entity's orientation (e.g. "forward from this cannon").
73
+
74
+ ## Quick Reference
75
+
76
+ | Method | Type | Description |
77
+ |---|---|---|
78
+ | `Physics.applyImpulseToPlayer(dir, mag?)` | One-shot | Instant directional push |
79
+ | `Physics.applyKnockbackToPlayer(pos, mag, radius?, falloff?)` | One-shot | Push away from point |
80
+ | `Physics.applyForceToPlayer(entity, dir, mag?)` | Continuous | Persistent push while active |
81
+ | `Physics.removeForceFromPlayer(entity)` | Control | Stop a continuous force |
82
+ | `Physics.applyForceToPlayerForDuration(entity, secs, dir, mag?)` | Timed | Force that expires automatically |
83
+ | `Physics.applyRepulsionForceToPlayer(entity, pos, mag, radius)` | Continuous | Distance-based push from point |
84
+ | `Transform.localToWorldDirection(entity, dir)` | Utility | Convert local direction to world space |
85
+
86
+ ## Best Practices
87
+
88
+ - Use `applyImpulseToPlayer` for one-off events (jump pads, explosions, hits).
89
+ - Use `applyForceToPlayer` + `removeForceFromPlayer` with trigger zones for areas (wind tunnels, conveyor belts).
90
+ - Use `KnockbackFalloff.LINEAR` for most area effects — it feels natural and predictable.
91
+ - Always check `result.trigger?.entity !== engine.PlayerEntity` in trigger callbacks to only affect the local player.
92
+ - A negative knockback magnitude creates a pull/gravity well effect.
93
+ - Multiple forces from different entities stack independently.
@@ -153,12 +153,16 @@ changeRealm({ realm: 'other-realm.dcl.eth', message: 'Join this realm?' })
153
153
 
154
154
  ## Timers
155
155
 
156
- **setTimeout / setInterval** are supported via the QuickJS runtime polyfill:
156
+ **Use the `timers` API from `@dcl/sdk/ecs`**, not the global `setTimeout`/`setInterval`. The globals are not reliable in the QuickJS runtime.
157
157
 
158
158
  ```typescript
159
- setTimeout(() => console.log('delayed'), 2000)
160
- const id = setInterval(() => console.log('tick'), 1000)
161
- clearInterval(id)
159
+ import { timers } from '@dcl/sdk/ecs'
160
+
161
+ const t = timers.setTimeout(() => console.log('delayed'), 2000)
162
+ timers.clearTimeout(t)
163
+
164
+ const id = timers.setInterval(() => console.log('tick'), 1000)
165
+ timers.clearInterval(id)
162
166
  ```
163
167
 
164
168
  **System-based timers** (recommended for game logic — synchronized with the frame loop):
@@ -251,7 +255,7 @@ npx @dcl/sdk-commands test
251
255
 
252
256
  - Always wrap async code in `executeTask()` — bare promises will be silently dropped
253
257
  - Use `signedFetch` (not plain `fetch`) when your backend needs to verify the player's identity
254
- - Prefer system-based timers over `setTimeout`/`setInterval` for game logic — they stay in sync with the frame loop
258
+ - Prefer system-based timers over `timers.setTimeout`/`timers.setInterval` for game logic — they stay in sync with the frame loop. Use `timers.*` for one-shot scheduled actions (auto-close door, delayed sound, etc.).
255
259
  - Check `realm.realmInfo?.isPreview` to detect preview mode and enable debug features
256
260
  - Use `readFile()` for data files (JSON configs, level data) deployed alongside the scene
257
261
  - `removeEntityWithChildren()` is essential when cleaning up complex entity hierarchies
@@ -106,6 +106,7 @@ Keep it to 1-2 screenshots per task. Each screenshot consumes significant tokens
106
106
  | Missing textures (pink/magenta) | Texture file not found or wrong path | Verify file exists in project and path matches code |
107
107
  | Objects appear wrong scale | Scale values off or model exported at wrong scale | Check Transform.scale values; 1,1,1 is original model size |
108
108
  | Lighting looks flat | No LightSource components in scene | Add point or spot lights — see **lighting-environment** skill |
109
+ | Custom lights look too dim or invisible | Intensity value too low | Point/spot lights typically need `intensity: 8000–32000` candela. Values below ~1000 are usually invisible |
109
110
 
110
111
  ## What to Look For in Screenshots
111
112