@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
@@ -17,7 +17,7 @@ Decentraland is a **continuous, shared 3D world**. Design around these constrain
17
17
 
18
18
  ## 2. Scene Limitation Formulas
19
19
 
20
- All limits scale with parcel count `n`. Know these formulas and design within them.
20
+ All limits scale with parcel count `n`. **Except for hard MB file-size limits on deploy, all other limits CAN be exceeded** — scenes won't crash, but performance degrades and the scene may be unusable on low-end devices. Treat the table as guidelines, not enforcement.
21
21
 
22
22
  | Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
23
23
  |---|---|---|---|---|---|---|
@@ -35,7 +35,6 @@ All limits scale with parcel count `n`. Know these formulas and design within th
35
35
 
36
36
  - **Dimensions must be power-of-two**: 256, 512, 1024, 2048
37
37
  - **Recommended sizes**: 1024x1024 for scene objects, 512x512 for wearables
38
- - **Avoid textures over 2048x2048** — they consume excessive memory and often exceed limits
39
38
  - **Use texture atlases** to combine multiple small textures into one, reducing draw calls and material count
40
39
  - Prefer compressed formats (WebP) over raw PNG where possible
41
40
  - Share texture references across materials — do not duplicate texture files
@@ -5,22 +5,47 @@ description: Dynamic lighting and environment in Decentraland scenes. LightSourc
5
5
 
6
6
  # Lighting and Environment in Decentraland
7
7
 
8
- ## Point Lights
8
+ ## Authoring split
9
9
 
10
- Emit light in all directions from a position:
10
+ `LightSource` is supported in `main-entities.ts` — declare the lamp's fixture model AND its light in the same entry. The user can drag the fixture in the editor and the light follows.
11
11
 
12
12
  ```typescript
13
- import { engine, Transform, LightSource } from '@dcl/sdk/ecs'
14
- import { Vector3, Color3 } from '@dcl/sdk/math'
13
+ // main-entities.ts
14
+ import type { Scene } from '@dcl/sdk/scene-types'
15
+
16
+ export const scene = {
17
+ ceiling_lamp: {
18
+ components: {
19
+ Transform: { position: { x: 8, y: 3, z: 8 } },
20
+ GltfContainer: { src: 'models/lamp.glb' },
21
+ LightSource: {
22
+ type: { $case: 'point', point: {} },
23
+ color: { r: 1, g: 1, b: 1 },
24
+ intensity: 16000
25
+ }
26
+ }
27
+ }
28
+ } satisfies Scene
29
+ ```
15
30
 
16
- const light = engine.addEntity()
17
- Transform.create(light, { position: Vector3.create(8, 3, 8) })
31
+ For purely decorative lights with no mesh, declare a Transform-only entity and skip `GltfContainer`. Toggling lights at runtime still happens in `src/index.ts` via `LightSource.getMutable(getEntityOrNullByName('ceiling_lamp'))`.
18
32
 
19
- LightSource.create(light, {
20
- type: LightSource.Type.Point({}),
21
- color: Color3.White(),
22
- intensity: 300 // candela
23
- })
33
+ ## Point Lights
34
+
35
+ Emit light in all directions from a position:
36
+
37
+ ```typescript
38
+ // main-entities.ts entry
39
+ ambient_point: {
40
+ components: {
41
+ Transform: { position: { x: 8, y: 3, z: 8 } },
42
+ LightSource: {
43
+ type: { $case: 'point', point: {} },
44
+ color: { r: 1, g: 1, b: 1 },
45
+ intensity: 16000 // candela — point lights typically need 8000–32000 to be visible
46
+ }
47
+ }
48
+ }
24
49
  ```
25
50
 
26
51
  ### Colored Point Light
@@ -29,29 +54,31 @@ LightSource.create(light, {
29
54
  LightSource.create(light, {
30
55
  type: LightSource.Type.Point({}),
31
56
  color: Color3.create(1, 0.5, 0), // Warm orange
32
- intensity: 200,
57
+ intensity: 12000,
33
58
  range: 15 // Maximum distance in meters
34
59
  })
35
60
  ```
36
61
 
37
62
  ## Spot Lights
38
63
 
39
- Emit a cone of light in a direction:
64
+ Emit a cone of light in a direction. The cone's orientation comes from the entity's Transform rotation.
40
65
 
41
66
  ```typescript
42
- import { Quaternion } from '@dcl/sdk/math'
43
-
44
- const spotlight = engine.addEntity()
45
- Transform.create(spotlight, {
46
- position: Vector3.create(8, 4, 8),
47
- rotation: Quaternion.fromEulerDegrees(-90, 0, 0) // Point downward
48
- })
49
-
50
- LightSource.create(spotlight, {
51
- type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
52
- color: Color3.White(),
53
- intensity: 800
54
- })
67
+ // main-entities.ts
68
+ stage_spot: {
69
+ components: {
70
+ Transform: {
71
+ position: { x: 8, y: 4, z: 8 },
72
+ rotation: { x: -0.7071, y: 0, z: 0, w: 0.7071 } // pitch -90° (down)
73
+ },
74
+ GltfContainer: { src: 'models/spotlight.glb' },
75
+ LightSource: {
76
+ type: { $case: 'spot', spot: { innerAngle: 25, outerAngle: 45 } },
77
+ color: { r: 1, g: 1, b: 1 },
78
+ intensity: 16000
79
+ }
80
+ }
81
+ }
55
82
  ```
56
83
 
57
84
  - `innerAngle` — full-brightness cone angle (degrees)
@@ -66,10 +93,12 @@ Enable shadows on point or spot lights:
66
93
  LightSource.create(spotlight, {
67
94
  type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
68
95
  shadow: true,
69
- intensity: 800
96
+ intensity: 16000
70
97
  })
71
98
  ```
72
99
 
100
+ > Shadows are only available on **spot lights**, not point lights.
101
+
73
102
  ### Shadow Mask Textures (Gobos)
74
103
 
75
104
  Project a pattern through the light:
@@ -91,10 +120,11 @@ lightData.active = !lightData.active
91
120
 
92
121
  ## Light Limits
93
122
 
94
- - Maximum **one active light per parcel** (16m x 16m)
95
- - The renderer auto-culls lights based on quality settings and proximity
96
- - Up to ~3 shadowed lights visible at once
97
- - Intensity is in candela visible distance grows roughly with `sqrt(intensity)`
123
+ - Maximum **one active light per parcel** (16m x 16m) — multi-parcel scenes can group lights close together when needed.
124
+ - The renderer auto-culls lights based on quality settings and proximity. Quality range allows **4–10 lights visible simultaneously**.
125
+ - Shadows are only available on spot lights; up to ~3 shadowed lights visible at once.
126
+ - Intensity is in candela. Practical visible range: point lights ~8000–32000, spot lights ~10000–24000. Values below ~1000 are usually invisible.
127
+ - Emissive materials **don't illuminate surrounding entities** — they only have a glow effect on themselves. Combine an emissive material with a `LightSource` for both.
98
128
 
99
129
  ## SkyboxTime (Day/Night Cycle)
100
130
 
@@ -171,38 +201,55 @@ executeTask(async () => {
171
201
 
172
202
  ## Emissive Materials (Glow Effects)
173
203
 
174
- For a visual glow without casting light on surroundings:
204
+ `Material` is supported in `main-entities.ts`, so a glow that doesn't need to cast light on surroundings can be declared inline:
175
205
 
176
206
  ```typescript
177
- import { engine, Material } from '@dcl/sdk/ecs'
178
- import { Color4, Color3 } from '@dcl/sdk/math'
179
-
180
- // Self-illuminated material (emissiveColor uses Color3, not Color4)
181
- Material.setPbrMaterial(entity, {
182
- albedoColor: Color4.create(0, 0, 0, 1),
183
- emissiveColor: Color3.create(0, 1, 0), // Green glow
184
- emissiveIntensity: 2.0
185
- })
207
+ // main-entities.ts
208
+ glowing_orb: {
209
+ components: {
210
+ Transform: { position: { x: 8, y: 1, z: 8 } },
211
+ MeshRenderer: { mesh: { $case: 'sphere', sphere: {} } },
212
+ Material: {
213
+ material: {
214
+ $case: 'pbr',
215
+ pbr: {
216
+ albedoColor: { r: 0, g: 0, b: 0, a: 1 },
217
+ emissiveColor: { r: 0, g: 1, b: 0 },
218
+ emissiveIntensity: 2.0
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
186
224
  ```
187
225
 
188
226
  ### Combining Emissive + LightSource
189
227
 
190
- For an object that both glows visually and casts light:
228
+ A single entity can carry both emissive glow on the material AND light emission.
191
229
 
192
230
  ```typescript
193
- // Visual glow on the mesh
194
- Material.setPbrMaterial(bulb, {
195
- emissiveColor: Color3.create(1, 0.9, 0.7),
196
- emissiveIntensity: 1.5
197
- })
198
-
199
- // Actual light emission
200
- LightSource.create(bulb, {
201
- type: LightSource.Type.Point({}),
202
- color: Color3.create(1, 0.9, 0.7),
203
- intensity: 200,
204
- range: 10
205
- })
231
+ // main-entities.ts
232
+ bulb: {
233
+ components: {
234
+ Transform: { position: { x: 8, y: 3, z: 8 } },
235
+ GltfContainer: { src: 'models/bulb.glb' },
236
+ Material: {
237
+ material: {
238
+ $case: 'pbr',
239
+ pbr: {
240
+ emissiveColor: { r: 1, g: 0.9, b: 0.7 },
241
+ emissiveIntensity: 1.5
242
+ }
243
+ }
244
+ },
245
+ LightSource: {
246
+ type: { $case: 'point', point: {} },
247
+ color: { r: 1, g: 0.9, b: 0.7 },
248
+ intensity: 12000,
249
+ range: 10
250
+ }
251
+ }
252
+ }
206
253
  ```
207
254
 
208
255
  ### Shadow Types
@@ -220,7 +267,7 @@ LightSource.create(spotEntity, {
220
267
  shadow: PBLightSource_ShadowType.ST_SOFT
221
268
  }),
222
269
  shadow: true,
223
- intensity: 800
270
+ intensity: 16000
224
271
  })
225
272
  ```
226
273
 
@@ -5,7 +5,36 @@ description: Synchronize state between players in Decentraland using CRDT networ
5
5
 
6
6
  # Multiplayer Synchronization in Decentraland
7
7
 
8
- Decentraland scenes are inherently multiplayer. All players in the same scene share the same space. SDK7 uses CRDT-based synchronization.
8
+ ## Authoring split
9
+
10
+ `syncEntity()` adds a `NetworkEntity` component at runtime — and `NetworkEntity` is **not** in `main-entities.ts`'s supported component list. The pattern is:
11
+
12
+ - **Predefined synced entities** (a door, a switch, a scoreboard — known at author time): declare them in `main-entities.ts` with their visual components. Look them up in `src/index.ts` and call `syncEntity(entity, [...componentIds], SyncIds.NAME)` there.
13
+ - **Dynamically-spawned synced entities** (projectiles, drops, runtime-created game objects): create with `engine.addEntity()` in `src/index.ts` and `syncEntity` immediately.
14
+
15
+ ```typescript
16
+ // main-entities.ts
17
+ door: {
18
+ components: {
19
+ Transform: { position: { x: 8, y: 1, z: 8 } },
20
+ MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ```typescript
26
+ // src/index.ts
27
+ import { engine, Transform, syncEntity } from '@dcl/sdk/network'
28
+
29
+ export function main() {
30
+ const door = engine.getEntityOrNullByName('door')
31
+ if (door) syncEntity(door, [Transform.componentId], SyncIds.DOOR)
32
+ }
33
+ ```
34
+
35
+ Decentraland scenes are inherently multiplayer — every player in the same scene shares the same space. **By default, however, players interact with the environment independently — changes one player makes are NOT shared with others.** Opt in to sharing by wrapping mutable state in `syncEntity`, broadcasting events through `MessageBus`, or persisting through your own backend.
36
+
37
+ `syncEntity` state persists only as long as **at least one player remains in the scene**. The state resets as soon as the scene is empty. For durable state across scene resets, persist to your own server.
9
38
 
10
39
  > **Runtime constraint:** Decentraland runs in a QuickJS sandbox. No Node.js APIs (`fs`, `http`, `path`, `process`). Use `fetch()` and `WebSocket` for network communication. See the **scene-runtime** skill for async patterns.
11
40
 
@@ -24,7 +53,7 @@ Choose the right networking approach based on what you need:
24
53
  **Decision flow:**
25
54
  1. Does every player need to see the same state, including late joiners? --> `syncEntity`
26
55
  2. Is it a fire-and-forget event only for players currently in the scene? --> `MessageBus`
27
- 3. Do you need to talk to an external server? --> `fetch` or `signedFetch`
56
+ 3. Do you need state persisted after all players leave, or server-side validation? --> external server via `fetch` or `signedFetch`
28
57
  4. Do you need continuous real-time server communication? --> `WebSocket`
29
58
  5. Combine approaches freely: use `syncEntity` for world state, `MessageBus` for effects, and `fetch` for persistence.
30
59
 
@@ -7,25 +7,28 @@ description: NFT display and blockchain interaction in Decentraland. NftShape (f
7
7
 
8
8
  ## Display NFT Artwork
9
9
 
10
- Show an NFT from Ethereum in a decorative frame:
10
+ `NftShape` is supported in `main-entities.ts` — declare the framed NFT directly. The user can drag it around in the visual editor.
11
11
 
12
12
  ```typescript
13
- import { engine, Transform, NftShape, NftFrameType } from '@dcl/sdk/ecs'
14
- import { Vector3, Quaternion, Color4 } from '@dcl/sdk/math'
15
-
16
- const nftFrame = engine.addEntity()
17
- Transform.create(nftFrame, {
18
- position: Vector3.create(8, 2, 8),
19
- rotation: Quaternion.fromEulerDegrees(0, 0, 0)
20
- })
21
-
22
- NftShape.create(nftFrame, {
23
- urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536',
24
- color: Color4.White(),
25
- style: NftFrameType.NFT_CLASSIC
26
- })
13
+ // main-entities.ts
14
+ import type { Scene } from '@dcl/sdk/scene-types'
15
+
16
+ export const scene = {
17
+ hero_nft: {
18
+ components: {
19
+ Transform: { position: { x: 8, y: 2, z: 8 } },
20
+ NftShape: {
21
+ urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536',
22
+ color: { r: 1, g: 1, b: 1, a: 1 },
23
+ style: 0 // NftFrameType.NFT_CLASSIC — enum values are integers in the literal
24
+ }
25
+ }
26
+ }
27
+ } satisfies Scene
27
28
  ```
28
29
 
30
+ The `style` field is a numeric enum — the literal cannot reference `NftFrameType.NFT_CLASSIC` directly because the AST walker only accepts JSON-compatible expressions. Use the integer value (table below) and leave a comment.
31
+
29
32
  ### NFT URN Format
30
33
 
31
34
  ```
@@ -37,31 +40,33 @@ urn:decentraland:ethereum:erc721:<contractAddress>:<tokenId>
37
40
 
38
41
  ### Available Frame Styles
39
42
 
40
- ```typescript
41
- NftFrameType.NFT_CLASSIC // Simple classic frame
42
- NftFrameType.NFT_BAROQUE_ORNAMENT // Ornate baroque
43
- NftFrameType.NFT_DIAMOND_ORNAMENT // Diamond pattern
44
- NftFrameType.NFT_MINIMAL_WIDE // Minimal wide border
45
- NftFrameType.NFT_MINIMAL_GREY // Minimal grey border
46
- NftFrameType.NFT_BLOCKY // Pixelated/blocky
47
- NftFrameType.NFT_GOLD_EDGES // Gold edge trim
48
- NftFrameType.NFT_GOLD_CARVED // Carved gold
49
- NftFrameType.NFT_GOLD_WIDE // Wide gold border
50
- NftFrameType.NFT_GOLD_ROUNDED // Rounded gold
51
- NftFrameType.NFT_METAL_MEDIUM // Medium metal
52
- NftFrameType.NFT_METAL_WIDE // Wide metal
53
- NftFrameType.NFT_METAL_SLIM // Slim metal
54
- NftFrameType.NFT_METAL_ROUNDED // Rounded metal
55
- NftFrameType.NFT_PINS // Pinned to wall
56
- NftFrameType.NFT_MINIMAL_BLACK // Minimal black
57
- NftFrameType.NFT_MINIMAL_WHITE // Minimal white
58
- NftFrameType.NFT_TAPE // Taped to wall
59
- NftFrameType.NFT_WOOD_SLIM // Slim wood
60
- NftFrameType.NFT_WOOD_WIDE // Wide wood
61
- NftFrameType.NFT_WOOD_TWIGS // Twig/branch wood
62
- NftFrameType.NFT_CANVAS // Canvas style
63
- NftFrameType.NFT_NONE // No frame
64
- ```
43
+ | value | enum name | description |
44
+ |---|---|---|
45
+ | 0 | NFT_CLASSIC | Simple classic frame |
46
+ | 1 | NFT_BAROQUE_ORNAMENT | Ornate baroque |
47
+ | 2 | NFT_DIAMOND_ORNAMENT | Diamond pattern |
48
+ | 3 | NFT_MINIMAL_WIDE | Minimal wide border |
49
+ | 4 | NFT_MINIMAL_GREY | Minimal grey border |
50
+ | 5 | NFT_BLOCKY | Pixelated/blocky |
51
+ | 6 | NFT_GOLD_EDGES | Gold edge trim |
52
+ | 7 | NFT_GOLD_CARVED | Carved gold |
53
+ | 8 | NFT_GOLD_WIDE | Wide gold border |
54
+ | 9 | NFT_GOLD_ROUNDED | Rounded gold |
55
+ | 10 | NFT_METAL_MEDIUM | Medium metal |
56
+ | 11 | NFT_METAL_WIDE | Wide metal |
57
+ | 12 | NFT_METAL_SLIM | Slim metal |
58
+ | 13 | NFT_METAL_ROUNDED | Rounded metal |
59
+ | 14 | NFT_PINS | Pinned to wall |
60
+ | 15 | NFT_MINIMAL_BLACK | Minimal black |
61
+ | 16 | NFT_MINIMAL_WHITE | Minimal white |
62
+ | 17 | NFT_TAPE | Taped to wall |
63
+ | 18 | NFT_WOOD_SLIM | Slim wood |
64
+ | 19 | NFT_WOOD_WIDE | Wide wood |
65
+ | 20 | NFT_WOOD_TWIGS | Twig/branch wood |
66
+ | 21 | NFT_CANVAS | Canvas style |
67
+ | 22 | NFT_NONE | No frame |
68
+
69
+ In `src/index.ts` where enum identifiers are allowed, use `NftFrameType.NFT_CLASSIC` etc. directly.
65
70
 
66
71
  ## Check Player Wallet
67
72
 
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: npcs
3
+ description: Create NPCs (non-player characters) in Decentraland scenes. Two approaches: the NPC Toolkit library (dcl-npc-toolkit) for GLB-based NPCs with built-in dialogue, movement, and state machines; and AvatarShape for avatar-look NPCs dressed in wearables. Use when the user wants to add an NPC, character, shopkeeper, quest giver, guard, or any non-player entity with behavior or dialogue. For live player data (position, profile, wearables) see player-avatar instead.
4
+ ---
5
+
6
+ # NPCs in Decentraland
7
+
8
+ Two approaches — choose based on what the NPC needs to do:
9
+
10
+ | Approach | Use when |
11
+ |---|---|
12
+ | **NPC Toolkit** (`dcl-npc-toolkit`) | GLB model, needs dialogue, walking, state machine behavior |
13
+ | **AvatarShape** | Needs to look like a Decentraland avatar (wearables, expressions) |
14
+
15
+ ## Authoring split
16
+
17
+ - **AvatarShape NPCs** are static and fully declarable in `main-entities.ts` (`AvatarShape` is in the supported component list). Click handlers / proximity systems live in `src/index.ts`.
18
+ - **NPC Toolkit NPCs** are created via `createNPC(...)` from the toolkit library, which is a runtime API — call it from `src/index.ts`. The placement entity itself (a marker for the spawn position) can still live in `main-entities.ts` if you want it editable in the gizmo.
19
+
20
+ ---
21
+
22
+ ## Approach 1 — NPC Toolkit (GLB-based)
23
+
24
+ The toolkit handles dialogue UI, movement along paths, animations, and interaction out of the box.
25
+
26
+ **Install:**
27
+ ```bash
28
+ npm i dcl-npc-toolkit
29
+ ```
30
+
31
+ **Basic usage:**
32
+ ```typescript
33
+ // src/index.ts
34
+ import { createNPC, Dialog } from 'dcl-npc-toolkit'
35
+ import { Vector3, Quaternion } from '@dcl/sdk/math'
36
+
37
+ export function main() {
38
+ const npcEntity = createNPC(
39
+ { position: Vector3.create(8, 0, 8), rotation: Quaternion.fromEulerDegrees(0, 180, 0) },
40
+ 'models/guard.glb',
41
+ (entity) => {
42
+ // called when player clicks the NPC
43
+ startDialogue(entity)
44
+ },
45
+ {
46
+ idleAnim: 'Idle',
47
+ walkingAnim: 'Walk',
48
+ hoverText: 'Talk',
49
+ onlyExternalTrigger: false
50
+ }
51
+ )
52
+ }
53
+ ```
54
+
55
+ ### Gotchas (NPC Toolkit)
56
+
57
+ - **Button labels are visually truncated.** Dialog button labels render with `textWrap: 'nowrap'` in a fixed-width slot (~217px at default font 16). Anything past ~15 characters is silently clipped — no ellipsis. Use short labels like `"Yes"`, `"No thanks"`, `"Tell me more"`, `"Decline"`. To fit longer text, drop `fontSize` (e.g. 12) or set `size` on the button.
58
+ - Opening dialogs on an entity not created via `createNPC` requires `addDialog(entity)` and a minimal `npcDataComponent.set(entity, ...)` — see the toolkit reference for the full setup.
59
+ - Speech bubbles need `createDialogBubble(entity)` before `talkBubble`. Bubbles do not render question buttons; questions are HUD-only.
60
+
61
+ ---
62
+
63
+ ## Approach 2 — AvatarShape (Decentraland avatar look)
64
+
65
+ Create an NPC that looks like a Decentraland player avatar, dressed in any wearables. **Supported in `main-entities.ts`** — declare the NPC declaratively:
66
+
67
+ ```typescript
68
+ // main-entities.ts
69
+ import type { Scene } from '@dcl/sdk/scene-types'
70
+
71
+ export const scene = {
72
+ guard: {
73
+ components: {
74
+ Transform: { position: { x: 8, y: 0, z: 8 } },
75
+ AvatarShape: {
76
+ id: 'npc-1', // unique (required)
77
+ name: 'Guard', // shown above head
78
+ bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
79
+ wearables: [
80
+ 'urn:decentraland:off-chain:base-avatars:eyebrows_00',
81
+ 'urn:decentraland:off-chain:base-avatars:mouth_00',
82
+ 'urn:decentraland:off-chain:base-avatars:eyes_00',
83
+ 'urn:decentraland:off-chain:base-avatars:blue_tshirt',
84
+ 'urn:decentraland:off-chain:base-avatars:brown_pants',
85
+ 'urn:decentraland:off-chain:base-avatars:classic_shoes',
86
+ 'urn:decentraland:off-chain:base-avatars:short_hair'
87
+ ],
88
+ emotes: [],
89
+ hairColor: { r: 0.92, g: 0.76, b: 0.62 },
90
+ skinColor: { r: 0.94, g: 0.85, b: 0.6 }
91
+ }
92
+ }
93
+ }
94
+ } satisfies Scene
95
+ ```
96
+
97
+ **Notes:**
98
+ - Always include eyebrows, mouth, and eyes wearables — the avatar won't render face features without them.
99
+ - Moving the `Transform` position causes the NPC to walk/run to the destination (it does **not** teleport).
100
+ - Use `expressionTriggerTimestamp` as a Lamport timestamp to replay the same emote: first play = 0, second play = 1, etc.
101
+
102
+ ### Playing expressions on an AvatarShape NPC
103
+
104
+ ```typescript
105
+ // src/index.ts
106
+ const npc = engine.getEntityOrNullByName('guard')
107
+ if (npc) {
108
+ AvatarShape.getMutable(npc).expressionTriggerId = 'wave'
109
+ AvatarShape.getMutable(npc).expressionTriggerTimestamp = 1
110
+ }
111
+ ```
112
+
113
+ ### Mannequin mode (show wearables without a body)
114
+
115
+ Useful for storefronts and wearable displays:
116
+
117
+ ```typescript
118
+ // main-entities.ts
119
+ mannequin: {
120
+ components: {
121
+ Transform: { position: { x: 4, y: 0, z: 4 } },
122
+ AvatarShape: {
123
+ id: 'mannequin-1',
124
+ name: 'Display',
125
+ bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
126
+ wearables: ['urn:decentraland:matic:collections-v2:0x...:0'],
127
+ emotes: [],
128
+ show_only_wearables: true
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ For the full `AvatarShape` field reference and common base wearable URNs, see the `player-avatar` skill's references.
135
+
136
+ ---
137
+
138
+ ## Adding interactivity to AvatarShape NPCs
139
+
140
+ AvatarShape entities are **not clickable by default** — they have no collider, so pointer events won't register on them directly. Use one of:
141
+
142
+ ### Option A — Add a MeshCollider for click interaction
143
+
144
+ Declare the collider in `main-entities.ts` alongside the AvatarShape:
145
+
146
+ ```typescript
147
+ // main-entities.ts (added to the guard entry above)
148
+ MeshCollider: { mesh: { $case: 'cylinder', cylinder: {} } }
149
+ ```
150
+
151
+ ```typescript
152
+ // src/index.ts
153
+ import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
154
+
155
+ export function main() {
156
+ const npc = engine.getEntityOrNullByName('guard')
157
+ if (npc) pointerEventsSystem.onPointerDown(
158
+ { entity: npc, opts: { button: InputAction.IA_POINTER, hoverText: 'Talk' } },
159
+ () => { console.log('Player clicked NPC') }
160
+ )
161
+ }
162
+ ```
163
+
164
+ ### Option B — Proximity-based interaction
165
+
166
+ Trigger the interaction when the player walks near the NPC (no collider required):
167
+
168
+ ```typescript
169
+ import { engine, pointerEventsSystem } from '@dcl/sdk/ecs'
170
+
171
+ export function main() {
172
+ const npc = engine.getEntityOrNullByName('guard')
173
+ if (npc) pointerEventsSystem.onProximityEnter(
174
+ { entity: npc, opts: { maxPlayerDistance: 4 } },
175
+ () => { /* start dialogue or other interaction */ }
176
+ )
177
+ }
178
+ ```
179
+
180
+ See the `add-interactivity` skill for the full Proximity Events API.
@@ -5,9 +5,15 @@ description: Optimize Decentraland scene performance. Scene limit formulas (tria
5
5
 
6
6
  # Optimizing Decentraland Scenes
7
7
 
8
+ ## Authoring split
9
+
10
+ The patterns in this skill — object pools, LOD, asset preloading, system throttling — are all **runtime mechanics** and live in `src/index.ts`. The static layout (chairs, walls, lamps, the props that get LOD'd) should still be declared in `main-entities.ts`; the optimization code reads those entities by name (`engine.getEntityOrNullByName`) or by component query (`engine.getEntitiesWith(Transform)`).
11
+
12
+ The parenting optimization (a static container with many children) is best expressed in `main-entities.ts` using `Transform.parent: 'parent_name'` — that way the editor can move the entire group as a unit.
13
+
8
14
  ## Scene Limits (Per Parcel Count)
9
15
 
10
- All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
16
+ All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically. **Except for hard MB size limits on deploy, all other limits CAN be exceeded** — scenes won't crash, but performance degrades and the scene may become unusable on lower-end devices. Treat the numbers as guidelines, not enforcement.
11
17
 
12
18
  | Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
13
19
  |---|---|---|---|---|---|---|
@@ -100,7 +106,6 @@ MeshRenderer.setPlane(entity) // Very cheap
100
106
 
101
107
  - **Dimensions must be power-of-two**: 256, 512, 1024, 2048
102
108
  - **Recommended sizes**: 512x512 for most objects, 1024x1024 max for hero pieces
103
- - **Avoid textures over 2048x2048** — they consume excessive memory and often exceed limits
104
109
  - Use `.png` for UI/sprites with transparency
105
110
  - Use `.jpg` for photos and textures without transparency
106
111
  - Prefer compressed formats (WebP) over raw PNG where possible