@dcl-regenesislabs/opendcl 0.1.0-22234509684.commit-63dfd19

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 (54) hide show
  1. package/README.md +234 -0
  2. package/context/components-reference.md +113 -0
  3. package/context/open-source-3d-assets.md +705 -0
  4. package/context/sdk7-complete-reference.md +3684 -0
  5. package/context/sdk7-examples.md +1709 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +76 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/scene-context.d.ts +97 -0
  11. package/dist/scene-context.d.ts.map +1 -0
  12. package/dist/scene-context.js +203 -0
  13. package/dist/scene-context.js.map +1 -0
  14. package/dist/utils.d.ts +2 -0
  15. package/dist/utils.d.ts.map +1 -0
  16. package/dist/utils.js +4 -0
  17. package/dist/utils.js.map +1 -0
  18. package/extensions/dcl-context.ts +123 -0
  19. package/extensions/dcl-deploy.ts +89 -0
  20. package/extensions/dcl-header.ts +162 -0
  21. package/extensions/dcl-init.ts +62 -0
  22. package/extensions/dcl-preview.ts +144 -0
  23. package/extensions/dcl-setup-ollama.ts +312 -0
  24. package/extensions/dcl-setup.ts +96 -0
  25. package/extensions/dcl-status.ts +88 -0
  26. package/extensions/dcl-tasks.ts +102 -0
  27. package/extensions/dcl-update-check.ts +79 -0
  28. package/extensions/dcl-validate.ts +80 -0
  29. package/extensions/plan-mode/index.ts +340 -0
  30. package/extensions/plan-mode/utils.ts +168 -0
  31. package/extensions/process-registry.ts +25 -0
  32. package/extensions/scene-utils.ts +31 -0
  33. package/package.json +65 -0
  34. package/prompts/explain.md +16 -0
  35. package/prompts/review.md +19 -0
  36. package/prompts/system.md +126 -0
  37. package/skills/add-3d-models/SKILL.md +115 -0
  38. package/skills/add-interactivity/SKILL.md +176 -0
  39. package/skills/advanced-input/SKILL.md +238 -0
  40. package/skills/advanced-rendering/SKILL.md +235 -0
  41. package/skills/animations-tweens/SKILL.md +173 -0
  42. package/skills/audio-video/SKILL.md +167 -0
  43. package/skills/authoritative-server/SKILL.md +329 -0
  44. package/skills/build-ui/SKILL.md +231 -0
  45. package/skills/camera-control/SKILL.md +199 -0
  46. package/skills/create-scene/SKILL.md +67 -0
  47. package/skills/deploy-scene/SKILL.md +106 -0
  48. package/skills/deploy-worlds/SKILL.md +107 -0
  49. package/skills/lighting-environment/SKILL.md +216 -0
  50. package/skills/multiplayer-sync/SKILL.md +132 -0
  51. package/skills/nft-blockchain/SKILL.md +246 -0
  52. package/skills/optimize-scene/SKILL.md +160 -0
  53. package/skills/player-avatar/SKILL.md +239 -0
  54. package/skills/smart-items/SKILL.md +181 -0
@@ -0,0 +1,329 @@
1
+ ---
2
+ name: authoritative-server
3
+ description: Build multiplayer scenes with a headless authoritative server that controls game state, validates changes, and prevents cheating. Install @dcl/sdk@auth-server and run with hammurabi-server. Use isServer() to branch logic, registerMessages() for client-server communication, validateBeforeChange() for server-only components, Storage for persistence, and EnvVar for configuration. Use when user wants authoritative server, anti-cheat, server-side validation, persistent storage, environment variables, or server messages.
4
+ ---
5
+
6
+ # Authoritative Server Pattern
7
+
8
+ Build multiplayer Decentraland scenes where a **headless server** controls game state, validates changes, and prevents cheating. The same codebase runs on both server and client, with the server having full authority.
9
+
10
+ Before reading this skill, read `{baseDir}/../../context/sdk7-complete-reference.md` for general SDK7 knowledge. For basic CRDT multiplayer (no server), see the `multiplayer-sync` skill instead.
11
+
12
+ ## Setup
13
+
14
+ Install the auth-server SDK branch:
15
+
16
+ ```bash
17
+ npm install @dcl/sdk@auth-server
18
+ ```
19
+
20
+ Your `scene.json` must include a world name:
21
+
22
+ ```json
23
+ {
24
+ "worldConfiguration": {
25
+ "name": "my-world-name"
26
+ }
27
+ }
28
+ ```
29
+
30
+ Run the scene:
31
+
32
+ ```bash
33
+ # With authoritative server (required for this pattern)
34
+ npx @dcl/hammurabi-server@next
35
+
36
+ # Standard dev server (no auth server, for client-only testing)
37
+ npm run start
38
+ ```
39
+
40
+ ## Server/Client Branching
41
+
42
+ Use `isServer()` to branch logic in a single codebase:
43
+
44
+ ```typescript
45
+ import { isServer } from '@dcl/sdk/network'
46
+
47
+ export async function main() {
48
+ if (isServer()) {
49
+ // Server-only: game logic, validation, state management
50
+ const { server } = await import('./server/server')
51
+ server()
52
+ return
53
+ }
54
+
55
+ // Client-only: UI, input, message sending
56
+ setupClient()
57
+ setupUi()
58
+ }
59
+ ```
60
+
61
+ The server runs your scene code headlessly (no rendering). It has access to all player positions via `PlayerIdentityData` and manages all authoritative game state.
62
+
63
+ ## Synced Components with Validation
64
+
65
+ Define custom components that sync from server to all clients. **Always** use `validateBeforeChange()` to prevent clients from modifying server-authoritative state.
66
+
67
+ ### Custom Components (Global Validation)
68
+
69
+ ```typescript
70
+ import { engine, Schemas } from '@dcl/sdk/ecs'
71
+ import { AUTH_SERVER_PEER_ID } from '@dcl/sdk/network/message-bus-sync'
72
+
73
+ export const GameState = engine.defineComponent('game:State', {
74
+ phase: Schemas.String,
75
+ score: Schemas.Number,
76
+ timeRemaining: Schemas.Number
77
+ })
78
+
79
+ // Restrict ALL modifications to server only
80
+ GameState.validateBeforeChange((value) => {
81
+ return value.senderAddress === AUTH_SERVER_PEER_ID
82
+ })
83
+ ```
84
+
85
+ ### Built-in Components (Per-Entity Validation)
86
+
87
+ For built-in components like `Transform` and `GltfContainer`, use per-entity validation so you don't block client-side transforms on the player's own entities:
88
+
89
+ ```typescript
90
+ import { Entity, Transform, GltfContainer } from '@dcl/sdk/ecs'
91
+ import { AUTH_SERVER_PEER_ID } from '@dcl/sdk/network/message-bus-sync'
92
+
93
+ type ComponentWithValidation = {
94
+ validateBeforeChange: (entity: Entity, cb: (value: { senderAddress: string }) => boolean) => void
95
+ }
96
+
97
+ function protectServerEntity(entity: Entity, components: ComponentWithValidation[]) {
98
+ for (const component of components) {
99
+ component.validateBeforeChange(entity, (value) => {
100
+ return value.senderAddress === AUTH_SERVER_PEER_ID
101
+ })
102
+ }
103
+ }
104
+
105
+ // Usage: after creating a server-managed entity
106
+ const entity = engine.addEntity()
107
+ Transform.create(entity, { position: Vector3.create(10, 5, 10) })
108
+ GltfContainer.create(entity, { src: 'assets/model.glb' })
109
+ protectServerEntity(entity, [Transform, GltfContainer])
110
+ ```
111
+
112
+ ### Syncing Entities
113
+
114
+ After creating and protecting an entity, sync it to all clients:
115
+
116
+ ```typescript
117
+ import { syncEntity } from '@dcl/sdk/network'
118
+
119
+ syncEntity(entity, [Transform.componentId, GameState.componentId])
120
+ ```
121
+
122
+ ## Messages
123
+
124
+ Use `registerMessages()` for client-to-server and server-to-client communication:
125
+
126
+ ### Define Messages
127
+
128
+ ```typescript
129
+ import { Schemas } from '@dcl/sdk/ecs'
130
+ import { registerMessages } from '@dcl/sdk/network'
131
+
132
+ export const Messages = {
133
+ // Client -> Server
134
+ playerJoin: Schemas.Map({ displayName: Schemas.String }),
135
+ playerAction: Schemas.Map({ actionType: Schemas.String, data: Schemas.Number }),
136
+
137
+ // Server -> Client
138
+ gameEvent: Schemas.Map({ eventType: Schemas.String, playerName: Schemas.String })
139
+ }
140
+
141
+ export const room = registerMessages(Messages)
142
+ ```
143
+
144
+ ### Send Messages
145
+
146
+ ```typescript
147
+ // Client sends to server
148
+ room.send('playerJoin', { displayName: 'Alice' })
149
+
150
+ // Server sends to ALL clients
151
+ room.send('gameEvent', { eventType: 'ROUND_START', playerName: '' })
152
+
153
+ // Server sends to ONE client
154
+ room.send('gameEvent', { eventType: 'YOU_WIN', playerName: 'Alice' }, { to: [playerAddress] })
155
+ ```
156
+
157
+ ### Receive Messages
158
+
159
+ ```typescript
160
+ // Server receives from client
161
+ room.onMessage('playerJoin', (data, context) => {
162
+ if (!context) return
163
+ const playerAddress = context.from // Wallet address of sender
164
+ console.log(`[Server] Player joined: ${data.displayName} (${playerAddress})`)
165
+ })
166
+
167
+ // Client receives from server
168
+ room.onMessage('gameEvent', (data) => {
169
+ console.log(`Event: ${data.eventType}`)
170
+ })
171
+ ```
172
+
173
+ ### Wait for Room Connection
174
+
175
+ Before sending messages from the client, wait for the connected scene room:
176
+
177
+ ```typescript
178
+ import { engine } from '@dcl/sdk/ecs'
179
+ import { RealmInfo } from '@dcl/sdk/ecs'
180
+
181
+ let joined = false
182
+ engine.addSystem(() => {
183
+ if (joined) return
184
+ const realm = RealmInfo.getOrNull(engine.RootEntity)
185
+ if (realm?.isConnectedSceneRoom) {
186
+ joined = true
187
+ room.send('playerJoin', { displayName: 'Player' })
188
+ }
189
+ })
190
+ ```
191
+
192
+ ## Server Reading Player Positions
193
+
194
+ The server can read **actual** player positions — critical for anti-cheat:
195
+
196
+ ```typescript
197
+ import { engine, PlayerIdentityData, Transform } from '@dcl/sdk/ecs'
198
+
199
+ engine.addSystem(() => {
200
+ for (const [entity, identity] of engine.getEntitiesWith(PlayerIdentityData)) {
201
+ const transform = Transform.getOrNull(entity)
202
+ if (!transform) continue
203
+
204
+ const address = identity.address
205
+ const position = transform.position
206
+ // Use actual server-verified position, not client-reported data
207
+ }
208
+ })
209
+ ```
210
+
211
+ Never trust client-reported positions. Always read `PlayerIdentityData` + `Transform` on the server.
212
+
213
+ ## Storage
214
+
215
+ Persist data across server restarts. **Server-only** — guard with `isServer()`.
216
+
217
+ ```typescript
218
+ import { Storage } from '@dcl/sdk/server'
219
+ ```
220
+
221
+ ### World Storage (Global)
222
+
223
+ Shared across all players:
224
+
225
+ ```typescript
226
+ // Store
227
+ await Storage.world.set('leaderboard', JSON.stringify(leaderboardData))
228
+
229
+ // Retrieve
230
+ const data = await Storage.world.get<string>('leaderboard')
231
+ if (data) {
232
+ const leaderboard = JSON.parse(data)
233
+ }
234
+
235
+ // Delete
236
+ await Storage.world.delete('oldKey')
237
+ ```
238
+
239
+ ### Player Storage (Per-Player)
240
+
241
+ Keyed by player wallet address:
242
+
243
+ ```typescript
244
+ // Store
245
+ await Storage.player.set(playerAddress, 'highScore', String(score))
246
+
247
+ // Retrieve
248
+ const saved = await Storage.player.get<string>(playerAddress, 'highScore')
249
+ const highScore = saved ? parseInt(saved) : 0
250
+
251
+ // Delete
252
+ await Storage.player.delete(playerAddress, 'highScore')
253
+ ```
254
+
255
+ Storage only accepts strings. Use `JSON.stringify()`/`JSON.parse()` for objects and `String()`/`parseInt()` for numbers.
256
+
257
+ Local development storage is at `node_modules/@dcl/sdk-commands/.runtime-data/server-storage.json`.
258
+
259
+ ## Environment Variables
260
+
261
+ Configure your scene without hardcoding values. **Server-only** — guard with `isServer()`.
262
+
263
+ ```typescript
264
+ import { EnvVar } from '@dcl/sdk/server'
265
+
266
+ // Read a variable with default
267
+ const maxPlayers = parseInt((await EnvVar.get('MAX_PLAYERS')) || '4')
268
+ const debugMode = ((await EnvVar.get('DEBUG')) || 'false') === 'true'
269
+ ```
270
+
271
+ ### Local Development
272
+
273
+ Create a `.env` file in your project root:
274
+
275
+ ```
276
+ MAX_PLAYERS=8
277
+ GAME_DURATION=300
278
+ DEBUG=true
279
+ ```
280
+
281
+ Add `.env` to your `.gitignore`.
282
+
283
+ ### Deploy to Production
284
+
285
+ ```bash
286
+ # Set a variable
287
+ npx sdk-commands deploy-env MAX_PLAYERS --value 8
288
+
289
+ # Delete a variable
290
+ npx sdk-commands deploy-env OLD_VAR --delete
291
+ ```
292
+
293
+ Deployed env vars take precedence over `.env` file values.
294
+
295
+ ## Recommended Project Structure
296
+
297
+ ```
298
+ src/
299
+ ├── index.ts # Entry point — isServer() branching
300
+ ├── client/
301
+ │ ├── setup.ts # Client initialization, message handlers
302
+ │ └── ui.tsx # React ECS UI reading synced state
303
+ ├── server/
304
+ │ ├── server.ts # Server init, systems, message handlers
305
+ │ └── gameState.ts # Server state management class
306
+ └── shared/
307
+ ├── schemas.ts # Synced component definitions + validateBeforeChange
308
+ └── messages.ts # Message definitions via registerMessages()
309
+ ```
310
+
311
+ Put synced components and messages in `shared/` so both server and client import the same definitions. Keep server logic (Storage, EnvVar, game systems) in `server/`. Keep UI and client input in `client/`.
312
+
313
+ ## Testing & Debugging
314
+
315
+ - **Log prefixes**: Use `[Server]` and `[Client]` prefixes in `console.log()` to distinguish server and client output in the terminal.
316
+ - **Stale CRDT files**: If you see "Outside of the bounds of written data" errors, delete `main.crdt` and `main1.crdt` files and restart.
317
+ - **Storage inspection**: Check `node_modules/@dcl/sdk-commands/.runtime-data/server-storage.json` to inspect persisted data during local development.
318
+ - **No setTimeout/setInterval**: The DCL runtime does not support these. Use `engine.addSystem()` with a timer variable instead.
319
+ - **Entity sync issues**: Verify you call `syncEntity(entity, [componentIds])` with the correct component IDs (`MyComponent.componentId`).
320
+
321
+ ## Important Notes
322
+
323
+ - **Use `Schemas.Int64` for timestamps**: `Schemas.Number` corrupts large numbers (13+ digits). Always use `Schemas.Int64` for values like `Date.now()`.
324
+ - **Room readiness**: Clients must wait for `RealmInfo.get(engine.RootEntity).isConnectedSceneRoom` before sending messages.
325
+ - **Custom vs built-in validation**: Custom components use global `validateBeforeChange((value) => ...)`. Built-in components (Transform, GltfContainer) use per-entity `validateBeforeChange(entity, (value) => ...)`.
326
+ - **Single codebase**: Both server and client run the same `index.ts` entry point. Use `isServer()` to branch.
327
+ - **No Node.js APIs**: The DCL runtime uses sandboxed QuickJS — no `fs`, `http`, `setTimeout`, etc. Use SDK-provided APIs (Storage, EnvVar, engine systems) instead.
328
+ - **SDK branch**: The auth-server pattern requires `@dcl/sdk@auth-server`, not the standard `@dcl/sdk` package.
329
+ - For basic CRDT multiplayer without a server, see the `multiplayer-sync` skill.
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: build-ui
3
+ description: Build 2D user interfaces for Decentraland scenes using React-ECS. Create HUDs, menus, health bars, scoreboards, dialogs, buttons, and input forms. Use when user wants to add UI, HUD, buttons, text overlays, menus, or on-screen elements.
4
+ ---
5
+
6
+ # Building UI with React-ECS
7
+
8
+ Decentraland SDK7 uses a React-like JSX system for 2D UI overlays.
9
+
10
+ ## Setup
11
+
12
+ ### File: src/ui.tsx
13
+ ```tsx
14
+ import ReactEcs, { UiEntity, Label, Button } from '@dcl/sdk/react-ecs'
15
+
16
+ function MyUI() {
17
+ return (
18
+ <UiEntity
19
+ uiTransform={{
20
+ width: '100%',
21
+ height: '100%',
22
+ justifyContent: 'center',
23
+ alignItems: 'center'
24
+ }}
25
+ >
26
+ <Label value="Hello Decentraland!" fontSize={24} />
27
+ </UiEntity>
28
+ )
29
+ }
30
+
31
+ export function setupUi() {
32
+ ReactEcs.setUiRenderer(MyUI)
33
+ }
34
+ ```
35
+
36
+ ### File: src/index.ts
37
+ ```typescript
38
+ import { setupUi } from './ui'
39
+
40
+ export function main() {
41
+ setupUi()
42
+ }
43
+ ```
44
+
45
+ ### Required tsconfig.json settings
46
+ ```json
47
+ {
48
+ "compilerOptions": {
49
+ "jsx": "react-jsx",
50
+ "jsxImportSource": "@dcl/sdk/react-ecs-lib"
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Core Components
56
+
57
+ ### UiEntity (Container)
58
+ ```tsx
59
+ <UiEntity
60
+ uiTransform={{
61
+ width: 300, // Pixels or '50%'
62
+ height: 200,
63
+ positionType: 'absolute', // 'absolute' or 'relative' (default)
64
+ position: { top: 10, right: 10 }, // Only with absolute
65
+ flexDirection: 'column', // 'row' | 'column'
66
+ justifyContent: 'center', // 'flex-start' | 'center' | 'flex-end' | 'space-between'
67
+ alignItems: 'center', // 'flex-start' | 'center' | 'flex-end' | 'stretch'
68
+ padding: { top: 10, bottom: 10, left: 10, right: 10 },
69
+ margin: { top: 5 },
70
+ display: 'flex' // 'flex' | 'none' (hide)
71
+ }}
72
+ uiBackground={{
73
+ color: { r: 0, g: 0, b: 0, a: 0.8 } // Semi-transparent black
74
+ }}
75
+ />
76
+ ```
77
+
78
+ ### Label (Text)
79
+ ```tsx
80
+ <Label
81
+ value="Score: 100"
82
+ fontSize={18}
83
+ color={{ r: 1, g: 1, b: 1, a: 1 }}
84
+ textAlign="middle-center"
85
+ font="sans-serif"
86
+ uiTransform={{ width: 200, height: 30 }}
87
+ />
88
+ ```
89
+
90
+ ### Button
91
+ ```tsx
92
+ <Button
93
+ value="Click Me"
94
+ variant="primary" // 'primary' | 'secondary'
95
+ fontSize={16}
96
+ uiTransform={{ width: 150, height: 40 }}
97
+ onMouseDown={() => {
98
+ console.log('Button clicked!')
99
+ }}
100
+ />
101
+ ```
102
+
103
+ ### Input
104
+ ```tsx
105
+ import { Input } from '@dcl/sdk/react-ecs'
106
+
107
+ <Input
108
+ placeholder="Type here..."
109
+ fontSize={14}
110
+ color={{ r: 1, g: 1, b: 1, a: 1 }}
111
+ uiTransform={{ width: 250, height: 35 }}
112
+ onSubmit={(value) => {
113
+ console.log('Submitted:', value)
114
+ }}
115
+ />
116
+ ```
117
+
118
+ ### Dropdown
119
+ ```tsx
120
+ import { Dropdown } from '@dcl/sdk/react-ecs'
121
+
122
+ <Dropdown
123
+ options={['Option A', 'Option B', 'Option C']}
124
+ selectedIndex={0}
125
+ onChange={(index) => {
126
+ console.log('Selected:', index)
127
+ }}
128
+ uiTransform={{ width: 200, height: 35 }}
129
+ fontSize={14}
130
+ />
131
+ ```
132
+
133
+ ## State Management
134
+
135
+ Use module-level variables for UI state (React hooks are NOT available):
136
+
137
+ ```tsx
138
+ let score = 0
139
+ let showMenu = false
140
+
141
+ function GameUI() {
142
+ return (
143
+ <UiEntity uiTransform={{ width: '100%', height: '100%' }}>
144
+ {/* HUD - always visible */}
145
+ <Label
146
+ value={`Score: ${score}`}
147
+ fontSize={20}
148
+ uiTransform={{
149
+ positionType: 'absolute',
150
+ position: { top: 10, left: 10 }
151
+ }}
152
+ />
153
+
154
+ {/* Menu - conditionally shown */}
155
+ {showMenu && (
156
+ <UiEntity
157
+ uiTransform={{
158
+ width: 300,
159
+ height: 400,
160
+ positionType: 'absolute',
161
+ position: { top: '50%', left: '50%' }
162
+ }}
163
+ uiBackground={{ color: { r: 0.1, g: 0.1, b: 0.1, a: 0.9 } }}
164
+ >
165
+ <Label value="Game Menu" fontSize={24} />
166
+ <Button
167
+ value="Resume"
168
+ variant="primary"
169
+ onMouseDown={() => { showMenu = false }}
170
+ uiTransform={{ width: 200, height: 40 }}
171
+ />
172
+ </UiEntity>
173
+ )}
174
+ </UiEntity>
175
+ )
176
+ }
177
+
178
+ // Update state from game logic
179
+ export function addScore(points: number) {
180
+ score += points
181
+ }
182
+
183
+ export function toggleMenu() {
184
+ showMenu = !showMenu
185
+ }
186
+ ```
187
+
188
+ ## Common UI Patterns
189
+
190
+ ### Health Bar
191
+ ```tsx
192
+ let health = 100
193
+
194
+ function HealthBar() {
195
+ return (
196
+ <UiEntity
197
+ uiTransform={{
198
+ width: 200, height: 20,
199
+ positionType: 'absolute',
200
+ position: { bottom: 20, left: '50%' }
201
+ }}
202
+ uiBackground={{ color: { r: 0.3, g: 0.3, b: 0.3, a: 0.8 } }}
203
+ >
204
+ <UiEntity
205
+ uiTransform={{ width: `${health}%`, height: '100%' }}
206
+ uiBackground={{ color: { r: 0.2, g: 0.8, b: 0.2, a: 1 } }}
207
+ />
208
+ </UiEntity>
209
+ )
210
+ }
211
+ ```
212
+
213
+ ### Image Background
214
+ ```tsx
215
+ <UiEntity
216
+ uiTransform={{ width: 200, height: 200 }}
217
+ uiBackground={{
218
+ textureMode: 'stretch',
219
+ texture: { src: 'images/logo.png' }
220
+ }}
221
+ />
222
+ ```
223
+
224
+ ## Important Notes
225
+
226
+ - React hooks (`useState`, `useEffect`, etc.) are **NOT** available — use module-level variables
227
+ - The UI renderer re-renders every frame, so state changes are reflected immediately
228
+ - UI is rendered as a 2D overlay on top of the 3D scene
229
+ - Use `display: 'none'` in `uiTransform` to hide elements without removing them
230
+ - File extension must be `.tsx` for JSX support
231
+ - Only one `ReactEcs.setUiRenderer()` call per scene — combine all UI into one root component