@dcl-regenesislabs/opendcl 0.2.1-26502482653.commit-5089b10 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +3 -5
  2. package/context/sdk7-cheat-sheet.md +0 -4
  3. package/dist/index.js +12 -0
  4. package/dist/index.js.map +1 -1
  5. package/extensions/dcl-init.ts +6 -58
  6. package/extensions/dcl-setup-ollama.ts +312 -0
  7. package/package.json +4 -3
  8. package/prompts/system.md +41 -71
  9. package/skills/add-3d-models/SKILL.md +70 -120
  10. package/skills/add-interactivity/SKILL.md +2 -74
  11. package/skills/advanced-input/SKILL.md +1 -34
  12. package/skills/advanced-rendering/SKILL.md +9 -82
  13. package/skills/animations-tweens/SKILL.md +98 -203
  14. package/skills/audio-video/SKILL.md +83 -184
  15. package/skills/build-ui/SKILL.md +2 -25
  16. package/skills/camera-control/SKILL.md +7 -78
  17. package/skills/create-scene/SKILL.md +13 -56
  18. package/skills/deploy-scene/SKILL.md +0 -12
  19. package/skills/deploy-worlds/SKILL.md +0 -35
  20. package/skills/game-design/SKILL.md +2 -1
  21. package/skills/lighting-environment/SKILL.md +56 -103
  22. package/skills/multiplayer-sync/SKILL.md +2 -31
  23. package/skills/nft-blockchain/SKILL.md +40 -45
  24. package/skills/optimize-scene/SKILL.md +2 -7
  25. package/skills/player-avatar/SKILL.md +7 -133
  26. package/skills/scene-runtime/SKILL.md +5 -9
  27. package/skills/visual-feedback/SKILL.md +0 -1
  28. package/skills/audio-analysis/SKILL.md +0 -164
  29. package/skills/editor-gizmo/.gitignore +0 -11
  30. package/skills/editor-gizmo/SKILL.md +0 -222
  31. package/skills/editor-gizmo/src/__editor/camera.ts +0 -277
  32. package/skills/editor-gizmo/src/__editor/discovery.ts +0 -210
  33. package/skills/editor-gizmo/src/__editor/drag.ts +0 -265
  34. package/skills/editor-gizmo/src/__editor/gizmo.ts +0 -496
  35. package/skills/editor-gizmo/src/__editor/history.ts +0 -72
  36. package/skills/editor-gizmo/src/__editor/index.ts +0 -138
  37. package/skills/editor-gizmo/src/__editor/input.ts +0 -55
  38. package/skills/editor-gizmo/src/__editor/math-utils.ts +0 -114
  39. package/skills/editor-gizmo/src/__editor/persistence.ts +0 -113
  40. package/skills/editor-gizmo/src/__editor/selection.ts +0 -157
  41. package/skills/editor-gizmo/src/__editor/state.ts +0 -117
  42. package/skills/editor-gizmo/src/__editor/ui.tsx +0 -699
  43. package/skills/npcs/SKILL.md +0 -180
  44. package/skills/particle-system/SKILL.md +0 -222
  45. package/skills/player-physics/SKILL.md +0 -93
package/prompts/system.md CHANGED
@@ -25,58 +25,26 @@ You are **OpenDCL**, an AI coding assistant specialized in Decentraland SDK7 sce
25
25
  - 1 parcel: ~512 entities, ~10,000 triangles. Scales with parcel count.
26
26
  - All coordinates are in meters. Y is up. Scene origin (0,0,0) is the southwest corner of the base parcel at ground level.
27
27
 
28
- ### Authoring Model: Data in `main-entities.ts`, Behavior in `src/`
28
+ ### Key Patterns
29
29
 
30
- OpenDCL scenes split the source of truth in two:
31
-
32
- - **`main-entities.ts`** at the scene root — typed declarative entities keyed by Name, with their data components (Transform, GltfContainer, MeshRenderer, Material, AudioSource, etc.). Compiled to `main.crdt` at build time and preloaded by the engine before `main()` runs.
33
- - **`src/index.ts`** — behavior only. References entities by Name and attaches systems, pointer events, tweens.
34
-
35
- **Adding a declared entity (in `main-entities.ts`):**
36
- ```typescript
37
- import type { Scene } from '@dcl/sdk/scene-types'
38
-
39
- export const scene = {
40
- blue_cube: {
41
- components: {
42
- Transform: { position: { x: 8, y: 1, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
43
- MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
44
- Material: { material: { $case: 'pbr', pbr: { albedoColor: { r: 1, g: 0, b: 0, a: 1 } } } },
45
- },
46
- },
47
- } satisfies Scene
48
- ```
49
-
50
- **Referencing it in code:**
30
+ **Creating an entity with components:**
51
31
  ```typescript
52
- import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
53
- import type { scene } from '../main-entities'
32
+ import { engine, Transform, MeshRenderer, Material } from '@dcl/sdk/ecs'
33
+ import { Vector3, Color4 } from '@dcl/sdk/math'
54
34
 
55
- type EntityName = keyof typeof scene
56
-
57
- export function main() {
58
- const cube = engine.getEntityOrNullByName<EntityName>('blue_cube')
59
- if (cube === null) return
60
-
61
- pointerEventsSystem.onPointerDown(
62
- { entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } },
63
- () => { /* handle click */ },
64
- )
65
- }
35
+ const cube = engine.addEntity()
36
+ Transform.create(cube, { position: Vector3.create(8, 1, 8) })
37
+ MeshRenderer.setBox(cube)
38
+ Material.setPbrMaterial(cube, { albedoColor: Color4.Red() })
66
39
  ```
67
40
 
68
- **Rules:**
69
- - Every declarative entity goes in `main-entities.ts` with a unique Name. The `satisfies Scene` clause keeps literal keys typed for safe references.
70
- - Parents are referenced by Name (`parent: 'barrel_1'`); the build resolves them.
71
- - Pure-data components (Transform, GltfContainer, MeshRenderer, MeshCollider, Material, AudioSource, VideoPlayer, TextShape, Animator config, NftShape, Billboard, VisibilityComponent) all live in `main-entities.ts`.
72
- - Behavior, callbacks, systems, conditional logic stay in `src/`.
73
- - The `scene` literal must contain only JSON-compatible values — no function calls, no spreads, no comments inside the object.
74
-
75
- **Dynamic entities** spawned at runtime (effects, projectiles, runtime markers) still use `engine.addEntity()` and **don't get Names** — they're invisible to the editor and not persisted:
41
+ **Adding interactivity:**
76
42
  ```typescript
77
- const explosion = engine.addEntity()
78
- Transform.create(explosion, { position: Vector3.create(...) })
79
- GltfContainer.create(explosion, { src: 'models/Explosion.glb' })
43
+ import { pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
44
+
45
+ pointerEventsSystem.onPointerDown({ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } }, () => {
46
+ // Handle click
47
+ })
80
48
  ```
81
49
 
82
50
  **Systems (per-frame logic):**
@@ -105,11 +73,10 @@ export function setupUi() {
105
73
  ```
106
74
  scene-project/
107
75
  ├── scene.json # Scene metadata (parcels, title, main entry)
108
- ├── main-entities.ts # Declarative entities (data) — preloaded into the engine before main() runs
109
76
  ├── package.json # Dependencies (@dcl/sdk)
110
- ├── tsconfig.json # TypeScript config (must include main-entities.ts)
77
+ ├── tsconfig.json # TypeScript config
111
78
  └── src/
112
- ├── index.ts # Main entry point references entities by Name, attaches behavior
79
+ ├── index.ts # Main entry point (export function main)
113
80
  └── ui.tsx # UI components (optional)
114
81
  ```
115
82
 
@@ -127,25 +94,19 @@ scene-project/
127
94
  ## How to Help Users
128
95
 
129
96
  ### Empty Folder (No scene.json)
130
- **Do NOT ask the user what they want to build.** Instead, immediately run the `init` tool to scaffold the project — no questions, no menu of options, just init. This uses the official SDK scaffolding to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration, and installs dependencies. Never create these files manually. After init completes, ask the user what they'd like to do next. Offer small, concrete steps — don't propose building an entire scene at once.
97
+ 1. Ask the user what they want to build.
98
+ 2. **Use the `init` tool first** — this uses the official SDK scaffolding to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration, and installs dependencies. Never create these files manually.
99
+ 3. After init completes, customize `scene.json` (title, description, parcels) and add the first element to `src/index.ts`. Then offer next steps — don't build the entire scene at once.
131
100
 
132
101
  ### Existing Scene
133
- 1. Read scene.json, main-entities.ts (if it exists), and src/index.ts to understand the project.
102
+ 1. Read scene.json and src/index.ts to understand the project.
134
103
  2. Offer contextual help — adding features, fixing bugs, optimizing.
135
104
  3. Always preserve existing code when making edits.
136
- 4. When adding a new declarative entity (cube, model, lamp, etc.), edit `main-entities.ts` and reference the new Name from `src/index.ts` if it needs behavior. Don't reach for `engine.addEntity()` in code as a default.
137
105
 
138
106
  ### Best Practices
139
-
140
- **Always declare static / editable entities in `main-entities.ts`** — never via `engine.addEntity()` in `src/index.ts` for things the user might want to move, rotate, or see in the visual editor. Entities declared in `main-entities.ts` are preloaded by the engine before `main()` runs, are visible in the editor's hierarchy panel, and are draggable. Entities created at runtime via `engine.addEntity()` are invisible to the editor and lost when the user reloads.
141
-
142
- Use `engine.addEntity()` only for genuinely dynamic things (effects spawned at runtime, projectiles, throwaway markers). Those entities should NOT have a `Name` component — Naming is the marker for "this entity belongs in main-entities.ts."
143
-
144
- When the user asks to add a barrel, a tree, a prop, a model — that goes in `main-entities.ts` first, and code references it via `engine.getEntityOrNullByName<EntityName>(name)`. Don't fall back to the old `addEntity + Transform.create + GltfContainer.create` pattern in code unless the user explicitly asks for runtime spawning.
145
-
146
107
  - Always position objects within the scene boundaries (based on parcels).
147
- - For positions/rotations inside `main-entities.ts`, use plain object literals (`{ x: 8, y: 1, z: 8 }`), not `Vector3.create()` — the `scene` literal must stay JSON-compatible. In `src/index.ts` (behavior code), `Vector3.create()` and `Quaternion.fromEulerDegrees()` are fine.
148
- - For 3D models, declare `GltfContainer: { src: 'models/myModel.glb' }` inside the entity's `components` block in `main-entities.ts`.
108
+ - Use `Vector3.create()` and `Quaternion.fromEulerDegrees()` for transforms.
109
+ - For 3D models, use `GltfContainer.create(entity, { src: 'models/myModel.glb' })`.
149
110
  - `GltfContainer` only works with **local files** — never use external URLs for the `src` field. Always download models into the scene's `models/` directory first.
150
111
  - Place `.glb` files in a `models/` directory, textures in `images/`.
151
112
  - Don't start the preview server automatically after writing code. The user will type `/preview` when ready.
@@ -154,30 +115,39 @@ When the user asks to add a barrel, a tree, a prop, a model — that goes in `ma
154
115
  - Search with `grep -i "keyword" skills/add-3d-models/references/model-catalog.md`, fetch the preview thumbnail to confirm, then download with curl.
155
116
  - Download matching models with `curl -o models/filename.glb "URL"` before referencing them in code.
156
117
 
157
- ### Visual Feedback
118
+ ### Visual Iteration Workflow
119
+
120
+ When the preview server is running, **proactively use the `screenshot` tool after making scene changes**. Don't wait for the user to check — verify your own work:
121
+
122
+ 1. Write code or modify the scene.
123
+ 2. Use `screenshot` (with a `wait` of ~2000ms for hot-reload) to see the result.
124
+ 3. Describe what you see honestly — what's working, what's missing, what looks wrong.
125
+ 4. If something is off, fix it and screenshot again.
158
126
 
159
- When the preview server is running, use the `screenshot` tool **after completing code changes** to verify the result. Do NOT use screenshots to explore or navigate the scene.
127
+ This way the user gets a working scene without having to open a browser and report issues back to you.
160
128
 
161
- 1. Make all code changes first.
162
- 2. Take **one** screenshot (with `wait: 2000` for hot-reload) to verify.
163
- 3. Describe what you see honestly — what works, what's wrong.
164
- 4. If something is off, fix the code and take **one more** screenshot to confirm.
129
+ The screenshot tool supports actions before capture — move around (moveForward, moveLeft, etc.), look around (lookLeft, lookUp), click objects, press keys. Use these to explore from different angles or test interactivity.
165
130
 
166
- Keep it to **1-2 screenshots per task**. Each screenshot consumes significant tokens. Do not wander around taking multiple screenshots to "explore" — that wastes the user's budget.
131
+ The browser stays open between calls only the first screenshot takes ~15s (launch + enter scene). Subsequent ones are instant.
167
132
 
168
- The screenshot tool supports actions before capture (move, look, click, key press), but use these sparingly and only when needed to verify a specific thing (e.g., moving to see an object you just placed behind the spawn point).
133
+ If the user asks you to iterate autonomously (e.g., "keep going until it looks right"):
134
+ 1. Make code changes.
135
+ 2. Wait for hot reload (~2s), then take a screenshot.
136
+ 3. Analyze whether the result matches the goal.
137
+ 4. If not, make targeted fixes and screenshot again.
138
+ 5. Repeat (up to 5 iterations) until done.
169
139
 
170
140
  ## Tools & Commands
171
141
 
172
142
  You have these Decentraland-specific tools — **use them directly** when the user's request matches:
173
143
  - `init` — Scaffold a new scene (**always use this first** in an empty folder)
174
144
  - `preview` — Start the Bevy-web preview server
175
- - `screenshot` — Capture a screenshot of the running preview to verify code changes. Limit to 1-2 per task.
145
+ - `screenshot` — Capture a screenshot of the running preview. Supports movement and interaction actions before capture.
176
146
  - `deploy` — Deploy to Genesis City or a World (auto-detects from scene.json)
177
147
  - `tasks` — List or stop running background processes
178
148
 
179
149
  The user can also type these as `/init`, `/preview`, `/deploy`, `/tasks` slash commands directly.
180
- Additional user-only commands: `/review`, `/explain`, `/setup`
150
+ Additional user-only commands: `/review`, `/explain`, `/setup`, `/setup-ollama`
181
151
 
182
152
  ## Pacing
183
153
 
@@ -5,30 +5,27 @@ description: Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContai
5
5
 
6
6
  # Adding 3D Models to Decentraland Scenes
7
7
 
8
- ## Where models go: `main-entities.ts`
8
+ ## Loading a 3D Model
9
9
 
10
- A 3D model placed at author time is a static visible entity. **Declare it in `main-entities.ts`**, not via `engine.addEntity()` in `src/index.ts`. The build compiles `main-entities.ts` into `main.crdt`, the engine preloads it before `main()` runs, and the editor can drag/rotate the model interactively.
10
+ Use `GltfContainer` to load `.glb` or `.gltf` files:
11
11
 
12
12
  ```typescript
13
- // main-entities.ts
14
- import type { Scene } from '@dcl/sdk/scene-types'
15
-
16
- export const scene = {
17
- my_model: {
18
- components: {
19
- Transform: { position: { x: 8, y: 0, z: 8 } },
20
- GltfContainer: {
21
- src: 'models/myModel.glb',
22
- visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
23
- }
24
- }
25
- }
26
- } satisfies Scene
27
- ```
13
+ import { engine, Transform, GltfContainer, ColliderLayer } from '@dcl/sdk/ecs'
14
+ import { Vector3, Quaternion } from '@dcl/sdk/math'
28
15
 
29
- > **Always set `visibleMeshesCollisionMask`** on `GltfContainer`. Catalog models don't include separate collider meshes — using the visible mesh as the collider ensures the model is solid and clickable. Use the integer value (`ColliderLayer.CL_PHYSICS = 1`, `CL_POINTER = 2`, both = `3`) inside `main-entities.ts` since enums aren't allowed in the literal.
16
+ const model = engine.addEntity()
17
+ Transform.create(model, {
18
+ position: Vector3.create(8, 0, 8),
19
+ rotation: Quaternion.fromEulerDegrees(0, 0, 0),
20
+ scale: Vector3.create(1, 1, 1)
21
+ })
22
+ GltfContainer.create(model, {
23
+ src: 'models/myModel.glb',
24
+ visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER
25
+ })
26
+ ```
30
27
 
31
- **When to use `engine.addEntity()` in `src/index.ts` instead**: only when the model is spawned dynamically (procedurally placed in a loop, dropped on an event, gated by NFT ownership, etc.). For static props, always use `main-entities.ts`.
28
+ > **Always set `visibleMeshesCollisionMask`** when loading models. Catalog models don't include separate collider meshes using the visible mesh as the collider ensures the model is solid and clickable.
32
29
 
33
30
  ## File Organization
34
31
 
@@ -49,80 +46,52 @@ project/
49
46
  ## Colliders
50
47
 
51
48
  ### Using Model's Built-in Colliders
52
-
49
+ Models exported with collision meshes work automatically. Set the collision mask:
53
50
  ```typescript
54
- // main-entities.ts — declared inline with GltfContainer
55
- building: {
56
- components: {
57
- Transform: { position: { x: 8, y: 0, z: 8 } },
58
- GltfContainer: {
59
- src: 'models/building.glb',
60
- visibleMeshesCollisionMask: 3, // CL_PHYSICS | CL_POINTER
61
- invisibleMeshesCollisionMask: 1 // CL_PHYSICS
62
- }
63
- }
64
- }
51
+ GltfContainer.create(model, {
52
+ src: 'models/building.glb',
53
+ visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
54
+ invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS
55
+ })
65
56
  ```
66
57
 
67
58
  ### Adding Simple Colliders
68
-
69
- For basic shapes (no GLTF), add `MeshCollider`:
70
-
59
+ For basic shapes, add `MeshCollider`:
71
60
  ```typescript
72
- // main-entities.ts
73
- invisible_wall: {
74
- components: {
75
- Transform: { position: { x: 0, y: 1, z: 8 }, scale: { x: 0.1, y: 2, z: 16 } },
76
- MeshCollider: { mesh: { $case: 'box', box: { uvs: [] } } }
77
- }
78
- }
61
+ import { MeshCollider } from '@dcl/sdk/ecs'
62
+ MeshCollider.setBox(model) // Box collider
63
+ MeshCollider.setSphere(model) // Sphere collider
79
64
  ```
80
65
 
81
- Other shapes: `{ $case: 'sphere', sphere: {} }`, `{ $case: 'plane', plane: { uvs: [] } }`, `{ $case: 'cylinder', cylinder: {} }`.
82
-
83
- ## ⚠️ Important: Never Pass `undefined` in Transform Fields
84
-
85
- The SDK serializer crashes if any Transform field (`position`, `rotation`, `scale`) is present but `undefined`. **Omit the key entirely** instead — both in `main-entities.ts` literals and in any runtime helpers. In `main-entities.ts` this is natural (you just don't write the field).
86
-
87
66
  ## Common Model Operations
88
67
 
89
68
  ### Scaling
90
-
91
69
  ```typescript
92
- // main-entities.ts
93
- big_statue: {
94
- components: {
95
- Transform: { position: { x: 8, y: 0, z: 8 }, scale: { x: 2, y: 2, z: 2 } },
96
- GltfContainer: { src: 'models/statue.glb' }
97
- }
98
- }
70
+ Transform.create(model, {
71
+ position: Vector3.create(8, 0, 8),
72
+ scale: Vector3.create(2, 2, 2) // 2x size
73
+ })
99
74
  ```
100
75
 
101
- ### Rotation (Euler angles converted to a quaternion at author time)
102
-
103
- Quaternions are `{ x, y, z, w }`. For `Quaternion.fromEulerDegrees(0, 90, 0)` the equivalent literal is `{ x: 0, y: 0.7071, z: 0, w: 0.7071 }`. If you need exact-degree rotations and don't want to compute by hand, set `rotation` to identity in `main-entities.ts` and rotate at runtime in `src/index.ts` using `Transform.getMutable(entity).rotation = Quaternion.fromEulerDegrees(0, 90, 0)`.
104
-
105
- ### Parenting
106
-
107
- Reference the parent by **name** (a string key from the same `scene` object). The build resolves names to entity IDs in a second pass.
76
+ ### Rotation
77
+ ```typescript
78
+ Transform.create(model, {
79
+ position: Vector3.create(8, 0, 8),
80
+ rotation: Quaternion.fromEulerDegrees(0, 90, 0) // Rotate 90° on Y axis
81
+ })
82
+ ```
108
83
 
84
+ ### Parenting (Attach to Another Entity)
109
85
  ```typescript
110
- // main-entities.ts
111
- character: {
112
- components: {
113
- Transform: { position: { x: 8, y: 0, z: 8 } },
114
- GltfContainer: { src: 'models/character.glb' }
115
- }
116
- },
117
- hat: {
118
- components: {
119
- Transform: {
120
- position: { x: 0, y: 2, z: 0 }, // 2m above parent's origin
121
- parent: 'character'
122
- },
123
- GltfContainer: { src: 'models/hat.glb' }
124
- }
125
- }
86
+ const parent = engine.addEntity()
87
+ Transform.create(parent, { position: Vector3.create(8, 0, 8) })
88
+
89
+ const child = engine.addEntity()
90
+ Transform.create(child, {
91
+ position: Vector3.create(0, 2, 0), // 2m above parent
92
+ parent: parent
93
+ })
94
+ GltfContainer.create(child, { src: 'models/hat.glb' })
126
95
  ```
127
96
 
128
97
  ## Free 3D Models — OpenDCL Catalog (5,700+ models)
@@ -177,34 +146,19 @@ curl -o models/zombie-purple.glb "https://models.dclregenesislabs.xyz/blobs/bafy
177
146
  ```
178
147
 
179
148
  ```typescript
180
- // main-entities.ts declare the entity with all its initial state
181
- import type { Scene } from '@dcl/sdk/scene-types'
182
-
183
- export const scene = {
184
- zombie: {
185
- components: {
186
- Transform: { position: { x: 8, y: 0, z: 8 } },
187
- GltfContainer: { src: 'models/zombie-purple.glb' },
188
- Animator: {
189
- states: [
190
- { clip: 'ZombieWalk', playing: true, loop: true },
191
- { clip: 'ZombieAttack', playing: false, loop: false }
192
- ]
193
- }
194
- }
195
- }
196
- } satisfies Scene
197
- ```
198
-
199
- To switch animations at runtime (e.g., trigger attack on click), use `src/index.ts`:
149
+ // Use in code with animations
150
+ import { engine, Transform, GltfContainer, Animator } from '@dcl/sdk/ecs'
151
+ import { Vector3 } from '@dcl/sdk/math'
200
152
 
201
- ```typescript
202
- import { engine, Animator } from '@dcl/sdk/ecs'
203
-
204
- export function main() {
205
- const zombie = engine.getEntityOrNullByName('zombie')
206
- if (zombie) Animator.playSingleAnimation(zombie, 'ZombieAttack')
207
- }
153
+ const zombie = engine.addEntity()
154
+ Transform.create(zombie, { position: Vector3.create(8, 0, 8) })
155
+ GltfContainer.create(zombie, { src: 'models/zombie-purple.glb' })
156
+ Animator.create(zombie, {
157
+ states: [
158
+ { clip: 'ZombieWalk', playing: true, loop: true },
159
+ { clip: 'ZombieAttack', playing: false, loop: false }
160
+ ]
161
+ })
208
162
  ```
209
163
 
210
164
  > **Important**: `GltfContainer` only works with **local files**. Never use external URLs for the model `src` field. Always download models into `models/` first.
@@ -212,23 +166,19 @@ export function main() {
212
166
 
213
167
  ### Checking Model Load State
214
168
 
215
- Load-state polling is runtime behavior put it in `src/index.ts` and reference the entity by name:
169
+ Use `GltfContainerLoadingState` to check if a model has finished loading:
216
170
 
217
171
  ```typescript
218
- import { engine, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
219
-
220
- export function main() {
221
- engine.addSystem(() => {
222
- const model = engine.getEntityOrNullByName('zombie')
223
- if (!model) return
224
- const state = GltfContainerLoadingState.getOrNull(model)
225
- if (state?.currentState === LoadingState.FINISHED) {
226
- console.log('Model loaded successfully')
227
- } else if (state?.currentState === LoadingState.FINISHED_WITH_ERROR) {
228
- console.log('Model failed to load')
229
- }
230
- })
231
- }
172
+ import { GltfContainer, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
173
+
174
+ engine.addSystem(() => {
175
+ const state = GltfContainerLoadingState.getOrNull(modelEntity)
176
+ if (state && state.currentState === LoadingState.FINISHED) {
177
+ console.log('Model loaded successfully')
178
+ } else if (state && state.currentState === LoadingState.FINISHED_WITH_ERROR) {
179
+ console.log('Model failed to load')
180
+ }
181
+ })
232
182
  ```
233
183
 
234
184
  ## Troubleshooting
@@ -5,37 +5,6 @@ description: Add click handlers, hover effects, pointer events, trigger areas, r
5
5
 
6
6
  # Adding Interactivity to Decentraland Scenes
7
7
 
8
- ## Authoring split
9
-
10
- The clickable entity (cube, button, model) is static — declare it in `main-entities.ts` with its Transform / Mesh / Material. The clickability itself is **always** added at runtime in `src/index.ts` via `pointerEventsSystem.onPointerDown(...)` (or the related helpers). The helper writes the `PointerEvents` component AND registers the callback in a single call — do NOT also declare `PointerEvents` in `main-entities.ts`; the helper would just overwrite it and the duplication invites drift.
11
-
12
- ```typescript
13
- // main-entities.ts — entity only, no PointerEvents
14
- clickable_cube: {
15
- components: {
16
- Transform: { position: { x: 8, y: 1, z: 8 } },
17
- MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
18
- }
19
- }
20
- ```
21
-
22
- ```typescript
23
- // src/index.ts — register clickability via the helper system
24
- import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
25
-
26
- export function main() {
27
- const cube = engine.getEntityOrNullByName('clickable_cube')
28
- if (cube) {
29
- pointerEventsSystem.onPointerDown(
30
- { entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Open' } },
31
- () => { /* what happens on click */ }
32
- )
33
- }
34
- }
35
- ```
36
-
37
- `TriggerArea` and `Raycast` are also runtime — they live in `src/index.ts`. Code examples below that create entities inline with `engine.addEntity()` are for runtime/technical entities (raycast probes, trigger volumes generated from data); for static clickable props, declare the prop in `main-entities.ts` and attach handlers in `src/index.ts` as above.
38
-
39
8
  ## Decision Tree
40
9
 
41
10
  | Need | Approach | API |
@@ -120,10 +89,10 @@ pointerEventsSystem.removeOnPointerUp(cube)
120
89
  ```
121
90
 
122
91
  ### Important: Colliders Required
123
- Pointer events only work on entities with a **collider on the `CL_POINTER` layer**. Add one if your entity doesn't have a mesh:
92
+ Pointer events only work on entities with a **collider**. Add one if your entity doesn't have a mesh:
124
93
  ```typescript
125
94
  import { MeshCollider } from '@dcl/sdk/ecs'
126
- MeshCollider.setBox(entity) // Invisible box collider — defaults include CL_POINTER
95
+ MeshCollider.setBox(entity) // Invisible box collider
127
96
  ```
128
97
 
129
98
  For GLTF models, set the collision mask:
@@ -136,47 +105,6 @@ GltfContainer.create(entity, {
136
105
 
137
106
  ---
138
107
 
139
- ## Proximity Events (Pointer-Free Triggers)
140
-
141
- Like `pointerEventsSystem.onPointerDown`, but fires based on **player distance** to the entity instead of a click. Useful for "press E when near" interactions and signposts that highlight on approach. No collider required — the system polls the player position vs the entity transform.
142
-
143
- ```typescript
144
- import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
145
-
146
- const door = engine.getEntityOrNullByName('shop_door')
147
- if (door) {
148
- pointerEventsSystem.onProximityDown(
149
- {
150
- entity: door,
151
- opts: {
152
- button: InputAction.IA_PRIMARY,
153
- hoverText: 'Open shop',
154
- maxPlayerDistance: 3 // metres
155
- }
156
- },
157
- () => { /* run when the player presses the button within range */ }
158
- )
159
-
160
- pointerEventsSystem.onProximityEnter(
161
- { entity: door, opts: { maxPlayerDistance: 5 } },
162
- () => { /* fired once when the player enters the radius */ }
163
- )
164
-
165
- pointerEventsSystem.onProximityLeave(
166
- { entity: door, opts: { maxPlayerDistance: 5 } },
167
- () => { /* fired once when the player leaves the radius */ }
168
- )
169
- }
170
- ```
171
-
172
- - `maxPlayerDistance` is required and is measured from the **avatar root**, not the camera.
173
- - `priority` (number) — if multiple proximity events overlap, the higher value wins.
174
- - Remove with `pointerEventsSystem.removeOnProximityDown(entity)` etc.
175
-
176
- Prefer **proximity events** over `pointerEventsSystem.onPointerDown` when the entity has no visible collider or when the player shouldn't need to aim at it (signs, doors that just open when approached, etc.).
177
-
178
- ---
179
-
180
108
  ## Trigger Areas (Proximity Detection)
181
109
 
182
110
  Detect when the player enters, exits, or stays inside an area:
@@ -83,8 +83,6 @@ engine.addSystem(myInputSystem)
83
83
 
84
84
  ### Global Input Checks
85
85
 
86
- Fires regardless of whether the player's cursor was over an entity.
87
-
88
86
  ```typescript
89
87
  function globalInputSystem() {
90
88
  // Was the key just pressed this frame?
@@ -101,31 +99,6 @@ function globalInputSystem() {
101
99
  engine.addSystem(globalInputSystem)
102
100
  ```
103
101
 
104
- ### Tag-Based Input Batching
105
-
106
- If you have many similar entities that all respond to the same input (e.g., every barrel responds to E to break), tag them via the `Tag` component and iterate the tag query each frame:
107
-
108
- ```typescript
109
- import { engine, Tag, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
110
-
111
- // At setup: tag the entities (or declare Tag in main-entities.ts).
112
- for (const barrel of [b1, b2, b3]) {
113
- Tag.createOrReplace(barrel, { value: 'breakable' })
114
- }
115
-
116
- engine.addSystem(() => {
117
- for (const [entity] of engine.getEntitiesByTag('breakable')) {
118
- const cmd = inputSystem.getInputCommand(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN, entity)
119
- if (cmd) {
120
- // entity was the IA_PRIMARY target this frame
121
- engine.removeEntity(entity)
122
- }
123
- }
124
- })
125
- ```
126
-
127
- Cleaner than registering N individual `pointerEventsSystem.onPointerDown` handlers when the behavior is uniform.
128
-
129
102
  ## All InputAction Values
130
103
 
131
104
  | InputAction | Key/Button |
@@ -165,15 +138,11 @@ InputModifier.create(engine.PlayerEntity, {
165
138
  mode: InputModifier.Mode.Standard({ disableAll: true })
166
139
  })
167
140
 
168
- // Restrict specific movement (every flag is optional and defaults to false)
141
+ // Restrict specific movement
169
142
  InputModifier.createOrReplace(engine.PlayerEntity, {
170
143
  mode: InputModifier.Mode.Standard({
171
- disableWalk: false,
172
- disableJog: false,
173
144
  disableRun: true,
174
145
  disableJump: true,
175
- disableDoubleJump: true,
176
- disableGliding: true,
177
146
  disableEmote: true
178
147
  })
179
148
  })
@@ -182,8 +151,6 @@ InputModifier.createOrReplace(engine.PlayerEntity, {
182
151
  InputModifier.deleteFrom(engine.PlayerEntity)
183
152
  ```
184
153
 
185
- While restricted: gravity still applies, the camera still rotates, and pointer / proximity events still fire. All restrictions auto-lift when the player leaves the scene.
186
-
187
154
  **Important:** InputModifier only works in the DCL 2.0 desktop client. It has no effect in the web browser explorer.
188
155
 
189
156
  ### Cutscene Pattern