LeCodesdocs

Conventions

Cross-cutting rules that hold everywhere in the SDK. Every reference page assumes these.

No imports — everything is a global

The whole SDK surface (Scene, Sprite, UIScreen, Vec3, fetch, …) is injected at build time; user code never imports it. A project's only import statements are relative paths to its own files (import { hud } from './ui/hud') and to its assets (import hero from './hero.png'). A bare package import is a build error.

Entry point: the file with no exports. In a multi-file project it wires the others together and starts the app (Router.init(home) or scene.open()).

Assets: reference a project file with an import or the inline asset('./path') macro — a compile-time rewrite, so the path must be a string literal. Remote https://… URLs are plain strings.

Time

  • setLoop(dt => …) fires every frame; dt is seconds since the previous frame (~0.016 at 60 fps). Frame-independent motion is position += speed * dt.
  • setTimeout / setInterval take milliseconds.
  • Animation durations are milliseconds: animate({ duration }) and UI .animateTo() / .animateFrom() (duration, delay).

Space, units, angles

2D (Scene2D) 3D (Scene) UI / screen
Axes X right, Y up right-handed, Y up, forward = −Z X right, Y down
Unit 1 world unit = 1 logical px at zoom 1 ≈ meters logical px (dp/pt)
Angles rotation in degrees, CCW eulerAngles in degrees
  • Raw-angle arguments are radians (Quat.fromAxisAngle, Mat4.rotate*, Vec2.rotate); euler-angle properties and factories are degrees (node.rotation, Quat.fromEuler). DEG2RAD / RAD2DEG are compile-time constants for converting.
  • Screen coordinates (clientX/clientY, device.width/height) are logical px (like iOS pt / Android dp), never physical pixels; device.pixelRatio is the physical-per-logical ratio.
  • One sign trap: 2D world Y points up, but screen Y and sprite anchor space point down (anchor: [0.5, 1] = bottom-center = "feet").

Colors

Anywhere a color is accepted, all of these work (ColorInput): hex strings "#e33", "#e33c", "#ff3333", "#ff3333cc"; packed int 0xff3333 (24-bit RGB only — alpha can't be passed as a number); normalized float arrays [r, g, b] / [r, g, b, a] (0..1).

Note

hex is the only string form. CSS-style rgba(...) / named colors are not parsed — they silently become opaque black. (UI styles are the exception: they go to the UI engine, which has its own parser — see ui/styling.md.)

Math value semantics

Vec2 / Vec3 / Quat / Mat4 are mutable structs with pure methods: fields are settable (v.x = 3), but every method returns a NEW value and never mutates its source. They are iterable and interchangeable with raw tuples — [0, 1, 0] works wherever a Vec3 is accepted.

Node transform getters return fresh copies (value semantics, like Unity):

TypeScript
node.position.x = 3                                   // ✗ silent no-op — mutates a discarded copy
node.x = 3                                            // ✓ single-axis setter
const p = node.position; p.y += 1; node.position = p  // ✓ mutate a local, assign back

Events

  • Scenes, nodes, device, WebSocket, media players: addEventListener(channel, cb) / removeEventListener(channel, cb).
  • UI elements instead use chainable on* methods (.onClick(cb), .onChange(cb)).
  • Pointer events: click fires on pointer-up over the target; touchstart fires on pointer-down and its event object can ev.track({...}) to capture the rest of the gesture. See runtime/touch.md.

Chaining

Constructors take an options object; setters that configure return this, so building reads as one chain. UI factories chain .style() and on*. Node capabilities attach as aspects and also chain:

TypeScript
const hero = new Sprite({ texture })
  .aspect(SpriteAnimation, { size: [32, 48], fps: 10, clips: { walk: [1, 2, 3] } })
  .aspect(Shape2D, { box: [16, 8] })
  .aspect(Physics2D, { motion: 'dynamic', fixedRotation: true })
hero.anim.play('walk')          // each aspect adds a named accessor

See core/aspects.md.

Physics owns dynamic transforms

Once a node has a dynamic body (Physics / Physics2D), the physics step writes its transform. Do not set node.position per frame — drive the body with velocity / applyImpulse (kinematic bodies: moveTo). Reading node.position / node.worldPosition is always correct; the SDK re-syncs from native automatically.

Host gating — features that can be absent

Some capabilities depend on the host build. They silently no-op when absent; guard with:

  • Physics.supported (3D physics), Physics2D.supported (2D physics)
  • QRScanner.isSupported
  • device.setPreciseTouch() — no-op where the platform has no coalesced-input concept

Lifecycle

  • Anything started in a screen's onOpen must be stopped in onClose (clearLoop, clearInterval, ws.close()), or it keeps running across navigation.
  • Objects wrapping native resources free them explicitly where a method exists: node.destroy() (2D/3D nodes), player.dispose() (media — throws on any use afterwards), Texture2D.destroy(), response.dispose() (fetch bodies). 3D Texture has no free — it lives until the engine closes.