# SYQEL Worlds — the complete guide for AI agents

You are reading the canonical machine-readable reference for authoring **SYQEL worlds**:
real-time, music-reactive 3D scenes that run inside SYQEL's apps on phones, desktops, and TVs.
If you are an AI helping a creator build a world, this document is your contract. Everything a
compliant world may do is here; anything not here is unavailable, and the review pipeline
enforces that mechanically.

Human docs: https://syqel.dev/docs/ · Review criteria: https://syqel.dev/docs/review/
Creator account & submissions: https://syqel.dev/account/

---

## 1. What a world is

One TypeScript module. Entry file `world.ts`. Default export is `defineWorld({ meta, create })`
from `@syqel/world-sdk`. Your scene class extends `ThreeWorldMode` (a fully managed Three.js
world: the host owns the renderer, post-processing, palette, and the audio analysis; you own the
scene graph and how it answers the music).

Your bundle ships ONLY your own code. `three`, `three/addons/*`, and `@syqel/world-sdk` are
provided by the host at runtime and rewired at build time — never bundled, never versioned by you.

### Minimal complete world

```ts
import * as THREE from 'three'
import { ThreeWorldMode, palColor, defineWorld, type ModeInput } from '@syqel/world-sdk'

class MyWorld extends ThreeWorldMode {
  private boxes: THREE.Mesh[] = []

  protected build(): void {
    for (const _s of this.buildStaged()) { /* drain */ }
  }

  // REQUIRED: a staged build. Each `yield` is a point where a TV may take a frame
  // while your world constructs. ≥5 slices, no slice dominating a heavy build.
  protected *buildStaged(): Generator<void> {
    this.scene.background = new THREE.Color(0x050510)
    this.scene.add(new THREE.AmbientLight(0x8888aa, 1.2))
    yield
    const mat = new THREE.MeshStandardMaterial({ color: 0x334455, emissive: 0x000000 })
    yield
    for (let i = 0; i < 16; i += 1) {
      const box = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), mat.clone())
      box.position.set((i % 4) * 3 - 4.5, Math.floor(i / 4) * 3 - 4.5, -10)
      this.scene.add(box)
      this.boxes.push(box)
    }
    yield
    // ... more slices for floors, particles, set pieces ...
    yield
    this.scene.fog = new THREE.FogExp2(0x050510, 0.02)
    yield
  }

  protected tick(input: ModeInput): void {
    const { frame, render, timeSec } = input
    for (let i = 0; i < this.boxes.length; i += 1) {
      const amp = frame.voices[i]?.amp ?? 0          // this voice's live amplitude
      const box = this.boxes[i]
      box.rotation.y = timeSec * (0.3 + amp)
      box.scale.setScalar(1 + amp * 0.8)
      const m = box.material as THREE.MeshStandardMaterial
      m.emissive.copy(palColor(render, i / 16))      // the HOST's palette, never hardcoded hue
      m.emissiveIntensity = 0.2 + amp * 1.5 + (this.vFlash[i] ?? 0) * 2 // punch on onsets
    }
  }
}

export default defineWorld({
  meta: {
    world: {
      id: 'your-handle/your-world',   // MUST equal the marketplace listing id
      name: 'Your World',
      archetype: 'mellow',            // 'mainstream' | 'acoustic' | 'bassGroove' | 'mellow' | 'extreme'
      mode: 'three',
      threeId: 'your-handle/your-world',
      palette: {
        hueBase: 210,                 // 0..360
        hueRange: 80,                 // hue spread across pitch
        saturation: 70,               // 0..100
        lightness: 55,                // 0..100
        bg: { r: 5, g: 5, b: 16 },    // background / trail fade colour
      },
      motion: {
        travelBase: 0.4,              // base travel multiplier
        travelEnergy: 0.6,            // how much live intensity adds to travel
        scatter: 0.3,                 // burst scatter multiplier
        starDensity: 0,               // 0..1 ambient star presence
      },
      rampSec: 0,                     // optional: seconds to full visual payoff (slow-building worlds)
    },
  },
  create: () => new MyWorld(),
})
```

---

## 2. The authoring surface (everything you may import)

Allowed imports — **this list is closed**:

- `@syqel/world-sdk` — the SDK (below)
- `three` — the host's Three.js instance
- `three/addons/*` — but only these exact specifiers (the host shims a fixed set; anything else
  is `undefined` at runtime and fails review):
  - `three/addons/postprocessing/EffectComposer.js`, `RenderPass.js`, `ShaderPass.js`,
    `UnrealBloomPass.js`, `GTAOPass.js`
  - `three/addons/environments/RoomEnvironment.js`
  - `three/addons/utils/BufferGeometryUtils.js`
  - (`GLTFLoader.js` / `RGBELoader.js` exist on the host but are **not available to marketplace
    worlds in v1** — see the assets note below)
- your own relative files (`./…`) — they bundle into your package

From `@syqel/world-sdk`:

- `defineWorld({ meta, create })` — the module's default export shape. `meta.world` is the
  engine `World` entry (fields above); `meta.volume` and `meta.pro` are host-catalogue hints.
- `ThreeWorldMode` — the base class. Protected surface available to your subclass:
  - `scene: THREE.Scene`, `camera: THREE.PerspectiveCamera` — yours to build and move
  - `vFlash: Float32Array(16)` — per-voice onset flash envelopes (decaying pulses, host-maintained)
  - `pulse: number` — global beat pulse envelope
  - `bloomStrengthBase`, `bloomThresholdBase` — bloom profile. BRIGHT/daylit worlds must RAISE
    the threshold (≥0.6) or the sky blooms into a white wall
  - `envIntensity` — environment-map lighting strength (PBR materials read it)
  - `useAO` — opt into ground-truth ambient occlusion (costs a pass; TVs skip it automatically)
  - `useGrade` + `grade { contrast, saturation, vignette, lift, tint }` — cinematic grade
  - `buildStaged?(): Generator<void>` — implement it; required by review
  - `build(): void` — implement as a drain of `buildStaged()` (see example)
  - `tick(input: ModeInput): void` — your per-frame update. The host renders after
- Helpers: `palColor(render, t)` (host palette colour at position t∈0..1 — use this, never
  hardcoded hues), `spreadPan(pan, i)` (deterministic stereo spread), `jitterGeometry(geo, amt)`
  (organic irregularity), `scaleCount(n)` / `worldDetail` (device-detail scaling — USE for
  particle/instance counts so TVs get proportionally lighter scenes), `enableShadow(obj)`.
- **Assets in v1: worlds are fully procedural — no external files, and no loaders at all.**
  There is no creator asset channel yet, so every loader (`THREE.TextureLoader`, `ImageLoader`,
  `FileLoader`, `GLTFLoader`, `RGBELoader`, …) and the SDK asset API (`loadModel`, `assetUrl`,
  `setAssetBase`) are rejected — including through the namespace (`new THREE.TextureLoader()`)
  and with relative paths. Make textures with `document.createElement('canvas')` and a 2D
  context; make geometry in code. (A gated asset channel is on the roadmap.)
- Types: `VisualMode`, `ModeInput`, `World`, `WorldPackage`.

### `ModeInput` (what `tick` receives every frame)

```
frame:  MusicalFrame   — the audio analysis (below)
render: RenderParams   — host palette + intensity (feed palColor; render.intensity ∈ 0..1)
dt:     number         — seconds since last frame
timeSec: number        — world clock
width, height          — device pixels
```

### `MusicalFrame` (the music, analyzed and calibrated for you)

- `voices: Voice[16]` — sixteen frequency-ordered voices; each `{ amp 0..1, freq 0..1 (low→high),
  pan −1..+1, tonal 0..1, onset: boolean, onsetStrength 0..1 }`. Voice 0 is the lowest register.
- `energy, bass, brightness, width, tonality, dynamics, onsetDensity, activeVoices` — global axes 0..1.
- `buildArc` — −1 (breaking down) .. +1 (building up). Drive tension: thicken fog, saturate,
  swell — and RELEASE on the drop.
- `beat?` — live beat grid: `{ phase, bar, onBeat, onDownbeat, confidence, bpm }`. Gate every
  musical decision on `beat.confidence > 0.4`; the grid reads 0 in silence.
- `sources?` — THE SOURCE CONTRACT: per-instrument envelopes when the host delivers them
  (kick / snare / hats / bass / harmony / air, or ML stems: drums / bass / vocals / guitar /
  piano / other). Each has amplitude, onset flash, identity hue, pan, and `solidity`
  (real stem vs DSP estimate). **Graceful degrade is REQUIRED**: when `sources` is absent your
  world must render fully on `voices` alone — sources may only ADD.
- `key`, `chroma` — musical key + pitch classes, for harmonically-tinted palettes.

---

## 3. Design principles (what makes a SYQEL world good, not just valid)

1. **The Orchestra Principle.** Sixteen voices → sixteen DISTINCT elements, never 16 clones.
   Register maps to character: the bass voice owns the biggest, slowest thing in the scene; the
   highest voice owns the smallest, fastest. Each element lights in ITS voice's colour
   (`palColor(render, i/16)`) at its stereo side.
2. **React on onsets, not levels.** `voice.onset` / `vFlash[i]` are the hits — punch emissives,
   trigger motion. Levels (`amp`) breathe; onsets strike. A world that only scales with volume
   reads as a screensaver.
3. **Build tension with `buildArc`, spend it on the drop.** The build-up thickens the air; the
   drop is the payoff — a slam, a flush, a reveal. Section changes deserve a set-piece.
4. **Degrade gracefully.** No sources → full show on voices. No beat confidence → no quantized
   tricks. Silence → an idle scene that still looks intentional.
5. **TVs are first-class.** Use `scaleCount()` for every particle/instance count. Stage your
   build (≥5 yields, no monolithic slice). Procedural canvas textures: keep them ≤2048² and
   paint once at build, not per frame.
6. **The scene must read at rest.** Ambient light + fog + composition should look like a place
   before a single note plays.

---

## 4. Hard constraints (the review pipeline rejects these mechanically)

Banned everywhere in your source — presence alone fails review:

- Any import outside the closed list in §2
- `eval`, `new Function`, dynamic `import()`
- Network: `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`
- Storage: `localStorage`, `sessionStorage`, `indexedDB`, cookies
- Workers: `Worker`, `SharedWorker`, `ServiceWorker`, `importScripts`, `MessageChannel`,
  `BroadcastChannel`, `postMessage`
- Ambient globals: `globalThis`, `window`, `self`, `top`, `parent`, `frames`, `navigator`,
  `location`, `history`, `process`, `require`, `crypto`, `WebAssembly`, `SharedArrayBuffer`,
  `Atomics`, `OffscreenCanvas`, `Reflect`, `Proxy`, `URL`, and the host global `__SYQEL_SDK__`
- Self-scheduling: `setTimeout`, `setInterval`, `queueMicrotask`, `requestAnimationFrame`,
  `requestIdleCallback` — worlds are tick-driven; the host calls your `tick()`
- Realm-escape / prototype tricks: `.constructor`, `.__proto__`, `.prototype`,
  `Object.defineProperty`/`setPrototypeOf`/`getPrototypeOf`
- **Absolute-URL string literals** (`https:`, `wss:`, `blob:`, `//host`) anywhere — a THREE
  loader pointed at a URL is a network beacon no `fetch` ban catches. `data:` URIs are fine.
- `document` — with ONE exception: `document.createElement('canvas')` for procedural textures
  (paint with the 2D context, wrap in `THREE.CanvasTexture`)

Worlds render. They do not reach out, phone home, store, schedule, or touch the realm. Your
package is rebuilt from source by SYQEL's pinned toolchain, statically analyzed, executed headless
under instrumentation (any trapped access fails), and human-reviewed — on every version. Static
analysis is a tripwire, not the boundary: the boundary is the rebuild, the signature, and
revocation. Don't try to defeat it — obfuscated worlds are rejected on sight in human review.

## 5. Performance budgets (enforced)

| Budget | Limit |
| --- | --- |
| Source archive (`.tar.gz`, source only) | ≤ 2 MB |
| Built bundle | ≤ 256 KB |
| Staged build | ≥ 5 slices; no slice > 40% of a heavy build |
| Draw calls (mesh count proxy) | ≤ 300 — use instancing and merged geometry |
| Triangles | ≤ 600,000 |
| Textures | ≤ 2048×2048 each; ~64 MB total |

The context: on TV hardware, 580 draw calls runs at 23–27 fps; ≲300 runs at 44–57 fps. Keep
geometry in code and textures procedural — v1 worlds carry no external assets.

## 6. Submitting

A submission is a `.tar.gz` of SOURCE (entry `world.ts`, relative imports allowed) plus a short
`.webm` preview. `meta.world.id` must equal the marketplace listing id (`handle/world-slug`).
Versions are semver and strictly increasing; every version re-runs the full review. States:
`submitted → checks-running → (auto-failed | awaiting-review) → (changes-requested | rejected |
approved) → published`. Gate results are returned verbatim — if a gate fails you get the exact
finding (file, line, reason) to fix.

Submit via SYQEL Studio, or at https://syqel.dev/account/ .

## 7. Checklist before submitting

- [ ] Default export is `defineWorld({ meta, create })`; `meta.world.id` = listing id
- [ ] `buildStaged()` implemented with ≥5 meaningful slices; `build()` drains it
- [ ] Reacts on onsets (`vFlash` / `voice.onset`), breathes on levels, uses `palColor` for colour
- [ ] Renders fully with no `sources`, no `beat` confidence, and in silence
- [ ] Counts go through `scaleCount()`; instancing for repeated geometry; ≤300 meshes
- [ ] No banned identifier anywhere (grep your own source for §4's list)
- [ ] Canvas textures ≤2048², painted at build time
- [ ] Runs at 60 fps in Studio's TV profile with the perf HUD green
