skills/create-scene/SKILL.md
Scaffold a new Decentraland SDK7 scene project. Creates scene.json, package.json, tsconfig.json, and src/index.ts. Covers scene.json schema (parcels, spawnPoints, permissions, featureToggles), multi-parcel layouts, and project structure. Use when the user wants to start a new scene, create a project, or set up from scratch. Do NOT use for deployment (see deploy-scene or deploy-worlds).
npx skillsauth add dcl-regenesislabs/opendcl create-sceneInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Runtime constraint: Decentraland runs in a QuickJS sandbox. No Node.js APIs (
fs,http,path,process). Use the SDK'sexecuteTask()+fetch()for async work. See the scene-runtime skill for details.
When the user wants to create a new scene, follow these steps:
If the user hasn't described their scene, ask them:
/initAlways run /init first. This uses the official @dcl/sdk-commands init to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration, and installs dependencies automatically.
Never manually create scene.json, package.json, or tsconfig.json — the SDK templates may change between versions and hand-written copies will diverge.
Before writing scene code, search the asset catalog for free models that match the user's theme:
grep -i "keyword" {baseDir}/../add-3d-models/references/model-catalog.md
{baseDir}/../../context/audio-catalog.md (50 free sounds — music, ambient, SFX, game mechanics, etc.)preview: URL at the end of each catalog line) to visually confirm modelsmodels/ directory:
mkdir -p models
curl -o models/zombie-purple.glb "https://models.dclregenesislabs.xyz/blobs/bafybeiffc..."
Important:
GltfContaineronly works with local files. Never use external URLs for the modelsrcfield.
After /init completes, customize the generated files based on what the user wants:
Update the display fields and parcels:
display.title — set to the scene namedisplay.description — set to a short descriptionscene.parcels — for multi-parcel scenes, list all parcels (e.g., ["0,0", "0,1", "1,0", "1,1"] for 2x2)scene.base — set to the southwest corner parcelmain-entities.ts + src/index.tsOpenDCL scenes use a two-file authoring model:
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.src/index.ts — behavior only. References entities by Name via engine.getEntityOrNullByName<EntityName>(name) and attaches systems, pointer events, tweens, etc.Example main-entities.ts:
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
blue_cube: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
Material: {
material: { $case: 'pbr', pbr: { albedoColor: { r: 0.2, g: 0.5, b: 1, a: 1 } } },
},
},
},
} satisfies Scene
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.
Example src/index.ts:
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
import type { scene } from '../main-entities'
type EntityName = keyof typeof scene
export function main() {
const cube = engine.getEntityOrNullByName<EntityName>('blue_cube')
if (cube === null) return
pointerEventsSystem.onPointerDown(
{ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } },
() => console.log('clicked'),
)
}
tsconfig.json should include main-entities.ts so it gets type-checked:
{
"extends": "@dcl/sdk/types/tsconfig.ecs7.json",
"include": ["src/**/*.ts", "src/**/*.tsx", "main-entities.ts"]
}
Rules:
main-entities.ts.engine.addEntity() directly. Don't give dynamic entities a Name — they don't go in main-entities.ts.main-entities.ts.src/.scene literal must be JSON-compatible — no function calls, no spreads, no comments inside the object.All valid scene.json fields:
| Field | Required | Description |
|-------|----------|-------------|
| ecs7 | Yes | Must be true for SDK7 |
| runtimeVersion | Yes | Must be "7" |
| main | Yes | Must be "bin/index.js" — the compiled output path |
| display.title | Recommended | Scene name shown in the map and Places |
| display.description | Recommended | Short description for discovery |
| display.navmapThumbnail | Optional | Image path for the Genesis City minimap |
| scene.parcels | Yes | Array of "x,y" coordinate strings |
| scene.base | Yes | The origin parcel (usually southwest corner) |
| spawnPoints | Optional | Where players appear when entering (see below) |
| requiredPermissions | Optional | Array of permissions (e.g., "ALLOW_MEDIA_HOSTNAMES") |
| allowedMediaHostnames | Optional | Whitelisted domains for external media |
| featureToggles | Optional | Enable/disable SDK features |
| worldConfiguration | Optional | For Worlds deployment (see deploy-worlds skill) |
| authoritativeMultiplayer | Optional | Enable authoritative server mode (see authoritative-server skill) |
Configure where and how players enter the scene:
{
"spawnPoints": [
{
"name": "spawn1",
"default": true,
"position": { "x": [1, 5], "y": [0, 0], "z": [2, 4] },
"cameraTarget": { "x": 8, "y": 1, "z": 8 }
}
]
}
[1, 5]) spawn players randomly within the rangecameraTarget orients the player's camera on spawn — point it at the scene's focal area"x": 8)| Layout | Parcels Array | Use Case |
|--------|--------------|----------|
| Single | ["0,0"] | Small games, galleries, single-room experiences |
| Strip | ["0,0", "1,0", "2,0"] | Hallways, racing tracks, linear journeys |
| L-Shape | ["0,0", "1,0", "0,1"] | Corner buildings, split experiences |
| 2x2 Square | ["0,0", "1,0", "0,1", "1,1"] | Open plazas, arenas, medium games |
| 3x3 Square | 9 parcels from "0,0" to "2,2" | Large games, multi-room buildings |
Base parcel: Always set scene.base to the southwest (lowest x,y) corner parcel.
Boundaries per parcel: 16m x 16m x 20m height. A 2x2 scene spans 32m x 32m.
After customizing the files:
preview tool to start the preview server (or run npx @dcl/sdk-commands start --bevy-web manually)main field in scene.json MUST be "bin/index.js" — this is the compiled output pathjsx and jsxImportSource tsconfig settings are already included by /init — do not modify themundefined 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.development
Capture screenshots of the running Decentraland preview to verify scene changes visually. Covers camera movement, interaction actions, and visual debugging. Use when the preview is running and you need to check what the scene looks like, debug visual issues, or verify layout. Do NOT use for code changes (make changes first, then screenshot).
development
Cross-cutting runtime APIs for Decentraland SDK7 scenes. Use when the user needs async operations (executeTask), HTTP requests (fetch, signedFetch), WebSocket connections, timers, realm/scene detection, restricted actions (movePlayerTo, teleportTo, triggerEmote, openExternalUrl), portable experiences, or the testing framework. Do NOT use for UI (see build-ui), multiplayer sync (see multiplayer-sync), or avatar/player data (see player-avatar).
development
Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
data-ai
Player and avatar system in Decentraland. Read player position/profile, customize appearance (AvatarBase), trigger emotes (triggerEmote/triggerSceneEmote), read equipped wearables (AvatarEquippedData), attach objects to players (AvatarAttach), create NPC avatars (AvatarShape), avatar modifier areas, and locomotion settings. Use when the user wants player data, emotes, wearables, NPC avatars, avatar attachments, or movement speed changes. Do NOT use for wallet/blockchain interactions (see nft-blockchain).