@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,132 @@
1
+ ---
2
+ name: multiplayer-sync
3
+ description: Synchronize state between players in Decentraland multiplayer scenes using CRDT-based networking. Use when user wants multiplayer, sync state, network entities, shared world state, or real-time collaboration.
4
+ ---
5
+
6
+ # Multiplayer Synchronization in Decentraland
7
+
8
+ Decentraland scenes are inherently multiplayer. All players in the same scene share the same space. SDK7 uses CRDT-based synchronization.
9
+
10
+ ## How Sync Works
11
+
12
+ - Components on entities created via `engine.addEntity()` are **automatically synced** between all players in the scene.
13
+ - The Decentraland runtime uses CRDTs (Conflict-free Replicated Data Types) to resolve conflicts.
14
+ - Last-write-wins semantics for most components (Transform, Material, etc.).
15
+ - No server code needed — sync is built into the runtime.
16
+
17
+ ## Basic Synced Entity
18
+
19
+ Any entity with standard components syncs automatically:
20
+
21
+ ```typescript
22
+ import { engine, Transform, MeshRenderer, Material } from '@dcl/sdk/ecs'
23
+ import { Vector3, Color4 } from '@dcl/sdk/math'
24
+
25
+ // This entity and all its components sync to all players
26
+ const sharedCube = engine.addEntity()
27
+ Transform.create(sharedCube, { position: Vector3.create(8, 1, 8) })
28
+ MeshRenderer.setBox(sharedCube)
29
+ Material.setPbrMaterial(sharedCube, { albedoColor: Color4.Red() })
30
+
31
+ // When any player changes the transform, all players see it
32
+ function moveCube() {
33
+ const transform = Transform.getMutable(sharedCube)
34
+ transform.position.x += 1 // All players see this change
35
+ }
36
+ ```
37
+
38
+ ## Custom Synced Components
39
+
40
+ Define custom components that sync between players:
41
+
42
+ ```typescript
43
+ import { engine, Schemas } from '@dcl/sdk/ecs'
44
+
45
+ // Define a custom synced component
46
+ const ScoreBoard = engine.defineComponent('scoreBoard', {
47
+ score: Schemas.Int,
48
+ playerName: Schemas.String,
49
+ lastUpdated: Schemas.Int64
50
+ })
51
+
52
+ // Use it on an entity — automatically syncs
53
+ const board = engine.addEntity()
54
+ ScoreBoard.create(board, { score: 0, playerName: '', lastUpdated: 0 })
55
+
56
+ // Update from any player
57
+ function addScore(points: number) {
58
+ const data = ScoreBoard.getMutable(board)
59
+ data.score += points
60
+ data.lastUpdated = Date.now()
61
+ }
62
+ ```
63
+
64
+ ## Player-Specific Data
65
+
66
+ Use `PlayerIdentityData` to distinguish players:
67
+
68
+ ```typescript
69
+ import { engine, PlayerIdentityData } from '@dcl/sdk/ecs'
70
+
71
+ engine.addSystem(() => {
72
+ for (const [entity] of engine.getEntitiesWith(PlayerIdentityData)) {
73
+ const data = PlayerIdentityData.get(entity)
74
+ console.log('Player:', data.address, 'Guest:', data.isGuest)
75
+ }
76
+ })
77
+ ```
78
+
79
+ ## Schema Types
80
+
81
+ Available schema types for custom components:
82
+
83
+ | Type | Usage |
84
+ |------|-------|
85
+ | `Schemas.Boolean` | true/false |
86
+ | `Schemas.Int` | Integer numbers |
87
+ | `Schemas.Float` | Decimal numbers |
88
+ | `Schemas.String` | Text strings |
89
+ | `Schemas.Int64` | Large integers (timestamps) |
90
+ | `Schemas.Vector3` | 3D coordinates |
91
+ | `Schemas.Quaternion` | Rotations |
92
+ | `Schemas.Color3` | RGB colors |
93
+ | `Schemas.Color4` | RGBA colors |
94
+ | `Schemas.Entity` | Entity reference |
95
+ | `Schemas.Array(innerType)` | Array of values |
96
+ | `Schemas.Map(valueType)` | Key-value maps |
97
+ | `Schemas.Optional(innerType)` | Nullable values |
98
+ | `Schemas.Enum(enumType)` | Enum values |
99
+
100
+ ## Communication Patterns
101
+
102
+ ### Global State (Shared Object)
103
+ ```typescript
104
+ // One entity holds shared game state
105
+ const gameState = engine.addEntity()
106
+ const GameState = engine.defineComponent('gameState', {
107
+ phase: Schemas.String,
108
+ timeRemaining: Schemas.Int,
109
+ isActive: Schemas.Boolean
110
+ })
111
+ GameState.create(gameState, { phase: 'waiting', timeRemaining: 60, isActive: false })
112
+ ```
113
+
114
+ ### Per-Player State
115
+ ```typescript
116
+ // Track each player's state separately using their entity
117
+ engine.addSystem(() => {
118
+ for (const [entity] of engine.getEntitiesWith(PlayerIdentityData)) {
119
+ // Each player's entity is unique to them
120
+ // Attach custom components to player entities for per-player data
121
+ }
122
+ })
123
+ ```
124
+
125
+ ## Important Notes
126
+
127
+ - **All component changes sync automatically** — no explicit "send" calls needed
128
+ - **CRDT resolution**: If two players change the same component simultaneously, last-write-wins
129
+ - **No server-side code**: Decentraland scenes run entirely client-side with CRDT sync
130
+ - **Entity limits apply**: Each synced entity counts toward the scene's entity budget
131
+ - **Custom schemas must be deterministic**: Same component name = same schema across all clients
132
+ - For server-authoritative multiplayer with validation and anti-cheat, see the `authoritative-server` skill
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: nft-blockchain
3
+ description: Display NFT artwork and interact with blockchain from Decentraland scenes. Show NFTs using NftShape with frame styles, check player wallet with getPlayer, sign messages with signedFetch, interact with smart contracts using eth-connect and createEthereumProvider, and handle MANA transactions. Use when user wants NFTs, blockchain, wallet, smart contracts, Web3, or crypto.
4
+ ---
5
+
6
+ # NFT and Blockchain in Decentraland
7
+
8
+ ## Display NFT Artwork
9
+
10
+ Show an NFT from Ethereum in a decorative frame:
11
+
12
+ ```typescript
13
+ import { engine, Transform, NftShape, NftFrameType } from '@dcl/sdk/ecs'
14
+ import { Vector3, 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
+ })
27
+ ```
28
+
29
+ ### NFT URN Format
30
+
31
+ ```
32
+ urn:decentraland:ethereum:erc721:<contractAddress>:<tokenId>
33
+ ```
34
+
35
+ - Works with any ERC-721 NFT on Ethereum mainnet
36
+ - The image is loaded automatically from the NFT's metadata
37
+
38
+ ### Available Frame Styles
39
+
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
+ ```
65
+
66
+ ## Check Player Wallet
67
+
68
+ ```typescript
69
+ import { getPlayer } from '@dcl/sdk/src/players'
70
+
71
+ function checkWallet() {
72
+ const player = getPlayer()
73
+ if (player && !player.isGuest) {
74
+ console.log('Player wallet address:', player.userId)
75
+ // userId is the Ethereum wallet address
76
+ } else {
77
+ console.log('Player is guest (no wallet)')
78
+ }
79
+ }
80
+ ```
81
+
82
+ Always check `isGuest` before attempting any blockchain interaction — guest players don't have a connected wallet.
83
+
84
+ ## Signed Requests
85
+
86
+ Send authenticated requests to a backend, signed with the player's wallet:
87
+
88
+ ```typescript
89
+ import { signedFetch } from '@dcl/sdk/signed-fetch'
90
+
91
+ executeTask(async () => {
92
+ try {
93
+ const response = await signedFetch('https://example.com/api/action', {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify({
97
+ action: 'claimReward',
98
+ amount: 100
99
+ })
100
+ })
101
+
102
+ const result = await response.json()
103
+ console.log('Result:', result)
104
+ } catch (error) {
105
+ console.log('Request failed:', error)
106
+ }
107
+ })
108
+ ```
109
+
110
+ `signedFetch` automatically includes a cryptographic signature proving the player's identity. Your backend can verify this signature to authenticate requests.
111
+
112
+ ## MANA Transactions
113
+
114
+ ```typescript
115
+ import { manaUser } from '@dcl/sdk/ethereum'
116
+
117
+ executeTask(async () => {
118
+ try {
119
+ // Check MANA balance
120
+ const balance = await manaUser.balance()
121
+ console.log('MANA balance:', balance)
122
+
123
+ // Send MANA to another address
124
+ const result = await manaUser.send('0x123...abc', 100) // 100 MANA
125
+ console.log('MANA sent:', result)
126
+ } catch (error) {
127
+ console.log('MANA transaction failed:', error)
128
+ }
129
+ })
130
+ ```
131
+
132
+ ## Smart Contract Interaction
133
+
134
+ Requires the `eth-connect` package:
135
+
136
+ ```bash
137
+ npm install eth-connect
138
+ ```
139
+
140
+ ### Store ABI in a Separate File
141
+
142
+ ```typescript
143
+ // contracts/myContract.ts
144
+ export default [
145
+ {
146
+ "constant": true,
147
+ "inputs": [{ "name": "_owner", "type": "address" }],
148
+ "name": "balanceOf",
149
+ "outputs": [{ "name": "balance", "type": "uint256" }],
150
+ "type": "function"
151
+ }
152
+ // ... rest of ABI
153
+ ]
154
+ ```
155
+
156
+ ### Create Contract Instance
157
+
158
+ ```typescript
159
+ import { RequestManager, ContractFactory } from 'eth-connect'
160
+ import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
161
+ import { abi } from '../contracts/myContract'
162
+
163
+ executeTask(async () => {
164
+ try {
165
+ // Create web3 provider
166
+ const provider = createEthereumProvider()
167
+ const requestManager = new RequestManager(provider)
168
+
169
+ // Create contract at a specific address
170
+ const factory = new ContractFactory(requestManager, abi)
171
+ const contract = await factory.at('0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb') as any
172
+
173
+ // Read data (no gas required)
174
+ const balance = await contract.balanceOf('0x123...abc')
175
+ console.log('Balance:', balance)
176
+ } catch (error) {
177
+ console.log('Contract interaction failed:', error)
178
+ }
179
+ })
180
+ ```
181
+
182
+ ### Write Operations (Require Gas)
183
+
184
+ ```typescript
185
+ executeTask(async () => {
186
+ try {
187
+ const userData = getPlayer()
188
+ if (userData.isGuest) return
189
+
190
+ // Write operation — prompts the player to sign the transaction
191
+ const writeResult = await contract.transfer(
192
+ '0xRecipientAddress',
193
+ 100,
194
+ {
195
+ from: userData.userId,
196
+ gas: 100000,
197
+ gasPrice: await requestManager.eth_gasPrice()
198
+ }
199
+ )
200
+ console.log('Transaction hash:', writeResult)
201
+ } catch (error) {
202
+ console.log('Transaction failed:', error)
203
+ }
204
+ })
205
+ ```
206
+
207
+ ### Gas Price and Balance Checking
208
+
209
+ ```typescript
210
+ import { RequestManager } from 'eth-connect'
211
+ import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
212
+
213
+ executeTask(async () => {
214
+ const provider = createEthereumProvider()
215
+ const requestManager = new RequestManager(provider)
216
+
217
+ const gasPrice = await requestManager.eth_gasPrice()
218
+ console.log('Current gas price:', gasPrice)
219
+
220
+ const balance = await requestManager.eth_getBalance('0x123...abc', 'latest')
221
+ console.log('Account balance:', balance)
222
+ })
223
+ ```
224
+
225
+ ## Testing with Sepolia
226
+
227
+ For development, use the Sepolia testnet:
228
+
229
+ 1. Set MetaMask to Sepolia network
230
+ 2. Get test ETH from a Sepolia faucet
231
+ 3. Deploy your contracts to Sepolia
232
+ 4. Contract addresses differ between mainnet and testnet — use environment checks
233
+
234
+ ## Best Practices
235
+
236
+ - **Always check `isGuest`** before any blockchain interaction — guest players can't sign transactions
237
+ - Use `executeTask(async () => { ... })` for all async blockchain calls
238
+ - Store ABI files separately (e.g., `contracts/`) — don't inline large ABIs
239
+ - Handle errors gracefully — blockchain operations can fail (rejected by user, insufficient gas, network issues)
240
+ - `eth-connect` must be installed as a dependency: `npm install eth-connect`
241
+ - Use `signedFetch` for backend authentication instead of raw `fetch` — it proves the player's identity
242
+ - Read operations (view/pure functions) don't require gas; write operations prompt the user to sign
243
+ - Test on Sepolia before deploying to mainnet
244
+ - NFT URNs only work with Ethereum mainnet ERC-721 tokens
245
+
246
+ For component field details, see `context/components-reference.md`.
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: optimize-scene
3
+ description: Optimize Decentraland scene performance, reduce entity count, minimize triangle budgets, improve loading times, and stay within scene limits. Use when user wants to optimize, improve performance, fix lag, reduce load time, or check scene limits.
4
+ ---
5
+
6
+ # Optimizing Decentraland Scenes
7
+
8
+ ## Scene Limits (Per Parcel Count)
9
+
10
+ | Parcels | Max Entities | Max Triangles | Max Textures | Max Materials | Max Height |
11
+ |---------|-------------|---------------|-------------|--------------|-----------|
12
+ | 1 | 512 | 10,000 | 10 MB | 20 | 20m |
13
+ | 2 | 1,024 | 20,000 | 20 MB | 40 | 20m |
14
+ | 4 | 2,048 | 40,000 | 40 MB | 80 | 20m |
15
+ | 8 | 4,096 | 80,000 | 80 MB | 160 | 20m |
16
+ | 16 | 8,192 | 160,000 | 160 MB | 320 | 20m |
17
+
18
+ ## Entity Count Optimization
19
+
20
+ ### Reuse Entities
21
+ ```typescript
22
+ // BAD: Creating new entity each time
23
+ function spawnBullet() {
24
+ const bullet = engine.addEntity() // Creates entity every call
25
+ // ...
26
+ }
27
+
28
+ // GOOD: Object pooling
29
+ const bulletPool: Entity[] = []
30
+ function getBullet(): Entity {
31
+ const existing = bulletPool.find(e => !ActiveBullet.has(e))
32
+ if (existing) return existing
33
+ const newBullet = engine.addEntity()
34
+ bulletPool.push(newBullet)
35
+ return newBullet
36
+ }
37
+ ```
38
+
39
+ ### Remove Unused Entities
40
+ ```typescript
41
+ engine.removeEntity(entity) // Frees the entity slot
42
+ ```
43
+
44
+ ### Use Parenting
45
+ Instead of separate transforms for each child, use entity hierarchy:
46
+ ```typescript
47
+ const parent = engine.addEntity()
48
+ Transform.create(parent, { position: Vector3.create(8, 0, 8) })
49
+
50
+ // Children inherit parent transform
51
+ const child1 = engine.addEntity()
52
+ Transform.create(child1, { position: Vector3.create(0, 1, 0), parent })
53
+
54
+ const child2 = engine.addEntity()
55
+ Transform.create(child2, { position: Vector3.create(1, 1, 0), parent })
56
+ ```
57
+
58
+ ## Triangle Count Optimization
59
+
60
+ ### Use Lower-Poly Models
61
+ - Small props: 100-500 triangles
62
+ - Medium objects: 500-1,500 triangles
63
+ - Large buildings: 1,500-5,000 triangles
64
+ - Hero pieces: Up to 10,000 triangles
65
+
66
+ ### Use LOD (Level of Detail)
67
+ Show simpler models at distance:
68
+ ```typescript
69
+ engine.addSystem(() => {
70
+ // Check distance to player and swap models
71
+ const playerPos = Transform.get(engine.PlayerEntity).position
72
+ const objPos = Transform.get(myEntity).position
73
+ const distance = Vector3.distance(playerPos, objPos)
74
+
75
+ const gltf = GltfContainer.getMutable(myEntity)
76
+ if (distance > 30) {
77
+ gltf.src = 'models/building_lod2.glb' // Low poly
78
+ } else if (distance > 15) {
79
+ gltf.src = 'models/building_lod1.glb' // Medium poly
80
+ } else {
81
+ gltf.src = 'models/building_lod0.glb' // High poly
82
+ }
83
+ })
84
+ ```
85
+
86
+ ### Use Primitives Instead of Models
87
+ For simple shapes, `MeshRenderer` is lighter than loading a .glb:
88
+ ```typescript
89
+ MeshRenderer.setBox(entity) // Very cheap
90
+ MeshRenderer.setSphere(entity) // Cheap
91
+ MeshRenderer.setPlane(entity) // Very cheap
92
+ ```
93
+
94
+ ## Texture Optimization
95
+
96
+ - Use `.png` for UI/sprites with transparency
97
+ - Use `.jpg` for photos and textures without transparency
98
+ - Compress textures: 512x512 or 1024x1024 max for most use cases
99
+ - Use texture atlases (combine multiple textures into one image)
100
+ - Avoid 4096x4096 textures unless absolutely necessary
101
+ - Reuse materials across entities:
102
+ ```typescript
103
+ // GOOD: Define material once, apply to many
104
+ Material.setPbrMaterial(entity1, { texture: Material.Texture.Common({ src: 'images/wall.jpg' }) })
105
+ Material.setPbrMaterial(entity2, { texture: Material.Texture.Common({ src: 'images/wall.jpg' }) })
106
+ // Same texture URL = shared in memory
107
+ ```
108
+
109
+ ## System Optimization
110
+
111
+ ### Avoid Per-Frame Allocations
112
+ ```typescript
113
+ // BAD: Creates new Vector3 every frame
114
+ engine.addSystem(() => {
115
+ const target = Vector3.create(8, 1, 8) // Allocation!
116
+ })
117
+
118
+ // GOOD: Reuse constants
119
+ const TARGET = Vector3.create(8, 1, 8)
120
+ engine.addSystem(() => {
121
+ // Use TARGET
122
+ })
123
+ ```
124
+
125
+ ### Throttle Expensive Operations
126
+ ```typescript
127
+ let lastCheck = 0
128
+ engine.addSystem((dt) => {
129
+ lastCheck += dt
130
+ if (lastCheck < 0.5) return // Only run every 0.5 seconds
131
+ lastCheck = 0
132
+ // Expensive operation here
133
+ })
134
+ ```
135
+
136
+ ### Remove Systems When Not Needed
137
+ ```typescript
138
+ const systemFn = (dt: number) => { /* ... */ }
139
+ engine.addSystem(systemFn)
140
+
141
+ // When no longer needed:
142
+ engine.removeSystem(systemFn)
143
+ ```
144
+
145
+ ## Loading Time Optimization
146
+
147
+ - Lazy-load 3D models (load on demand, not all at scene start)
148
+ - Use compressed .glb files (Draco compression)
149
+ - Minimize total asset size
150
+ - Use CDN URLs for large shared assets when possible
151
+ - Preload critical assets, defer non-essential ones
152
+
153
+ ## Common Performance Pitfalls
154
+
155
+ 1. **Too many systems**: Each system runs every frame. Combine related logic.
156
+ 2. **Unnecessary component queries**: Cache `engine.getEntitiesWith()` results when the set doesn't change.
157
+ 3. **Large GLTF files**: Optimize in Blender before export (decimate, remove hidden faces).
158
+ 4. **Uncompressed audio**: Use .mp3 instead of .wav for music (10x smaller).
159
+ 5. **Continuous raycasting**: Set `continuous: false` unless you need per-frame raycasting.
160
+ 6. **Text rendering**: `TextShape` is expensive. Use `Label` (UI) for text that doesn't need to be in 3D space.