lecodes-sdk
v2.0.4
Published
Downloads
2,197
Readme
sdk — the unified, import-free scripting API
One coherent, import-free syntax for le.codes projects across 2D (creator-2d), 3D
(creator-gl), UI (creator-ui), and the platform runtime. The whole library is delivered through
esbuild's inject(), so user code uses globals (Scene, Sprite, Mesh, UIScreen, setLoop, …)
with no imports; tree-shaking drops whatever a project doesn't use.
Replaces the worker package. See PLAN.md for the full design and build log.
API reference: the complete, curated docs live in docs/ — every global, every
method, example-first. Run bun run docs:check to verify coverage + links.
// 2D — no imports
const scene = new Scene2D({ background: '#10131a' })
const player = new Sprite({ texture: await Texture2D.load(asset('hero.png')), anchor: [0.5, 1] })
.clips({ size: [32, 48], idle: [0, 1, 2, 3], walk: [4, 5, 6, 7] })
player.play('idle')
scene.add(player); scene.open()
scene.camera.follow(player, { smooth: 0.15 })
setLoop(dt => { if (Input.key('ArrowRight')) { player.x += 120 * dt; player.play('walk') } })
// 3D — no imports
const world = new Scene({ bloom: true })
const ball = Mesh.sphere({ radius: 0.5, material: Material.lit({ color: '#e33' }), position: [0, 5, 0] })
.physics({ motion: 'dynamic', shape: { type: 'sphere', radius: 0.5 } })
world.add(ball, Light.sun({ castShadows: true }))
world.camera.position = [0, 6, 12]; world.camera.lookAt([0, 0, 0])
world.open()
setLoop(() => { if (Input.key('Space')) ball.body.applyImpulse([0, 6, 0]) })Fluent math (Vec2 / Vec3 / Quat / Mat4)
Per-frame math reads like the formula — no imports, no out-params. Vectors are mutable structs with
pure methods: fields are settable (v.x = 3, v.set(…)), but every method returns a NEW value and
never mutates its source. They're iterable and accept raw tuples anywhere, so [0, 1, 0] works wherever
a vector does.
const dir = target.position.sub(self.position).normalize()
self.position = self.position.add(dir.scale(speed * dt))
if (a.distanceTo(b) < 2) explode()
node.quaternion = node.quaternion.mul(Quat.fromAxisAngle(Vec3.up, turn))
// the node API returns fresh copies (value semantics, like Unity):
node.x = 3 // loud single-axis nudge
const p = node.position; p.y = 5; node.position = p // local-mutate, then assign back
// node.position.x = 3 ← a no-op (mutates a discarded copy); use node.x instead
// Mat4 keeps the metal exposed (public .m) for low-level surgery:
let m = node.matrix.translate(pivot).rotateY(yaw * dt).translate(pivot.negate())
m.m[12] += vx * dt
node.matrix = mQuat/Mat4 cover the rest: Quat.fromEuler/lookRotation/slerp, Mat4.compose/decompose/lookAt/
perspective. All of it is fuzz-tested against gl-matrix (dev-only oracle) in tests/math/oracle.test.ts.
Design rules
- No ECS / no components. Built-in behaviors are chainable node methods (
.physics(),.collider(),.clips()/.play()); reuse is a factory function. Per-frame logic issetLoop(dt => …). - Few globals. Variants are static factory methods (
Mesh.box(),Material.lit(),Texture2D.load()), not free functions. - Pure re-skin. Calls the existing
_creator/_creator2d/_creatorTree/_creatorUtilshost bridges — no native engine changes.
Layout
src/
inject.ts # THE aggregation point — the only file that exports public globals
bridges.d.ts # ambient host ABI (_creator*, declared, never bundled)
host.d.ts # ambient host runtime globals (setLoop, console, DEG2RAD, asset)
core/ # handle registry, color, event Emitter
math/ # Mathf (scalar) + fluent Vec2/Vec3 (vec), Quat (quat), Mat4 (mat4) — no gl-matrix shipped
runtime/ # fetch, storage, device, input, media, net, files, touch, misc
g2/ # 2D: Scene2D, Node2D, Sprite, Tilemap, Texture2D, Camera2D
gl/ # 3D: Scene, ARScene, Node, Mesh, Light, Material, Geometry, Texture,
# Camera, Ray, Plane, Noise, Particles
ui/ # UIScreen/Row/Column/Text/Button/… + Router + registerFont
animate/ # animate, cubicBezier, easings
plugins/ # optional, host-provided capabilities (QRScanner, CameraView) — gated on host support
compile/ # the user-project compiler (bundler + header + compileProject) — backend/CLI use itBuild & test
bun run build # → dist/inject.js (runtime), dist/global.d.ts + host.d.ts + types.json (editor types)
bun test # compile/parity + math/color + 2D + 3D-runtime + UI tests
bun run typecheck # tsc --noEmitTests live in tests/<area>/, one folder per src/<area> they exercise; tests/README.md
has the layout, the shared stubs and the one-process rules.
tests/headless/ holds live WebGL2 render harnesses (manual; need chrome-headless-shell). The 2D
harness is green: a project compiled through the real compileProject renders correct pixels.
How it integrates
The backend (backend/src/utils/compileProject.ts) and CLI (lecodes-cli) compile every user
project through this SDK (sdk/compile) — the legacy worker path was removed from user-project
compilation. A bare import from a runtime module (creator/ui/animate/utils/2d) is no longer
special-cased; it's just an unresolved import, so the build fails loudly and points at the file. The
worker package still exists for internal tooling (scene-viewer / model-preview), not for user code.
Deferred (API present, native pending)
- 2D physics / collider triggers (
.collider()is a no-op stub;Node2D.add()hierarchy). - Live 2D↔3D engine switching is wired in the viewer host (two canvases) but pending a real dual project to verify.
ARScene.addControls(target, options?) ports the AR object-placement gesture recipe (one-finger drag,
two-finger pinch-scale + twist-rotate) onto the fluent math; returns a { remove() } handle.
