@dcl-regenesislabs/opendcl 0.2.1-26238928766.commit-28648d7 → 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 -186
  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 -137
  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 -697
  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
@@ -5,11 +5,6 @@ description: Add sound effects, music, audio streaming, and video players to Dec
5
5
 
6
6
  # Audio and Video in Decentraland
7
7
 
8
- ## Authoring split
9
-
10
- - **`AudioSource`** (local audio files), **`AudioStream`** (streaming URLs), and **`VideoPlayer`** are all supported in `main-entities.ts` — declare the speaker / radio / screen entity fully there with the streaming/playback config.
11
- - Volume / play / pause toggles at runtime happen in `src/index.ts` via `getMutable`.
12
-
13
8
  ## When to Use Which Media Component
14
9
 
15
10
  | Need | Component | Key Difference |
@@ -25,26 +20,22 @@ description: Add sound effects, music, audio streaming, and video players to Dec
25
20
 
26
21
  ## Audio Source (Sound Effects & Music)
27
22
 
28
- Declare the speaker in `main-entities.ts`:
23
+ Play audio clips from files:
29
24
 
30
25
  ```typescript
31
- // main-entities.ts
32
- import type { Scene } from '@dcl/sdk/scene-types'
33
-
34
- export const scene = {
35
- speaker: {
36
- components: {
37
- Transform: { position: { x: 8, y: 1, z: 8 } },
38
- AudioSource: {
39
- audioClipUrl: 'sounds/music.mp3',
40
- playing: true,
41
- loop: true,
42
- volume: 0.5, // 0 to 1
43
- pitch: 1.0 // Playback speed (0.5 = half speed, 2.0 = double)
44
- }
45
- }
46
- }
47
- } satisfies Scene
26
+ import { engine, Transform, AudioSource } from '@dcl/sdk/ecs'
27
+ import { Vector3 } from '@dcl/sdk/math'
28
+
29
+ const speaker = engine.addEntity()
30
+ Transform.create(speaker, { position: Vector3.create(8, 1, 8) })
31
+
32
+ AudioSource.create(speaker, {
33
+ audioClipUrl: 'sounds/music.mp3',
34
+ playing: true,
35
+ loop: true,
36
+ volume: 0.5, // 0 to 1
37
+ pitch: 1.0 // Playback speed (0.5 = half speed, 2.0 = double)
38
+ })
48
39
  ```
49
40
 
50
41
  ### Supported Formats
@@ -52,26 +43,6 @@ export const scene = {
52
43
  - `.ogg`
53
44
  - `.wav`
54
45
 
55
- ### Spatial vs Non-Spatial Audio
56
-
57
- `AudioSource` defaults to spatial (volume falls off with distance). For background music / radio / non-positional sound effects, set `global: true`:
58
-
59
- ```typescript
60
- // main-entities.ts
61
- bg_music: {
62
- components: {
63
- Transform: { position: { x: 0, y: 0, z: 0 } }, // ignored when global
64
- AudioSource: {
65
- audioClipUrl: 'sounds/bg.mp3',
66
- playing: true,
67
- loop: true,
68
- volume: 0.5,
69
- global: true // heard everywhere in the scene at constant volume
70
- }
71
- }
72
- }
73
- ```
74
-
75
46
  ### File Organization
76
47
  ```
77
48
  project/
@@ -84,127 +55,97 @@ project/
84
55
  └── scene.json
85
56
  ```
86
57
 
87
- ### Play/Stop/Toggle (runtime, in `src/index.ts`)
58
+ ### Play/Stop/Toggle
88
59
  ```typescript
89
- import { engine, AudioSource } from '@dcl/sdk/ecs'
90
-
91
- export function main() {
92
- const speaker = engine.getEntityOrNullByName('speaker')
93
- if (!speaker) return
60
+ // Play
61
+ AudioSource.getMutable(speaker).playing = true
94
62
 
95
- AudioSource.getMutable(speaker).playing = true // play
96
- AudioSource.getMutable(speaker).playing = false // stop
63
+ // Stop
64
+ AudioSource.getMutable(speaker).playing = false
97
65
 
98
- // toggle
99
- const audio = AudioSource.getMutable(speaker)
100
- audio.playing = !audio.playing
101
- }
66
+ // Toggle
67
+ const audio = AudioSource.getMutable(speaker)
68
+ audio.playing = !audio.playing
102
69
  ```
103
70
 
104
71
  ### Play on Click
105
-
106
- Static entities (the button mesh and the click-sfx speaker) go in `main-entities.ts`. `PointerEvents` and the click handler are runtime — they live in `src/index.ts`.
107
-
108
72
  ```typescript
109
- // main-entities.ts
110
- sfx_button: {
111
- components: {
112
- Transform: { position: { x: 8, y: 1, z: 8 } },
113
- MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
114
- }
115
- },
116
- click_sfx: {
117
- components: {
118
- Transform: { position: { x: 8, y: 1, z: 8 } },
119
- AudioSource: {
120
- audioClipUrl: 'sounds/click.mp3',
121
- playing: false,
122
- loop: false,
123
- volume: 0.8
124
- }
125
- }
126
- }
127
- ```
73
+ import { pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
74
+
75
+ const button = engine.addEntity()
76
+ // ... set up transform and mesh ...
77
+
78
+ const audioEntity = engine.addEntity()
79
+ Transform.create(audioEntity, { position: Vector3.create(8, 1, 8) })
80
+ AudioSource.create(audioEntity, {
81
+ audioClipUrl: 'sounds/click.mp3',
82
+ playing: false,
83
+ loop: false,
84
+ volume: 0.8
85
+ })
128
86
 
129
- ```typescript
130
- // src/index.ts
131
- import { engine, AudioSource, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
132
-
133
- export function main() {
134
- const button = engine.getEntityOrNullByName('sfx_button')
135
- const sfx = engine.getEntityOrNullByName('click_sfx')
136
- if (!button || !sfx) return
137
-
138
- pointerEventsSystem.onPointerDown(
139
- { entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Play sound' } },
140
- () => {
141
- // Reset and play
142
- const audio = AudioSource.getMutable(sfx)
143
- audio.playing = false
144
- audio.playing = true
145
- }
146
- )
147
- }
87
+ pointerEventsSystem.onPointerDown(
88
+ { entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Play sound' } },
89
+ () => {
90
+ // Reset and play
91
+ const audio = AudioSource.getMutable(audioEntity)
92
+ audio.playing = false
93
+ audio.playing = true
94
+ }
95
+ )
148
96
  ```
149
97
 
150
98
  ## Audio Streaming
151
99
 
152
- `AudioStream` is supported in `main-entities.ts` — declare the radio entity with its streaming config in one place:
100
+ Stream audio from a URL (radio, live streams):
153
101
 
154
102
  ```typescript
155
- // main-entities.ts
156
- radio: {
157
- components: {
158
- Transform: { position: { x: 8, y: 1, z: 8 } },
159
- GltfContainer: { src: 'models/radio.glb' },
160
- AudioStream: {
161
- url: 'https://example.com/stream.mp3',
162
- playing: true,
163
- volume: 0.3
164
- }
165
- }
166
- }
167
- ```
103
+ import { engine, Transform, AudioStream } from '@dcl/sdk/ecs'
104
+ import { Vector3 } from '@dcl/sdk/math'
105
+
106
+ const radio = engine.addEntity()
107
+ Transform.create(radio, { position: Vector3.create(8, 1, 8) })
168
108
 
169
- Toggling play / volume at runtime is the same `getMutable` pattern as `AudioSource`.
109
+ AudioStream.create(radio, {
110
+ url: 'https://example.com/stream.mp3',
111
+ playing: true,
112
+ volume: 0.3
113
+ })
114
+ ```
170
115
 
171
116
  ## Video Player
172
117
 
173
- `VideoPlayer`, `MeshRenderer`, and the screen Transform all go in `main-entities.ts`. The video **texture binding** in `Material` needs a runtime Entity ID, not a name — the build only resolves `Transform.parent` by name. So `Material` is set at runtime in `src/index.ts`:
118
+ Play video on a surface:
174
119
 
175
120
  ```typescript
176
- // main-entities.ts
177
- video_screen: {
178
- components: {
179
- Transform: {
180
- position: { x: 8, y: 3, z: 15.9 },
181
- scale: { x: 8, y: 4.5, z: 1 } // 16:9 ratio
182
- },
183
- MeshRenderer: { mesh: { $case: 'plane', plane: { uvs: [] } } },
184
- VideoPlayer: {
185
- src: 'https://example.com/video.mp4',
186
- playing: true,
187
- loop: true,
188
- volume: 0.5,
189
- playbackRate: 1.0,
190
- position: 0 // start time in seconds
191
- }
192
- }
193
- }
194
- ```
195
-
196
- ```typescript
197
- // src/index.ts
198
- import { engine, Material } from '@dcl/sdk/ecs'
121
+ import { engine, Transform, VideoPlayer, Material, MeshRenderer } from '@dcl/sdk/ecs'
122
+ import { Vector3 } from '@dcl/sdk/math'
123
+
124
+ // Create a screen
125
+ const screen = engine.addEntity()
126
+ Transform.create(screen, {
127
+ position: Vector3.create(8, 3, 15.9),
128
+ scale: Vector3.create(8, 4.5, 1) // 16:9 ratio
129
+ })
130
+ MeshRenderer.setPlane(screen)
131
+
132
+ // Add video player
133
+ VideoPlayer.create(screen, {
134
+ src: 'https://example.com/video.mp4',
135
+ playing: true,
136
+ loop: true,
137
+ volume: 0.5,
138
+ playbackRate: 1.0,
139
+ position: 0 // Start time in seconds
140
+ })
199
141
 
200
- export function main() {
201
- const screen = engine.getEntityOrNullByName('video_screen')
202
- if (!screen) return
142
+ // Create video texture
143
+ const videoTexture = Material.Texture.Video({ videoPlayerEntity: screen })
203
144
 
204
- const videoTexture = Material.Texture.Video({ videoPlayerEntity: screen })
205
- // Basic material — better performance than PBR for video surfaces
206
- Material.setBasicMaterial(screen, { texture: videoTexture })
207
- }
145
+ // Basic material (recommended better performance)
146
+ Material.setBasicMaterial(screen, {
147
+ texture: videoTexture
148
+ })
208
149
  ```
209
150
 
210
151
  ### Video Controls
@@ -241,48 +182,6 @@ Material.setPbrMaterial(screen, {
241
182
  })
242
183
  ```
243
184
 
244
- ### Video on a GLTF Surface (Curved Screens, TVs, Monitors)
245
-
246
- When the "screen" is part of a model (a TV in a living room scene, a curved arena display), keep the GLTF and override its screen material with the video texture via `GltfNodeModifiers` at runtime:
247
-
248
- ```typescript
249
- // main-entities.ts — declare the TV model
250
- tv: {
251
- components: {
252
- Transform: { position: { x: 8, y: 1.5, z: 8 } },
253
- GltfContainer: { src: 'models/tv.glb' },
254
- VideoPlayer: { src: 'https://example.com/show.mp4', playing: true, loop: true }
255
- }
256
- }
257
- ```
258
-
259
- ```typescript
260
- // src/index.ts — bind the video texture to the screen sub-mesh by path
261
- import { engine, Material, GltfNodeModifiers } from '@dcl/sdk/ecs'
262
-
263
- export function main() {
264
- const tv = engine.getEntityOrNullByName('tv')
265
- if (!tv) return
266
-
267
- const videoTexture = Material.Texture.Video({ videoPlayerEntity: tv })
268
- GltfNodeModifiers.createOrReplace(tv, {
269
- modifiers: [
270
- {
271
- path: 'TV/Screen', // GLTF node path to the screen sub-mesh
272
- material: {
273
- material: {
274
- $case: 'unlit',
275
- unlit: { texture: videoTexture }
276
- }
277
- }
278
- }
279
- ]
280
- })
281
- }
282
- ```
283
-
284
- Use `path: ''` (empty) to apply the video material to every node of the model — useful when the whole model is the screen (e.g., a flat billboard mesh exported from Blender).
285
-
286
185
  ### Video Events
287
186
 
288
187
  Monitor video playback state:
@@ -38,11 +38,7 @@ const MyUI = () => (
38
38
  )
39
39
 
40
40
  export function setupUi() {
41
- // ALWAYS pass virtualWidth + virtualHeight — the renderer scales the layout
42
- // to fit the player's window using these as the reference. Without them,
43
- // sizes are interpreted in raw pixels and won't behave consistently across
44
- // resolutions and aspect ratios.
45
- ReactEcsRenderer.setUiRenderer(MyUI, { virtualWidth: 1920, virtualHeight: 1080 })
41
+ ReactEcsRenderer.setUiRenderer(MyUI)
46
42
  }
47
43
  ```
48
44
 
@@ -308,25 +304,6 @@ The `Dropdown` component supports additional props:
308
304
  />
309
305
  ```
310
306
 
311
- ### Multiple UI Modules (`addUiRenderer` / `removeUiRenderer`)
312
-
313
- If you have several independent UI modules — e.g., a HUD, a dialog system, a debug overlay — combine them under a single root *or* use `addUiRenderer` to mount each module against an **owner entity**. When the owner entity is deleted, the UI renderer is removed automatically.
314
-
315
- ```tsx
316
- import { engine, ReactEcsRenderer } from '@dcl/sdk/react-ecs'
317
-
318
- const hudOwner = engine.addEntity()
319
- ReactEcsRenderer.addUiRenderer(hudOwner, () => <HudOverlay />)
320
-
321
- // later, when the HUD should disappear:
322
- engine.removeEntity(hudOwner) // also removes the UI renderer
323
-
324
- // or explicitly:
325
- ReactEcsRenderer.removeUiRenderer(hudOwner)
326
- ```
327
-
328
- Each `addUiRenderer` mount renders independently. Useful for dynamic UIs that should appear/disappear based on game state without manually conditioning every sub-tree of one giant root component.
329
-
330
307
  ## Troubleshooting
331
308
 
332
309
  | Problem | Cause | Solution |
@@ -335,7 +312,7 @@ Each `addUiRenderer` mount renders independently. Useful for dynamic UIs that sh
335
312
  | UI elements overlapping | Missing `flexDirection` or wrong layout | Set `flexDirection: 'column'` on the parent container |
336
313
  | Button clicks not registering | Missing `onMouseDown` handler | Add `onMouseDown={() => { ... }}` to the Button or UiEntity |
337
314
  | JSX errors at compile time | File extension is `.ts` instead of `.tsx` | Rename the file to `.tsx` |
338
- | Multiple UIs fighting | More than one `setUiRenderer` call | Use ONE `setUiRenderer` for the main UI; for independent modules use `addUiRenderer(ownerEntity, ...)` instead |
315
+ | Multiple UIs fighting | More than one `setUiRenderer` call | Only call `setUiRenderer` once combine all UI into a single root component |
339
316
  | Text not visible | Text color matches background | Set contrasting `color` on Label or `uiText` |
340
317
 
341
318
  > **World interactions instead of screen UI?** See the **add-interactivity** skill for click handlers and pointer events on 3D objects.
@@ -5,59 +5,6 @@ description: Control camera behavior in Decentraland scenes. CameraMode detectio
5
5
 
6
6
  # Camera Control in Decentraland
7
7
 
8
- ## Authoring split
9
-
10
- `CameraModeArea` and `VirtualCamera` are supported in `main-entities.ts` — both static-by-nature components belong there. `MainCamera` is NOT supported because it lives on the reserved `engine.CameraEntity`; activate a virtual camera at runtime in `src/index.ts`.
11
-
12
- `VirtualCamera.lookAtEntity` accepts an entity **name** in `main-entities.ts` (resolved to an Entity ID at build time, same as `Transform.parent`).
13
-
14
- ```typescript
15
- // main-entities.ts
16
- cinematic_cam: {
17
- components: {
18
- Transform: {
19
- position: { x: 12, y: 4, z: 8 },
20
- rotation: { x: 0, y: 0.7071, z: 0, w: 0.7071 }
21
- },
22
- VirtualCamera: {
23
- defaultTransition: { transitionMode: { $case: 'time', time: 2 } },
24
- lookAtEntity: 'shopkeeper' // name of another entity in this file
25
- }
26
- }
27
- },
28
- first_person_zone: {
29
- components: {
30
- Transform: { position: { x: 8, y: 1, z: 8 } },
31
- CameraModeArea: {
32
- area: { x: 16, y: 2, z: 16 },
33
- mode: 0 // CameraType.CT_FIRST_PERSON
34
- }
35
- }
36
- }
37
- ```
38
-
39
- Activate the cinematic camera at runtime:
40
-
41
- ```typescript
42
- // src/index.ts
43
- import { engine, MainCamera } from '@dcl/sdk/ecs'
44
-
45
- export function main() {
46
- const cam = engine.getEntityOrNullByName('cinematic_cam')
47
- if (cam) MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cam })
48
- }
49
- ```
50
-
51
- The reserved `engine.CameraEntity` and `engine.PlayerEntity` are engine-managed and have no representation in `main-entities.ts`.
52
-
53
- ### CameraType values for `CameraModeArea.mode`
54
-
55
- | value | enum | meaning |
56
- |---|---|---|
57
- | 0 | CT_FIRST_PERSON | Force first-person inside the zone |
58
- | 1 | CT_THIRD_PERSON | Force third-person inside the zone |
59
- | 2 | CT_CINEMATIC | Force cinematic camera inside the zone |
60
-
61
8
  ## Reading Camera State
62
9
 
63
10
  Access the camera's current position and rotation via the reserved `engine.CameraEntity`:
@@ -252,30 +199,12 @@ engine.addSystem(followNpcCamera)
252
199
 
253
200
  > **Freezing player during cutscenes?** Combine VirtualCamera with `InputModifier` from the **advanced-input** skill to prevent player movement during cinematic sequences.
254
201
 
255
- ## Camera vs Colliders (preventing camera-through-wall)
256
-
257
- In third-person mode the player's camera can slide through walls if the wall's collider mask doesn't include `CL_POINTER`. The camera uses the **pointer** collider mask for occlusion checks — not `CL_PHYSICS`. Set both masks on walls and architecture that the camera should bounce off:
258
-
259
- ```typescript
260
- // main-entities.ts
261
- wall: {
262
- components: {
263
- Transform: { position: { x: 8, y: 1.5, z: 16 } },
264
- GltfContainer: {
265
- src: 'models/wall.glb',
266
- visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
267
- }
268
- }
269
- }
270
- ```
271
-
272
- For GLBs that ship with invisible collider meshes (Creator Hub asset packs), set `invisibleMeshesCollisionMask: 3` instead. Default of `CL_PHYSICS` only would let the camera pass through.
273
-
274
202
  ## Best Practices
275
203
 
276
- - Only one VirtualCamera should be active at a time.
277
- - Use `CameraModeArea` to force first-person in tight indoor spaces.
278
- - Keep transition speeds between 0.5 and 3.0 for comfortable camera movement.
279
- - Read camera state via `engine.CameraEntity` never try to write to it directly.
280
- - For look-at detection, combine camera position with raycasting (see `add-interactivity` skill).
281
- - Camera control is read-only outside of VirtualCamera and CameraModeArea you cannot directly move the player's camera.
204
+ - Only one VirtualCamera should be active at a time
205
+ - Use `CameraModeArea` to force first-person in tight indoor spaces
206
+ - Keep transition speeds between 0.5 and 3.0 for comfortable camera movement
207
+ - Always provide a way for the player to exit forced camera modes (e.g., leave the area)
208
+ - Read camera state via `engine.CameraEntity` never try to write to it directly
209
+ - For look-at detection, combine camera position with raycasting (see `add-interactivity` skill)
210
+ - Camera control is read-only outside of VirtualCamera and CameraModeArea — you cannot directly move the player's camera
@@ -52,68 +52,26 @@ Update the `display` fields and parcels:
52
52
  - `scene.parcels` — for multi-parcel scenes, list all parcels (e.g., `["0,0", "0,1", "1,0", "1,1"]` for 2x2)
53
53
  - `scene.base` — set to the southwest corner parcel
54
54
 
55
- ### `main-entities.ts` + `src/index.ts`
56
-
57
- OpenDCL scenes use a **two-file authoring model**:
58
-
59
- - `main-entities.ts` (scene root) — typed declarative entities + their data components, keyed by Name. Compiled to `main.crdt` at build time and preloaded by the engine before `main()` runs.
60
- - `src/index.ts` — behavior only. References entities by `Name` via `engine.getEntityOrNullByName<EntityName>(name)` and attaches systems, pointer events, tweens, etc.
61
-
62
- **Example `main-entities.ts`:**
55
+ ### src/index.ts
56
+ Replace the generated code with the user's scene. Example:
63
57
 
64
58
  ```typescript
65
- import type { Scene } from '@dcl/sdk/scene-types'
66
-
67
- export const scene = {
68
- blue_cube: {
69
- components: {
70
- Transform: { position: { x: 8, y: 1, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
71
- MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
72
- Material: {
73
- material: { $case: 'pbr', pbr: { albedoColor: { r: 0.2, g: 0.5, b: 1, a: 1 } } },
74
- },
75
- },
76
- },
77
- } satisfies Scene
78
- ```
79
-
80
- The `satisfies Scene` clause keeps the literal keys typed (so `keyof typeof scene` gives the typed entity-name union), while still validating the shape against the `Scene` schema.
81
-
82
- **Example `src/index.ts`:**
83
-
84
- ```typescript
85
- import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
86
- import type { scene } from '../main-entities'
87
-
88
- type EntityName = keyof typeof scene
59
+ import { engine, Transform, MeshRenderer, Material } from '@dcl/sdk/ecs'
60
+ import { Vector3, Color4 } from '@dcl/sdk/math'
89
61
 
90
62
  export function main() {
91
- const cube = engine.getEntityOrNullByName<EntityName>('blue_cube')
92
- if (cube === null) return
93
-
94
- pointerEventsSystem.onPointerDown(
95
- { entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } },
96
- () => console.log('clicked'),
97
- )
98
- }
99
- ```
100
-
101
- `tsconfig.json` should include `main-entities.ts` so it gets type-checked:
102
- ```json
103
- {
104
- "extends": "@dcl/sdk/types/tsconfig.ecs7.json",
105
- "include": ["src/**/*.ts", "src/**/*.tsx", "main-entities.ts"]
63
+ // Create a cube at the center of the scene
64
+ const cube = engine.addEntity()
65
+ Transform.create(cube, {
66
+ position: Vector3.create(8, 1, 8)
67
+ })
68
+ MeshRenderer.setBox(cube)
69
+ Material.setPbrMaterial(cube, {
70
+ albedoColor: Color4.create(0.2, 0.5, 1, 1)
71
+ })
106
72
  }
107
73
  ```
108
74
 
109
- **Rules:**
110
-
111
- - Every editable / declared entity must have a unique Name in `main-entities.ts`.
112
- - Dynamic entities created at runtime (effects, projectiles, dynamic UI markers) use `engine.addEntity()` directly. **Don't give dynamic entities a Name** — they don't go in `main-entities.ts`.
113
- - Anything that's pure data (Transform, GltfContainer, MeshRenderer, MeshCollider, Material, AudioSource, VideoPlayer, TextShape, Animator config, NftShape, Billboard, VisibilityComponent) goes in `main-entities.ts`.
114
- - Anything that's behavior (pointer callbacks, systems, tween triggers, conditional logic) goes in `src/`.
115
- - The `scene` literal must be JSON-compatible — no function calls, no spreads, no comments inside the object.
116
-
117
75
  ### scene.json Reference
118
76
 
119
77
  All valid `scene.json` fields:
@@ -189,4 +147,3 @@ After customizing the files:
189
147
  - Y axis is up, minimum Y=0 (ground)
190
148
  - The `main` field in scene.json MUST be `"bin/index.js"` — this is the compiled output path
191
149
  - The `jsx` and `jsxImportSource` tsconfig settings are already included by `/init` — do not modify them
192
- - **Never pass `undefined` values in Transform fields** (position, rotation, scale) — the SDK serializer crashes. If a field is optional, omit the key entirely instead of including it with an `undefined` value.
@@ -77,18 +77,6 @@ npx @dcl/sdk-commands deploy
77
77
  }
78
78
  ```
79
79
 
80
- ### Scene Tipping (`creator`)
81
-
82
- Let visitors send MANA tips to the scene creator. Add a `creator` field with the recipient's wallet address:
83
-
84
- ```json
85
- {
86
- "creator": "0x1234567890123456789012345678901234567890"
87
- }
88
- ```
89
-
90
- When set, a piggy-bank icon appears in the top-left for visitors. Clicking it opens a MANA tip modal. If the address is linked to a Decentraland NAME, the name is shown in the modal. Creators receive an in-app notification for each tip. Also configurable via Creator Hub → scene Settings → Details → Creator wallet address.
91
-
92
80
  ### Spawn Points
93
81
 
94
82
  Configure where players appear when entering the scene:
@@ -44,41 +44,6 @@ All Worlds are automatically listed on the [Places page](https://places.decentra
44
44
  }
45
45
  ```
46
46
 
47
- ### Skybox + Minimap configuration
48
-
49
- `worldConfiguration` accepts extra fields that aren't available for Genesis City scenes:
50
-
51
- ```json
52
- {
53
- "worldConfiguration": {
54
- "name": "my-name.dcl.eth",
55
- "skyboxConfig": {
56
- "fixedTime": 36000,
57
- "textures": ["textures/skybox.png"]
58
- },
59
- "miniMapConfig": {
60
- "visible": true,
61
- "dataImage": "images/minimap.png",
62
- "estateImage": "images/estate.png"
63
- }
64
- }
65
- }
66
- ```
67
-
68
- `skyboxConfig.fixedTime`:
69
-
70
- | Value | Time of day |
71
- |---|---|
72
- | `0` | Midnight |
73
- | `18000` | 6 AM (sunrise) |
74
- | `36000` | Noon |
75
- | `45000` | 6 PM (sunset) |
76
- | `50400` | Maximum |
77
-
78
- Omit `fixedTime` for a dynamic day/night cycle. `textures` is an array of cubemap face textures (top/bottom/front/back/left/right) when you want a fully custom sky.
79
-
80
- `miniMapConfig`: set `visible: true` to show the minimap inside the World; `dataImage` is the base tile, `estateImage` overlays estate boundaries.
81
-
82
47
  ## 2. Deploy
83
48
 
84
49
  **Use the `/deploy` command** — it auto-detects the `worldConfiguration` in scene.json and deploys to the Worlds content server automatically.
@@ -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`. **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.
20
+ All limits scale with parcel count `n`. Know these formulas and design within them.
21
21
 
22
22
  | Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
23
23
  |---|---|---|---|---|---|---|
@@ -35,6 +35,7 @@ All limits scale with parcel count `n`. **Except for hard MB file-size limits on
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
38
39
  - **Use texture atlases** to combine multiple small textures into one, reducing draw calls and material count
39
40
  - Prefer compressed formats (WebP) over raw PNG where possible
40
41
  - Share texture references across materials — do not duplicate texture files